深入理解Java多态机制
从字节码层面来看,Java中的所有方法调用,最终无外乎转换为如下几条调用指令。
- invokestatic: 调用静态方法。
- invokespecial: 调用实例构造器<init>方法,私有方法和父类方法。
- invokevirtual: 调用所有的虚方法。
- invokeinterface: 调用接口方法,会在运行时再确定一个实现此接口的对象。
- invokedynamic: 调用动态方法。JDK 7引入的,主要是为了支持动态语言的方法调用。
JVM提供了上述5条方法调用指令,所以不妨从字节码层面来一窥Java多态机制的执行过程。
1 虚方法和非虚方法
上述5条方法调用指令中的invokevirtual负责调用所有的虚方法。那么什么是虚方法?什么是非虚方法呢?
从Java语言层面来看,static,private,final修饰的方法,父类方法以及实例构造器,这些方法称为非虚方法。与之相反,其他所有的方法称为虚方法。
字节码指令层面来讲,invokestatic和invokespecial调用的方法都是非虚方法。
2 静态类型和实际类型
先看一看以下代码的定义:
Human man = new Man();
我们把 Human称为变量的静态类型,Man称为变量的实际类型。
引用变量都有两个类型:静态类型(定义该变量的类型)和实际类型(实际指向的对象的类型)。
静态类型是编译时可知的,而实际类型只有运行时才能确定。
3 Java中多态机制的实现过程
invokevirtual指令的运行时解析过程大致分为如下几个步骤:
- 找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C
- 如果在类型C中找到常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。
- 否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
- 如果始终没找到合适的方法,则抛出java.lang.AbstractMethodError异常。
正是由于invokevirtual指令是这样的一个执行过程,所以这就解释了为什么java语言里面实现多态需要如下三个条件:a. 父类引用指向子类对象。b. 有继承的存在。c. 子类重写父类方法。
- 由于父类引用指向子类对象,所以jvm会去首先去查找该子类对象对应的类型。
- 又由于有继承的存在,所以子类的方法不可能比父类少,这就保证了,只要该引用变量能调用的方法,子类中一定存在。所以第二步一定能在子类的类型中查找到调用的方法。
- 方法找到后就可以执行了,至于方法执行后能不能产生不同的效果(多态),得看子类是否重写了这个方法。所以要想产生多态,子类得重写父类方法。【注意:以上所说的方法均是指的虚方法。】
4 静态分派和动态分派
分派过程会揭示多态的一些最基本的体现,比如”重写“和“重载”在Java虚拟机中是如何实现的。
4.1 静态分派
所有依赖(实参的)静态类型来定位方法执行版本的分派动作称为静态分派。典型的应用就是方法重载(Overload)。
先看一个例子:
public class StaticDispatch {
static class Human {
}
static class Man extends Human {
}
static class Women extends Human {
}
public void sayHello(Human guy) {
System.out.println("hello, guy!");
}
public void sayHello(Man guy) {
System.out.println("hello, man!");
}
public void sayHello(Women guy) {
System.out.println("hello, women!");
}
public static void main(String[] args) {
Human man = new Man();
Human women = new Women();
StaticDispatch sd = new StaticDispatch();
sd.sayHello(man);
sd.sayHello(women);
}
}
输出结果:
hello, guy!
hello, guy!
没错,程序就是大家熟悉的重载(Overload)。在上述程序中,由于方法的接受者已经确定是StaticDispatch的实例sd了,所以最终调用的是哪个重载版本也就取决于传入参数的类型了。
Java中重载的本质
编译器在重载时是根据传入实参的静态类型而不是实际类型作为判定依据的。静态类型是编译时可知的,所以在编译阶段,编译器会根据实参的静态类型决定调用那个重载版本。
4.2 动态分派
在运行期根据变量的实际类型来确定方法执行版本的分派过错称为动态分派。典型的应用就是重写(override)。例子如下:
public class DynamicDispatch {
public static void main(String[] args) {
Human man = new Man();
Human women = new Women();
man.sayHello();
women.sayHello();
man = new Women();
man.sayHello();
}
}
abstract class Human {
protected abstract void sayHello();
}
class Man extends Human {
@Override
protected void sayHello() {
System.out.println("hello man!");
}
}
class Women extends Human {
@Override
protected void sayHello() {
System.out.println("hello women!");
}
}
输出结果:
hello man!
hello women!
hello women!
Java中重写的本质
见invokevirtual指令的运行时的解析过程。
4.3 综合例子
public class Dispatch {
static class QQ{}
static class _360{}
public static class Father{
public void hardChoice(_360 _360) {
System.out.println("Father choose 360");
}
public void hardChoice(QQ qq) {
System.out.println("Father choose qq");
}
}
public static class Son extends Father{
public void hardChoice(_360 _360) {
System.out.println("Son choose 360");
}
public void hardChoice(QQ qq) {
System.out.println("Son choose qq");
}
}
public static void main(String[] args) {
Father father = new Father();
Father son = new Son();
father.hardChoice(new _360());
son.hardChoice(new QQ());
}
}
输出结果:
Father choose 360
Son choose qq
分析如下:
Father father = new Father();
Father son = new Son(); /**
* Father里面有两个重载的hardChoice方法。所以会根据hardChoice()的实参的【静态类型】来决定调用哪个版本的方法。
*/
father.hardChoice(new _360()); /**
* 变量son的静态类型是Father,实际类型是Son。并且类Son重写了父类Father里面的两个重载的hardChoice方法。
* 所以运行的时候首先会确定调用子类Son里面的方法,然后在根据hardChoice()的实参的【静态类型】来决定调用Son里面的哪个版本的方法。
*/
son.hardChoice(new QQ());
5 虚拟机动态分派的实现
- 由于动态分派是非常频繁的操作,基于性能考虑,在JVM的具体实现中常常 做一些优化。最常用的“稳定优化”手段就是为类在方法区中建立一个虚方法表(Virtual Method Table,也称vtable)。与此对应的,invokeinterface执行时也会用到接口方法表(Interface Method Table,简称itable)。
- 虚方法表中存放着各个方法的实际入口地址。如果某个方法在子类中没有被重写,那么子类的虚方法表里的地址入口和父类相同方法的地址入口是一致的。如果子类重写了这个方法,子类方法表中的地址就会被替换为指向子类实现版本的入口地址。
参考资料
http://blog.csdn.net/kobejayandy/article/details/39620679
http://www.cnblogs.com/jack204/archive/2012/10/29/2745150.html
http://blog.csdn.net/oypc2303/article/details/4393831
http://blog.csdn.net/lidaweihgy/article/details/7660346
http://www.cnblogs.com/qinqinmeiren/archive/2011/07/15/2151687.html
http://www.geekcome.com/content-10-4128-1.html
深入理解Java多态机制的更多相关文章
- 深入理解java多态没有烤山药的存在,java就不香了吗?
目录 1. 从吃烤山药重新认识多态 2. 多态前提条件[重点] 3. 多态的体现 4. 多态动态绑定与静态绑定 5. 多态特性的虚方法(virtual) 7. 向上转型 8. 向下转型 9. 向上向下 ...
- 从零开始理解JAVA事件处理机制(2)
第一节中的示例过于简单<从零开始理解JAVA事件处理机制(1)>,简单到让大家觉得这样的代码简直毫无用处.但是没办法,我们要继续写这毫无用处的代码,然后引出下一阶段真正有益的代码. 一:事 ...
- 从零开始理解JAVA事件处理机制(3)
我们连续写了两小节的教师-学生的例子,必然觉得无聊死了,这样的例子我们就是玩上100遍,还是不知道该怎么写真实的代码.那从本节开始,我们开始往真实代码上面去靠拢. 事件最容易理解的例子是鼠标事件:我们 ...
- 理解Java类加载机制(译文)
理解java类加载机制 你想写类加载器?或者你遇到了ClassCastException异常,或者你遇到了奇怪的LinkageError状态约束异常.应该仔细看看java类的加载处理了. 什么是类加载 ...
- Java基础 -- 深入理解Java异常机制
异常指不期而至的各种状况,如:文件找不到.网络连接失败.非法参数等.异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程.Java通 过API中Throwable类的众多子类描述各种不同的异常. ...
- 【转】深入理解java异常处理机制
深入理解java异常处理机制 ; int c; for (int i = 2; i >= -2; i--) { c = b / i; System.out.println("i=&qu ...
- 转:一个经典例子让你彻彻底底理解java回调机制
一个经典例子让你彻彻底底理解java回调机制 转帖请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/17483273 ...
- 深入理解Java流机制(一)
一.前言 C语言本身没有输入输出语句,而是调用"stdio.h"库中的输入输出函数来实现.同样,C++语言本身也没有输入输出,不过有别于C语言,C++有一个面向对象的I/O流类库& ...
- 理解Java反射机制
理解Java反射机制 转载请注明出处,谢谢! 一.Java反射简介 什么是反射? Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在.灵活掌握Java反射机制,对学习框架技术有很大 ...
随机推荐
- Nginx 启用 https
在nginx.conf中增加新server配置 server { listen ; server_name www.some.com; ssl on; ssl_certificate sslkey/s ...
- 2016 08 27 印刷菜单增加sql语句
insert into `module` (`ID`, `CONSONANTCODE`, `CREATEDATE`, `DESCRIPTION`, `HANDLER`, `HASCHILD`, `IC ...
- css默认值汇总
原文:http://www.cnblogs.com/xiangzi888/archive/2011/10/12/2209514.html HTML标签CSS属性默认值汇总 这个东西,在你需要还原默认值 ...
- iOS开发 传感器(加速计、摇一摇、计步器)
一.传感器 1.什么是传感器传感器是一种感应\检测周围环境的一种装置, 目前已经广泛应用于智能手机上 传感器的作用用于感应\检测设备周边的信息不同类型的传感器, 检测的信息也不一样 iPhone中的下 ...
- Reverse Words in a String
void reverseWords(string &s) { string res = "", tmp = ""; int l = s.length() ...
- 2015-2016-2 《Java程序设计》教学进程
2015-2016-2 <Java程序设计>教学进程 目录 考核方式 寒假准备 教学进程 第00周学习任务和要求 第01周学习任务和要求 第02周学习任务和要求 第03周学习任务和要求 第 ...
- FineUI(专业版)高清大图赏析!(第二波)
FineUI(专业版)是由三生石上全新打造的基于 jQuery 的专业 ASP.NET 控件库,计划在七月下旬正式发布. 选择FineUI(专业版)的四大理由:1. 简单:专业版和开源版兼容(v4.x ...
- BatsingJSLib 2.3、Ajax上传多个文件
//2.3Ajax上传单个或多个文件 //<input type="file" multiple="multiple"/> //参数:文件的表单JD ...
- idea配置。
1.project Structure — 修改project(1.name,sdk,level(6-@Override in interface)) 修改modules(点击web,加上source ...
- 30行代码实现Javascript中的MVC
从09年左右开始,MVC逐渐在前端领域大放异彩,并终于在刚刚过去的2015年随着React Native的推出而迎来大爆发:AngularJS.EmberJS.Backbone.ReactJS.Rio ...