是父类或接口定义的引用变量能够指向子类或实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实现对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。程序员
浅谈多态机制的意义及实现面试
在面向对象编程(Object-Oriented Programming, OOP)中,多态机制无疑是其最具特点的功能,甚至能够说,不运用多态的编程不能称之为OOP。这也是为何有人说,使用面向对象语言的编程和面向对象的编程是两码事。算法
多态并无一个严格的定义,维基百科上给它下的定义比较宽松:编程
Subtype polymorphism, almost universally called just polymorphism in the context of object-oriented programming, is the ability of one type, A, to appear as and be used like another type, B.设计模式
1、子类型和子类架构
这里我想先提一会儿类型(Subtype)这个词和子类(Subclass)的区别,简单地说,只要是A类运用了extends关键字实现了对B类的继承,那么咱们就能够说Class A是Class B的子类,子类是一个语法层面上的词,只要知足继承的语法,就存在子类关系。app
子类型比子类有更严格的要求,它不只要求有继承的语法,同时要求若是存在子类对父类方法的改写(override),那么改写的内容必须符合父类本来的语义,其被调用后的做用应该和父类实现的效果方向一致。ide
对两者的对比是想强调一点:只有保证子类都是子类型,多态才有意义。函数
**2、多态的机制
**
本质上多态分两种:性能
编译时多态(又称静态多态)
运行时多态(又称动态多态)
重载(overload)就是编译时多态的一个例子,编译时多态在编译时就已经肯定,运行时运行的时候调用的是肯定的方法。
咱们一般所说的多态指的都是运行时多态,也就是编译时不肯定究竟调用哪一个具体方法,一直延迟到运行时才能肯定。这也是为何有时候多态方法又被称为延迟方法的缘由。
在维基百科中多态的行为被描述为:
The primary usage of polymorphism in industry (object-oriented programming theory) is the ability of objects belonging to different types to respond to method, field, or property calls of the same name, each one according to an appropriate type-specific behavior.
下面简要介绍一下运行时多态(如下简称多态)的机制。
多态一般有两种实现方法:
1.子类继承父类(extends)
2.类实现接口(implements)
不管是哪一种方法,其核心之处就在于对父类方法的改写或对接口方法的实现,以取得在运行时不一样的执行效果。
要使用多态,在声明对象时就应该遵循一条法则:声明的老是父类类型或接口类型,建立的是实际类型。举例来讲,假设咱们要建立一个ArrayList对象,声明就应该采用这样的语句:
List list =newArrayList();
而不是
ArrayList list =newArrayList();
在定义方法参数时也一般老是应该优先使用父类类型或接口类型,例如某方法应该写成:
publicvoid doSomething(List list);
而不是
publicvoid doSomething(ArrayList list);
这样声明最大的好处在于结构的灵活性:假如某一天我认为ArrayList的特性没法知足个人要求,我但愿可以用LinkedList来代替它,那么只须要在对象建立的地方把new ArrayList()改成new LinkedList便可,其它代码一律不用改动。
The programmer (and the program) does not have to know the exact type of the object in advance, and so the exact behavior is determined at run-time (this is called late binding or dynamic binding).
虚拟机会在执行程序时动态调用实际类的方法,它会经过一种名为动态绑定(又称延迟绑定)的机制自动实现,这个过程对程序员来讲是透明的。
3、多态的用途
多态最大的用途我认为在于对设计和架构的复用,更进一步来讲,《设计模式》中提倡的针对接口编程而不是针对实现编程就是充分利用多态的典型例子。定义功能和组件时定义接口,实现能够留到以后的流程中。同时一个接口能够有多个实现,甚至于彻底能够在一个设计中同时使用一个接口的多种实现(例如针对ArrayList和LinkedList不一样的特性决定究竟采用哪一种实现)。
4、多态的实现
下面从虚拟机运行时的角度来简要介绍多态的实现原理,这里以Java虚拟机(Java Virtual Machine, JVM)规范的实现为例。
在JVM执行Java字节码时,类型信息被存放在方法区中,一般为了优化对象调用方法的速度,方法区的类型信息中增长一个指针,该指针指向一张记录该类方法入口的表(称为方法表),表中的每一项都是指向相应方法的指针。
方法表的构造以下:
因为Java的单继承机制,一个类只能继承一个父类,而全部的类又都继承自Object类。方法表中最早存放的是Object类的方法,接下来是该类的父类的方法,最后是该类自己的方法。这里关键的地方在于,若是子类改写了父类的方法,那么子类和父类的那些同名方法共享一个方法表项,都被认做是父类的方法。
注意这里只有非私有的实例方法才会出现,而且静态方法也不会出如今这里,缘由很容易理解:静态方法跟对象无关,能够将方法地址直接引用,而不像实例方法须要间接引用。
更深刻地讲,静态方法是由虚拟机指令invokestatic调用的,私有方法和构造函数则是由invokespecial指令调用,只有被invokevirtual和invokeinterface指令调用的方法才会在方法表中出现。
因为以上方法的排列特性(Object——父类——子类),使得方法表的偏移量老是固定的。例如,对于任何类来讲,其方法表中equals方法的偏移量老是一个定值,全部继承某父类的子类的方法表中,其父类所定义的方法的偏移量也老是一个定值。
前面说过,方法表中的表项都是指向该类对应方法的指针,这里就开始了多态的实现:
假设Class A是Class B的子类,而且A改写了B的方法method(),那么在B的方法表中,method方法的指针指向的就是B的method方法入口。
而对于A来讲,它的方法表中的method方法则会指向其自身的method方法而非其父类的(这在类加载器载入该类时已经保证,同时JVM会保证老是能从对象引用指向正确的类型信息)。
结合方法指针偏移量是固定的以及指针老是指向实际类的方法域,咱们不难发现多态的机制就在这里:
在调用方法时,实际上必须首先完成实例方法的符号引用解析,结果是该符号引用被解析为方法表的偏移量。虚拟机经过对象引用获得方法区中类型信息的入口,查询类的方法表,当将子类对象声明为父类类型时,形式上调用的是父类方法,此时虚拟机会从实际类的方法表(虽然声明的是父类,可是实际上这里的类型信息中存放的是子类的信息)中查找该方法名对应的指针(这里用“查找”其实是不合适的,前面提到过,方法的偏移量是固定的,因此只需根据偏移量就能得到指针),进而就能指向实际类的方法了。
咱们的故事尚未结束,事实上上面的过程仅仅是利用继承实现多态的内部机制,多态的另一种实现方式:实现接口相比而言就更加复杂,缘由在于,Java的单继承保证了类的线性关系,而接口能够同时实现多个,这样光凭偏移量就很难准确得到方法的指针。因此在JVM中,多态的实例方法调用实际上有两种指令:
当使用invokeinterface指令调用方法时,就不能采用固定偏移量的办法,只能老老实实挨个找了(固然实际实现并不必定如此,JVM规范并无规定究竟如何实现这种查找,不一样的JVM实现能够有不一样的优化算法来提升搜索效率)。咱们不难看出,在性能上,调用接口引用的方法一般老是比调用类的引用的方法要慢。这也告诉咱们,在类和接口之间优先选择接口做为设计并不老是正确的,固然设计问题不在本文探讨的范围以内,但显然具体问题具体分析仍然不失为更好的选择。
我的看法:多态机制包括静态多态(编译时多态)和动态多态(运行时多态),静态多态好比说重载,动态多态是在编译时不能肯定调用哪一个方法,得在运行时肯定。动态多态的实现方法包括子类继承父类和类实现接口。当多个子类上转型(不知道这么说对不)时,对象掉用的是相应子类的方法,这种实现是与JVM有关的。
今天就分享这么多,欢迎各位朋友在留言区评论,对于有价值的留言,我都会一一回复的。若是以为文章对你有一丢丢帮助,请给我点个赞吧,让更多人看到该文章。另外,小编最近将收集的Java程序员进阶架构师和面试的资料作了一些整理,免费分享给每一位学习Java的朋友,须要的能够进群:751827870,欢迎你们进群和我一块儿交流。