什么是多态,JAVA 中多态的实现机制
首先声明啊,这里的多态不是生物学和物理学上的多态性,这个是指编程语言中的多态。官方说明:
- 多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口.
- 多态是允许你将父对象设置成为一个或更多的他的子对象相等的技术。
- 我们在程序中定义的引用变量所指向的具体类型和通过该引用变量的方法调用在编程的时候并不确定,当处于运行期间才确定。就是这个引用变量究竟指向哪一个实例对象,在编译期间是不确定的,只有运行期才能确定,这样不用修改源码就可以把变量绑定到不同的类实例上,让程序拥有了多个运行状态,这就是多态。
看完是不是很懵逼?
说人话就是:
允许将子类类型的指针赋值给父类类型的指针,把不同的子类对象都当作父类来看。就比如你在一个京剧世家,有人邀请你们家出个人来唱一出戏,邀请函上写的是你父亲的名字,但很不凑巧你父亲病了,你只能穿上你父亲的戏服,装作你父亲去登台唱戏。台下的人只知道是你们家的人登台,可能你父亲,也可能是你家人代替你父亲。你为了不被发现是代替你父亲,你就要隐藏起来其他技能。这就是多态。
多态又分为 编译时多态和运行时多态:
- 编译时多态(静态):重载
- 运行时多态(动态):重写
演示代码如下:
public class Father { //父类
void sing (){
System.out.println("我是爸爸,我会唱京剧" + "\n");
}
}
class Son extends Father{ //子类,继承父类
void sing (){
System.out.println("我是儿子,我会唱京剧" + "\n");
}
void kungFu (){
System.out.println("我是儿子,我会武术" + "\n");
}
}
class test{ //测试类
public static void main(String[] args) {
Father father = new Son(); //多态的表现
father.sing();
}
}
在使用 “father.” 调用方法时能发现,弹出的方法里根本没有Son类的kungFu方法:
运行结果如下:
儿子:嘻嘻,没被发现,装父亲成功了!
扩展
1.多态的实现机制
术语版本:
我们将引入Java静态分派和动态分派这个概念。
- 静态分派:所有依赖静态类型来定位方法执行版本的分派动作。静态分派发生在编译阶段,因此确定静态分派的动作实际上不是由虚拟机来执行的,而是由编译器来完成。(编译时多态)
- 动态分派:在运行期根据实际类型确定方法执行版本的分派动作。(运行时多态)
简单版本:
原理也很简单,父类或者接口定义的引用变量可以指向子类或者具体实现类的实例对象,由于程序调用方法是在运行期才动态绑定的,那么引用变量所指向的具体实例对象在运行期才确定。所以这个对象的方法是运行期正在内存运行的这个对象的方法而不是引用变量的类型中定义的方法。
如果想深入的同学可以参考可以参考《深入理解Java虚拟机》。
2.里氏代换原则
里氏代换原则(LSP): 子类对象能够替换父类对象,而程序逻辑不变。
里氏替换原则有至少以下两种含义:
- 里氏替换原则是针对继承而言的,如果继承是为了实现代码重用,也就是为了共享方法,那么共享的父类方法就应该保持不变,不能被子类重新定义。子类只能通过新添加方法来扩展功能,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。
- 如果继承的目的是为了多态,而多态的前提就是子类覆盖并重新定义父类的方法,为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。不符合LSP的最常见的情况是,父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义,这一类的实现继承会造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。
如何符合LSP?总结一句话 —— 就是尽量不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承。
如有问题欢迎指正
参考:
https://blog.csdn.net/sunxianghuang/article/details/52280002
https://www.zhihu.com/question/27191817/answer/145013324