理解 Java 多态性:编译时和运行时的区别

相同的行为, 不同的实现

多态多态, 多种状态, 而多种状态的体现就在不同的实现上面, 也可以说是不同的效果.
举个例子:
我这里有 3 个杯子, 里面都装了白酒, 你并不知道它们都是什么酒, 只有等你喝过之后才能知道, 第一个杯子是二锅头, 第二个是五粮液, 第三个是茅台.

1
2
Maotai mo = new Maotai();
Wine w = mo;

这里我们可以这样理解, 茅台是酒类的一种, 它们之间是继承关系, 当 a 指向了 mo 这个对象的时候, 它会自动向上转型为 Wine, 这也就是说 w 是可以指向茅台这个实例.

这样做的好处就是, 在继承中我们知道子类是父类的扩展, 它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能

但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们

所以对于多态我们可以总结如下:

  • 指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用这些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。
    不过还是可以调用子类中特有的方法, 后文中将会讲到.

  • 对于面向对象而已,多态分为编译时多态和运行时多态。

    • 编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。
    • 运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

多态的分类

  1. 静态多态 (编译期确定要执行的效果, 编译器只是做语法检查)
    • 重载 (Overload) 实现
  2. 动态多态 (编译期不知道运行效果, 而是运行期根据绑定对象的不同确定结果)
    • 重写 (Override)
    • 动态绑定

当一个父类引用指向子类对象的时候
为什么父类不能调用子类重载之后的方法? 为什么父类调用的方法的实现是子类重写后的实现?

  • 因为重载是对于一个类而言的, 当子类继承父类, 然后在本类中重载了父类的这个方法, 对于父类而言是看不到这个在子类中重载之后的方法的, 因为它只属于子类, 所以当父类调用这个方法的时候, 因为父类中根本就没有这个方法, 所以编译器会报错;
  • 然而当父类调用被子类重写的过后的方法时, 因为父类中存在同名方法, 编译器不会报错, 然后通过动态绑定, 将被调用的这个方法和子类关联起来, 所以方法实现的是子类重写过后的效果.
动态绑定
  • 将一个方法调用与该方法所在的类关联起来;
  • 在运行时候根据具体对象的类型绑定;
  • 当父类引用指向子类的对象, 调用重写的方法时, 是调用子类中重写的方法;
转型技术 (类之间必须要有继承关系)

左右两边数据类型不一致

  • 向上转型 –> 自动
    Person p = new Man();
    此时的引用 p 只能看见父类的方法, 而看不到子类所特有的方法
  • 向下转型 –> 强制
    Man m = (Man)p
    此时 m 引用可以看到全部的方法, 因为 m 指向了一个 Man 对象.
    这样就可以调用子类对象中访问修饰符允许的方法和属性了

多态的引用–异构 集合

创建一个不是同一类型, 但是有共同父类的数据集合, 不同对象的集合称为异构集合

多态的好处和弊端:

A: 好处
提高了程序的可维护性和可扩展性。
维护性:继承保证
扩展性:多态保证
B: 弊端
父类 / 父接口不能访问子类特有功能。

多态的体现形式:

  • 具体类多态
1
2
3
class Fu {}
class Zi extends Fu {}
Fu f = new Zi();
  • 抽象类多态
1
2
3
abstract class Fu {}
class Zi extends Fu {}
Fu f = new Zi();
  • 接口多态
1
2
3
4
interface Inter {}
//接口的实现类命名:接口名+Impl
class InterImpl implements Inter{}
Inter i = new InterImpl();

总结

  • 使用父类类型的引用指向子类的对象;
  • 该引用只能调用父类中定义的方法, 不能调用子类的特有的属性和方法;
  • 如果子类中重写父类的方法, 那么调用该方法, 将会调用子类中重写后的实现;
  • 在多态中, 子类可以调用父类的所有非私有方法;
  • 父类的引用可以指向子类的对象, 子类的引用不能指向父类的对象;