多态
- 定义
同一个对象,在不同时刻表现出来的不同形态。(或者说父类的同一个方法在不同子类表现不同的结果,可以理解为表面声明为父类对象,但是真正调用的是那个new子类的方法。就是比如A是B,C的父类。A a1=new B();和A a2=new A():虽然这个a1和a2都是表现为表面是A但是你分别调用a1.f();和a2.f();的结果就不一样,就体现出不同的状态) - 例子:
我们可以说猫是猫:即,猫 cat=new 猫();
也可以说猫是动物,即,动物 animal=new 猫(); - 多态的前提:
- 两个类有继承或者实现的关系
- 有方法重写
- 有父类指向子类对象
- 多态中的成员访问特点
- 成员变量
编译看父类,运行看父类(编译看左边,执行看左边) - 成员方法
编译看父类,运行看子类(编译看左边,执行看右边)
例子:
public class Animal {
public int age = 40;
public void eat() {
System.out.println("动物吃东西");
}
}
public class Cat extends Animal {
public int age = 20;
public int weight = 10;
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void playGame() {
System.out.println("猫捉迷藏");
}
}
public class AnimalDemo {
public static void main(String[] args) {
//有父类引用指向子类对象
Animal a = new Cat();
System.out.println(a.age);//输出40,体现多态的成员变量执行看左边。执行的效果看那个Animal a = new Cat();的左边,即看那个Animal类的age属性
// System.out.println(a.weight);//错误,体现多态的成员变量的编译看左边,这个编译不能通过
a.eat();//输出"猫吃鱼"。体现多态的成员方法,执行看右边。执行的效果看那个Animal a = new Cat();的右边,即看那个Animal类的eat方法
// a.playGame();//错误,体现多态的成员方法,编译看左边,这个编译不能通过
}
}
- 多态的好处和弊端
- 好处
提高程序的扩展性。定义方法时候,使用父类型作为参数,在使用的时候,可以传递这个父类对象或这个父类对象的子类对象。这样有什么好处呢?比如你要是不用多态的话,你一个工具类的一个方法只能处理一种类对象,比如你工具类里面有一个方法是
public void f(A a){a.g();}
那么这个工具类里的这个方法只能处理这个A这种对象f(new A()),但是你要是想传递一个B对象,你就只能再建一个方法。就算这两个方法的语句差不多,也要再建一个方法只是参数列表不同的方法,这样就很浪费时间,代码重复多。
public void f(A a){a.g();}
public void f(B b){b.g();}
但是你要是在工具类里面定义一个这样的方法,然后ZIMU这个类是A,B,C……的父类,那么他们都可以用这一个方法来做事,不用重新定义一个方法了,而且这个zimu.g();的执行效果是看具体传入的对象的。
public void f(ZIMU zimu){zimu.g();}
- 弊端
不能使用子类的特有成员
比如还是用上面那个好处的那个例子:要是父类里只有g()没有h();方法,,但是A对象有这个他特有的父类没有的h()方法,那么你这样使用就会出错,因为编译看左边,即看ZIMU这个类(看对象表明的类型,即接受对象的那个引用的类型)。
public void f(ZIMU zimu){zimu.h();}
- 多态中的转型
转型可以解决多态的弊端。下面介绍转型。转型分为向上转型和向下转型两种。向上转型是自动的,向下转型的话要用“(要转为的类)”这个东西,具体看例子。
- 向上转型
父类引用指向子类对象就是向上转型,如,父类 对象名=new 子类();即父类接受子类都是向上转型。 - 向下转型
格式:子类型 对象名 = (子类型)父类引用;
例子:
public class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
public void playGame() {
System.out.println("猫捉迷藏");
}
}
public class AnimalDemo {
public static void main(String[] args) {
//多态
Animal a = new Cat();//向上转型
a.eat();//编译看左边,执行看右边
// a.playGame();//编译看左边,执行看右边
Cat c = (Cat)a;//向下转型
c.eat();
c.playGame();//向下转型了,所以这个playGame方法就可以用了
}
}
- 注意:向下转型可能是会出现问题的,比如你把一个Animal cat=new Cat(); Dog dog=(Dog)cat;会抛出异常。即把实体为Cat的对象转为别的类可能出现问题,能不能转得看那个实体能不能放进到那个强转的那个类里面。
- instanceof
instance是 Java 的一个二元操作符,类似于 ==,>,< 等操作符。
instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回值为boolean型。
比如,x instanceof A。(其中x是对象,A是类的名字)
那么:
- x是A这个类的子类的实例或x是A这个类的实例都是返回true
- x不是A这个类的子类实例也不是本类实例,都是返回false
- x声明的时候表面上的类型和A这个类没有父子关系,那么编译不通过。就是编译看表面。执行看的是实际是否为他的子类或本类实例
例子:
package com.liudashuai;
//x instanceof A:检验x是否为类A的对象,返回值为boolean型。
// 要求x所属的类与类A必须是子类和父类的关系或本类,否则编译错误。
// 如果x属于类A的子类B,x instanceof A值也为true。
public class Demo4 extends HelloWorld{
public static void main(String[] args) {
Demo2 demo2=new Demo2();
Demo1 demo1=new Demo1();
Demo3 demo3=new Demo3();
Demo4 demo4=new Demo4();
Demo1 demo=new Demo3();
System.out.println(demo1 instanceof Demo1);//true,本类实例instanceof本类
System.out.println(demo1 instanceof Demo2);//false,父类实例instanceof子类
System.out.println(demo3 instanceof Demo2);//true,子类实例instanceof父类,
System.out.println(demo instanceof Demo2);//true,看实际的那个对象的类型判断true还是false。所以看实际的即“Demo3的实例 instanceof Demo2”是true,看表面的话,就是“Demo1的实例是不是Demo2”结果是false
// System.out.println(demo4 instanceof Demo2);//编译错误,因为这个demo4非Demo2这个类子类或父类或本类的实例。要求demo4表面所属的类与类Demo2必须是子类和父类的关系或本类,否则编译错误。=
HelloWorld helloWorld=new Demo4();
//表面是Demo2的父类(其实是子类的子类),实际上是Demo4,而Demo4和Demo2没有父子类关系,所以上面的demo4 instanceof Demo2编译错误是只看表面的
System.out.println(helloWorld instanceof Demo2);//false
}
}
package com.liudashuai;
public class Demo3 extends Demo2{
}
package com.liudashuai;
public class Demo2 extends Demo1{
}
package com.liudashuai;
public class HelloWorld {
}
package com.liudashuai;
public class Demo1 extends HelloWorld{
}
这几个类的父子关系是这样的: