Java 中多态的另一个语法实现是重写。重载是通过静态分派实现的,重写则是通过动态分派实现的。

在学习动态分派之前,需要对虚拟机的知识有一个初步的了解。

虚拟机运行时数据区

运行 Java 程序时,虚拟机先加载编译后的 .class 文件,然后根据文件内容来构建运行时数据区。关于 .class 文件的内容可以参考本书的第六章。

这里需要关心的是线程私有的虚拟机栈。每个线程都有自己的虚拟机栈,两者的生命周期相同。栈中的每个数据存储单元称为栈帧,栈帧里面存储的内容主要有:局部变量表、操作数栈、动态链接、方法出口。每个栈帧在虚拟机栈里面从入栈到出栈的过程,就对应每个方法从调用开始至执行完成的过程。栈顶表示当前栈帧,也就是正在执行的方法。

局部变量表保存的是在方法中定义的局部变量和方法参数。

操作数栈简称为操作栈,当方法开始执行时,操作栈是空的,然后将各种字节码指令写入或者取出操作栈,也就是出栈入栈操作,操作栈对应的是方法的具体执行。

那么关于多态重写的部分,我们只关心方法的调用,即虚拟机是如何根据继承链上不同的对象来找到相应的方法呢?下面通过书中的示例进一步了解方法的调用。

动态分派

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");
}
} public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}

这段代码的的输出就不贴了,但是要了解方法的调用,可以简要的查看一下这段代码的字节码,了解关键部分的字节码指令即可。下面是使用javap -verbose DynamicDispatch命令输出的字节码中 main()方法的提取:

 public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
Code:
stack=2, locals=3, args_size=1
// 创建一个对象,并将其引用值压入栈顶,注意指的是操作栈
// 这里是一个 Man 的实例
0: new #2 // class jvmlearn/DynamicDispatch$Man
// 复制栈顶数值并将复制值压入栈顶,上一步创建的对象的引用
// 注意:因为下面的 invokespecial 指令会取出栈顶的创建的对象引用
3: dup
// 调用超类构造方法,实例初始化方法,私有方法
// 也就是 Man 的父类 Human,这条暂时不用关心
4: invokespecial #3 // Method jvmlearn/DynamicDispatch$Man."<init>":()V
// 将栈顶引用型数值存入第二个本地变量,可以往下翻,找到 LocalVariableTable
// 第二行就是变量 man
7: astore_1
// 这里创建的是 Woman 的实例
8: new #4 // class jvmlearn/DynamicDispatch$Woman
// 同样将引用值压入栈顶
11: dup
// 同样调用父类的初始化方法
12: invokespecial #5 // Method jvmlearn/DynamicDispatch$Woman."<init>":()V
// 将引用值存到局部变量表的第三个变量,也就是 woman
15: astore_2
// 将第二个引用类型本地变量推送至栈顶
// 也就是 man
16: aload_1
// 调用实例方法
// 这一步很关键,可以看到实际调用的是 man 的方法
// 指令后面的 #6 表示常量池的第 6 项常量作为参数,后面的注释可以看到是 Human.sayHello() 方法的符号引用
17: invokevirtual #6 // Method jvmlearn/DynamicDispatch$Human.sayHello:()V
// 将第三个引用类型本地变量推送至栈顶
// 也就是 woman
20: aload_2
// 调用实例方法,也就是 woman 变量
// 可以看到,两次调用方法的指令是一样的
// 不同的地方只是实例不同
21: invokevirtual #6 // Method jvmlearn/DynamicDispatch$Human.sayHello:()V
// 这一步又创建了一个 Woman 实例,将引用值压入栈顶
24: new #4 // class jvmlearn/DynamicDispatch$Woman
// 复制引用并压入栈顶
27: dup
// 调用父类的初始化方法
28: invokespecial #5 // Method jvmlearn/DynamicDispatch$Woman."<init>":()V
// 将引用值存入第二个本地变量
31: astore_1
// 将引用类型的第二个本地推送至栈顶
32: aload_1
// 调用实例方法,可以看到和前面字节码行号为 17 的指令行,从指令到参数都一样
// 而最终执行的目标方法却不一样
33: invokevirtual #6 // Method jvmlearn/DynamicDispatch$Human.sayHello:()V
// 从当前方法返回 void
36: return
LineNumberTable:
// 行号表,前面是源码行号,后面是字节码行号,可以看到上面的字节码指令的前面都有数字
line 33: 0
// eg. 源码的第 33 行,对应字节码的第 0 行
line 34: 8
line 35: 16
line 36: 20
line 37: 24
line 38: 32
line 39: 36
LocalVariableTable:
// 局部变量表
Start Length Slot Name Signature
0 37 0 args [Ljava/lang/String;
8 29 1 man Ljvmlearn/DynamicDispatch$Human;
16 21 2 woman Ljvmlearn/DynamicDispatch$Human;

调用实例方法的指令是invokevirtual,重写就是通过该指令实现的,该指令的多态查找过程如下:

  1. 找出操作栈顶元素所指向的对象的实际类型,记作 C;
  2. 如果在类型 C 中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常;
  3. 如果上一步没找到,则按照继承关系从下往上依次对 C 的各个父类进行第 2 步的操作;
  4. 如果最终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常

由于invokevirtual指令执行的第一步就是在运行器确定接收者的实际类型,所以两次调用中的invokevirtual指令把常量池中的类方法符号引用解析到了不同的直接引用上,这就是 Java 中重写的本质。

参考

Java 中多态的实现(下)的更多相关文章

  1. 关于java中多态的理解

    java三大特性:封装,继承,多态. 多态是java的非常重要的一个特性: 那么问题来了:什么是多态呢? 定义:指允许不同类的对象对同一消息做出响应.即同一消息可以根据发送对象的不同而采用多种不同的行 ...

  2. 深入Java核心 Java中多态的实现机制(1)

    在疯狂java中,多态是这样解释的: 多态:相同类型的变量,调用同一个方法时,呈现出多中不同的行为特征, 这就是多态. 加上下面的解释:(多态四小类:强制的,重载的,参数的和包含的) 同时, 还用人这 ...

  3. 从虚拟机指令执行的角度分析JAVA中多态的实现原理

    从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧 ...

  4. 个人对Java中多态的一些简单理解

    什么是多态 面向对象的三大特性:封装.继承.多态.从一定角度来看,封装和继承几乎都是为多态而准备的.这是我们最后一个概念,也是最重要的知识点. 多态的定义:指允许不同类的对象对同一消息做出响应.即同一 ...

  5. Java中多态的一些简单理解

    什么是多态 .面向对象的三大特性:封装.继承.多态.从一定角度来看,封装和继承几乎都是为多态而准备的.这是我们最后一个概念,也是最重要的知识点. .多态的定义:指允许不同类的对象对同一消息做出响应.即 ...

  6. Java 中多态的实现(上)

    Java 中语法上实现多态的方式分为两种:1. 重载.2. 重写,重载又称之为编译时的多态,重写则是运行时的多态. 那么底层究竟时如何实现多态的呢,通过阅读『深入理解 Java 虚拟机』这本书(后文所 ...

  7. java中多态的实现机制

    多态的概念: 简单来说就是事物在运行过程中存在的不同状态,即父类或接口定义的引用变量指向子类或具体实现类的实例对象.程序调用方法在运行期才进行动态绑定,而不是引用变量的类型中定义的方法. 多态存在的前 ...

  8. Java中多态、抽象类和接口

    1:final关键字(掌握) (1)是最终的意思,可以修饰类,方法,变量. (2)特点: A:它修饰的类,不能被继承. B:它修饰的方法,不能被重写. C:它修饰的变量,是一个常量. (3)面试相关: ...

  9. 对Java中多态,封装,继承的认识(重要)

                                                            一.Java面向对象编程有三大特性:封装,继承,多态 在了解多态之前我觉得应该先了解一下 ...

随机推荐

  1. Kali Linux初始化

    配置SSH 1.将PubKeyAuthtication设置为 yes,同时将注释去除. 2.将PermitRootLogin改为PermitRootLogin yes,同时将注释去除. 3.启动/et ...

  2. Windows安装MySQL免安装版

    安装环境: win7 64位 IP地址:192.168.2.37 防火墙已经关闭 一.安装MySQL5.7.14免安装版 1.解压文件 2.新建my.ini文件 data文件夹自动生成 my.ini里 ...

  3. Blazui 常见问题:我更新了数据,为什么界面没刷新?

    首发于:http://www.blazor.group:8000/topic/reply?tpid=9 开门见山,不介绍,不废话 建议食用本文前先食用 https://www.cnblogs.com/ ...

  4. 挂号平台首页开发(UI组件部分)

    JQ插件模式开发UI组件 JQ插件开发方法: 1.$.extend() 扩展JQ(比较简单,功能略显不足) $.extend({ sayHello:function(){ console.log(&q ...

  5. [红日安全]Web安全Day2 - XSS跨站实战攻防

    本文由红日安全成员: Aixic 编写,如有不当,还望斧正. 大家好,我们是红日安全-Web安全攻防小组.此项目是关于Web安全的系列文章分享,还包含一个HTB靶场供大家练习,我们给这个项目起了一个名 ...

  6. 搭建Linux(Ubuntu)系统下的Differential Datalog运行环境

    DDlog is a bottom-up, incremental, in-memory, typed Datalog engine. It is well suited for writing pr ...

  7. 从零开始一个个人博客 by asp.net core and angular(一)

    这是一个个人叙述自己建设博客的帖子,既然是第一篇那肯定是不牵扯代码了,主要讲一下大体的东西,微软最新的web框架应该就数asp.net core 3.1了这是一个长期支持版,而且是跨平台又开源版本,所 ...

  8. vue基于video.js实现视频播放暂停---切图网

    切图网是最早致力于PSD2HTML切图等web前端外包服务的,随着前端技术的更新迭代,现在也已经全面投入了vue的浪潮了,下面是vue中实现视频播放的方法. vue.js中引入video视频播放器 m ...

  9. PMP--1.2 PMBOK指南组成部分

    图1.2.5 ​ PMBOK指南关键组成部分在项目中的相互关系说明:项目生命周期中包含项目阶段,项目阶段结束之后就是阶段关口: 而项目管理过程和项目管理过程组以及项目管理知识领域都是为了项目生命周期服 ...

  10. 分布式任务调度XXL-JOB初体验

    简介 XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速.学习简单.轻量级.易扩展.现已开放源代码并接入多家公司线上产品线,开箱即用. 官方文档很完善,不多赘述.本文主要是搭建XX ...