文章目录

  • 简介
  • 演示案例


简介

多态,多态使 Java 更有生命和鲜活起来。多态指子类对象可以直接赋值给父类变量,但在运行时依然表现出子类的特征。Java 引用变量有两种类型,分别是编译时类型和运行时类型,编译时类型由声明类型决定,运行时类型由赋值对象的类型决定。如果编译时类型和运行时类型不一致,就会出现所谓多态。

演示案例

父类的引用(声明成父类型的引用数据类型的变量)指向子类的对象:

Fu obj = new Zi();
int num = obj.num;
obj.show();
obj.methodZi();

1.如果父类和子类都有成员变量 num,请问上面的例子中访问的是谁的成员变量?
答:看等号左边是谁(变量的声明类型),则优先用谁的,没有则向上。所以访问的是父类的成员变量 num。父类的变量是不能被子类覆盖重写的,但是父类的成员变量可以被子类继承,也就是说子类对象中存在来自父类的成员变量 num,同时还存在一个在子类声明的成员变量 num

2.如果父类和子类都有成员方法 show(),请问上面的例子中访问的是谁的成员方法 show()
答:看等号右边是谁(创建哪个类的实例对象,即对象实际所属的类型),则优先调用谁的,没有则向上。子类有 show(),所以就是调用子类的,没有就向上在父类中查找。父类的成员方法可以被子类覆盖重写。如果父类的成员方法被子类重写了,那么就不会再继承到子类中

3.子类有方法 methodZi(),但是父类没有,那么通过变量名称调用方法 methodZi() 是否正确呢?
答:错误。Java 代码在编译的时,对于引用类型的变量,编译时看的是声明的类型,运行时才看对象的实际类型。变量 obj 声明的类型是父类,父类根本没有方法 methodZi(),所以 obj.methodZi(); 编译会报错。为什么 obj.show(); 不会报错,因为父类也有 show() 方法所以编译不会报错,但是在运行时看的是右边,右边的子类也有方法 show(),所以实际调用的是子类的方法 show()

口诀:
对于成员方法:编译看左边,运行看右边。
对于变量(成员变量和静态变量):编译看左边,运行也看左边。

多态的好处:
可以创建各种子类对象,都不会影响父类变量的方法调用代码,因为父类变量是根据父类所声明的方法进行调用的。

注意下面的错误代码:

FuInterface obj = new FuInterfaceImpl();
obj.staticMethod(); // 编译直接报错,原因就是变量obj的声明类型是个接口,如果是具体类则不会报编译错误。提示:只有含有此静态方法的接口类才能调用此静态方法

注:staticMethod() 是接口和实现类都有的静态方法

但是下面的代码没有错误:

Fu obj = new Zi();
obj.staticMethod(); // 编译通过,而且只会调用父类的静态方法,原因就是obj声明的类型Fu是一个具体类

注:
1.staticMethod() 是父类和子类都有的静态方法
2.子类不能覆盖重写父类的静态方法,但是子类也可以声明定义和父类一模一样的静态方法,但不属于覆盖重写
3.obj.staticMethod() 编译时会变成 Fu.staticMethod(),为什么?因为 obj 声明的类型 Fu 是一个具体类,如果 obj 声明的类型是接口,则编译器不会将变量名替换成所声明的类型名称

通过父类型的变量去访问静态变量,则无论变量的类型是具体类还是接口,都是访问父类的静态变量。

public void test6() {
    Animal animal = new Ape();
    System.out.println(animal.skinColor);
  }

说明:
接口 Animal 声明定义了静态变量 skinColor,其实是一个常量;类 Ape 中声明了静态变量 skinColor。但是编译的时候会查询变量 animal 的声明类型 Animal 中是否声明了变量 skinColor,如果没有声明则会报编译错误,如果有声明则会将变量名 animal 改成类名 Animal,所以说实际访问的是变量 animal 的声明类型中的静态变量 skinColor。这里编译时并不会判断变量 animal 的声明类型是具体类型还是接口。