标签:java 0基础Java自学之路 基础教程 教程 参考官方教程 多态的详细介绍
前言
初学 Java 的时候,博主觉得多态非常伤脑筋。今天这篇文章将通过官方教程、韩顺平老师、李明杰老师的讲解来学习多态。博主通过他们的视频来学习,但也有自己的想法,并不是照抄 PPT 哦!OK!Let’s study!
Java 有三大特性:封装、继承和多态(Polymorphism)
一、多态官方教程
The dictionary definition of polymorphism refers to a principle in biology(生物学) in which an organism(有机体) or species(物种) can have many different forms or stages.
多态的字典定义是:在生物学上,一个有机体或物种可以有许多不同的形式或阶段(形式或状态)。
This principle can also be applied to object-oriented programming(面向对象编程) and languages like the Java language.
这个原则(生物学的多态性)也可以应用于面向对象编程和编程语言(比如 Java)
// hello, man hello, woman public class StaticDispacth { static abstract class Human {} static class Man extends Human {} static class Woman extends Human{} public static void sayHi() { System.out.println("sayHi"); } public void sayHello(Human guy) { System.out.println("hello, guy"); } public void sayHello(Man guy) { System.out.println("hello, man"); } public void sayHello(Woman guy) { System.out.println("hello, woman"); } public static void main(String[] args) { sayHi(); Human man = new Man(); Human woman = new Woman(); StaticDispacth sr = new StaticDispacth(); sr.sayHello(man); sr.sayHello(woman); } }
实际上只会输出两个 hello, guy,而问题就是为什么传入Man和Woman的实例却调用了Human的方法呢?
从字节码指令探究
JVM中定义了5条方法调用字节码指令,分别可以归类到下面两种
- 非虚方法:可以简单的理解成不可重写的方法都属于这类,指令有 invokestatic:调用静态方法 invokespecial:调用实例构造器方法、私有方法、父类方法 虚方法:可以理解成能够重写的方法 invokevirtual:调用所有的虚方法,如实例方法(但是final方法不可被重写也分到了这一类) invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象 invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法
invokespecial的执行
- 对于方法,我们用“方法签名来描述”,比如 add(int a, int b)的签名是 add(int, int),而在重写引入的情况下,我的理解是应该由 调用实例(接受者).方法签名来确认应该使用哪个方法版本,因此重点就在于确定接受者和方法签名。 invokespecial执行的过程为 找到操作数栈顶的第一个元素所指向的对象的实际类型C 如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找结束;不通过则报异常 否则,按照继承关系从下往上一次对C的各个无泪进行第二部的搜索和权限验证 如果始终没有找到合适的方法,则跑出AbstractMethodError异常
静态分派与重载
- 重载发生在一个类中,因此接受者是确定的,当遇到重载的多个方法中参数数量相同而参数类型有继承关系时,需要进行静态分派来找到合适的方法版本。 对于“父类引用指向子类实例”,如上面的 Human man = new Man(); 这种情况,我们称Human为静态类型,而Man称为实际类型,重载时是通过参数的静态类型而不是实际类型作为判定依据的。 如上代码可见,由于man和woman的静态类型都是Human,因此调用的是参数为Human的方法。
动态分派与重写
- 重写发生在父子类之间,参数列表是一摸一样的,关键就在于确定接收者(PS:如果出现同时重载重写的情况,会先在编译期就确定了方法签名)。 在执行invokevirtual之前,会从局部变量表中a_load出接受者压到操作数栈顶(关于操作数栈等知识请看),再调用相应的方法。
// 动态分配是多态——重写的体现 public class DynamicDispatch { static abstract class Human { protected abstract void sayHello(); } static class Man extends Human { @Override protected void sayHello() { System.out.println("man say Hello"); } } static class Woman extends Human { @Override protected void sayHello() { System.out.println("woman say Hello"); } } // PS:子类新增加的方法父类引用是看不见的,想要调用子类新增的方法需要向下转型 public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); man.sayHello(); woman.sayHello(); man = new Woman(); man.sayHello(); } }
- 执行结果 查看字节码
单分派与多分派
- 方法的接受者与方法的参数统称为方法的宗量,单分派是只方法版本由一个宗量决定,多分派相反
虚拟机动态分派的实现
- 动态分派查找合适的方法版本是十分频繁的操作,而为了优化搜索,JVM采用类在方法区中建立虚方法表(vTable)的方式,使用虚方法表索引来代替元数据查找——就是索引的思想 虚方法表中存放着各个方法的实际入口地址,需要遵循以下规则: 父、子类的虚方法表中都应当有一样的索引序号(子类对父类方法的访问权限会在调用时会检查) 如果某个方法在子类中没有被重写,子类的虚方法表中相应的方法入口地址和父类的相同 如果子类中重写了父类的方法,则子类的虚方法表对应位置指向重写后的方法
标签:java,0基础Java自学之路,基础教程,教程,参考官方教程,多态的详细介绍 来源:
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。