《深入理解Java虚拟机》- 重载与重写
这一节打算从“方法调用”的主题进行分析。
方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不设计方法内部的具体运行过程。
一、概念
- 解析调用:所有方法调用中的目标方法在Class文件里都是一个常量池中的引用,在类加载的解析阶段,会将其中一部分符号引用转化为直接引用。也就是说,调用目标在程序代码写好、编译器进行编译时就必须能确定下来的方法调用成为解析。
小插曲:这里提供5条方法调用字节码指令
- invokestatic:调用静态方法
- invokespecial:调用实例构造器<init>方法、私有方法和父类方法
- invokevirtual:调用所有的虚方法
- invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象
- invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在Java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
只要能被invokestatic 和invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法4类,他们在类加载的时候就会把符号引用解析为该方法的直接引用,这些方法也可以成为非虚方法。另外,在Java语言规范中明确说明了final方法也是一种非虚方法,虽然final方法是使用invokevirtual指令来调用的,但是无法被覆盖,多态的选择结果也肯定是唯一的。
- 分派调用
- 静态分派(体现为重载)
- 动态分派(体现为重写)
二、代码
- 重载
- java代码
public class StaticDispatch{
static abstract class Human{}
static class Man extends Human{}
static class Woman 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(Woman guy){
System.out.println("hello,girl");
}
public static void main(String... args){
Human man = new Man();
Human woman = new Woman();
//Human称为变量的静态类型,后面的Man被称为实际类型。
StaticDispatch sr = new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
//静态类型变化(就是在使用时改变静态类型,变量本身的静态类型不会被改变,并且最终的静态类型是在编译器可知的)
sr.sayHello((Man)man);
sr.sayHello((Woman)woman);
}
}输出:
[root@localhost tmp4]# java StaticDispatch
hello,guy
hello,guy
hello,man
hello,girl分析:(上面的代码需要直接使用javac编译,绕过IDE的语法识别就可以了)在没有发生强制转换的时候,使用的是静态类型。当发生静态类型变化的时候,也就是使用强制转换的时候,虚拟机才会识别实际类型。
- 上面代码对应的字节码
Constant pool:
#1 = Methodref #16.#34 // java/lang/Object."<init>":()V
#2 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #37 // hello,guy
#4 = Methodref #38.#39 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = String #40 // hello,man
#6 = String #41 // hello,girl
#7 = Class #42 // StaticDispatch$Man
#8 = Methodref #7.#34 // StaticDispatch$Man."<init>":()V
#9 = Class #43 // StaticDispatch$Woman
#10 = Methodref #9.#34 // StaticDispatch$Woman."<init>":()V
#11 = Class #44 // StaticDispatch
#12 = Methodref #11.#34 // StaticDispatch."<init>":()V
#13 = Methodref #11.#45 // StaticDispatch.sayHello:(LStaticDispatch$Human;)V
#14 = Methodref #11.#46 // StaticDispatch.sayHello:(LStaticDispatch$Man;)V
#15 = Methodref #11.#47 // StaticDispatch.sayHello:(LStaticDispatch$Woman;)V
#16 = Class #48 // java/lang/Object
#17 = Utf8 Woman
#18 = Utf8 InnerClasses
#19 = Utf8 Man
#20 = Class #49 // StaticDispatch$Human
#21 = Utf8 Human
#22 = Utf8 <init>
#23 = Utf8 ()V
#24 = Utf8 Code
#25 = Utf8 LineNumberTable
#26 = Utf8 sayHello
#27 = Utf8 (LStaticDispatch$Human;)V
#28 = Utf8 (LStaticDispatch$Man;)V
#29 = Utf8 (LStaticDispatch$Woman;)V
#30 = Utf8 main
#31 = Utf8 ([Ljava/lang/String;)V
#32 = Utf8 SourceFile
#33 = Utf8 StaticDispatch.java
#34 = NameAndType #22:#23 // "<init>":()V
#35 = Class #50 // java/lang/System
#36 = NameAndType #51:#52 // out:Ljava/io/PrintStream;
#37 = Utf8 hello,guy
#38 = Class #53 // java/io/PrintStream
#39 = NameAndType #54:#55 // println:(Ljava/lang/String;)V
#40 = Utf8 hello,man
#41 = Utf8 hello,girl
#42 = Utf8 StaticDispatch$Man
#43 = Utf8 StaticDispatch$Woman
#44 = Utf8 StaticDispatch
#45 = NameAndType #26:#27 // sayHello:(LStaticDispatch$Human;)V
#46 = NameAndType #26:#28 // sayHello:(LStaticDispatch$Man;)V
#47 = NameAndType #26:#29 // sayHello:(LStaticDispatch$Woman;)V
#48 = Utf8 java/lang/Object
#49 = Utf8 StaticDispatch$Human
#50 = Utf8 java/lang/System
#51 = Utf8 out
#52 = Utf8 Ljava/io/PrintStream;
#53 = Utf8 java/io/PrintStream
#54 = Utf8 println
#55 = Utf8 (Ljava/lang/String;)V上面是常量池。(也就是#号对上的引用)
public static void main(java.lang.String...);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
Code:
stack=2, locals=4, args_size=1
0: new #7 // class StaticDispatch$Man
3: dup
4: invokespecial #8 // Method StaticDispatch$Man."<init>":()V
7: astore_1
8: new #9 // class StaticDispatch$Woman
11: dup
12: invokespecial #10 // Method StaticDispatch$Woman."<init>":()V
15: astore_2
16: new #11 // class StaticDispatch
19: dup
20: invokespecial #12 // Method "<init>":()V
23: astore_3
24: aload_3
25: aload_1
26: invokevirtual #13 // Method sayHello:(LStaticDispatch$Human;)V
29: aload_3
30: aload_2
31: invokevirtual #13 // Method sayHello:(LStaticDispatch$Human;)V
34: aload_3
35: aload_1
36: checkcast #7 // class StaticDispatch$Man
39: invokevirtual #14 // Method sayHello:(LStaticDispatch$Man;)V
42: aload_3
43: aload_2
44: checkcast #9 // class StaticDispatch$Woman
47: invokevirtual #15 // Method sayHello:(LStaticDispatch$Woman;)V
50: return
LineNumberTable:
line 18: 0
line 19: 8
line 21: 16
line 22: 24
line 23: 29
line 24: 34
line 25: 42
line 27: 50
}
SourceFile: "StaticDispatch.java"
InnerClasses:
static #17= #9 of #11; //Woman=class StaticDispatch$Woman of class StaticDispatch
static #19= #7 of #11; //Man=class StaticDispatch$Man of class StaticDispatch
static abstract #21= #20 of #11; //Human=class StaticDispatch$Human of class StaticDispatch这里的字节码中,可以看到通过invokespecial调用类的构造方法之后,到了26行和31行使用了分派默认调用了静态类型即Human,而39行和47行前使用了强转化之后,invokevirtual调用虚方法就会分派指向实际类型。
- java代码
- 重写
- java代码
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();
}
}这是简单的重写代码。输出如下:
[root@localhost tmp5]# java DynamicDispatch
man say hello
Woman say hello
Woman say hello - 字节码
[root@localhost tmp5]# javap -verbose DynamicDispatch
Classfile /usr/local/asmtools-7.0-build/binaries/lib/tmp5/DynamicDispatch.class
Last modified Aug 19, 2019; size 514 bytes
MD5 checksum a9486d1c7dc75a210b82bd18f1782dfa
Compiled from "DynamicDispatch.java"
public class DynamicDispatch
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #8.#22 // java/lang/Object."<init>":()V
#2 = Class #23 // DynamicDispatch$Man
#3 = Methodref #2.#22 // DynamicDispatch$Man."<init>":()V
#4 = Class #24 // DynamicDispatch$Woman
#5 = Methodref #4.#22 // DynamicDispatch$Woman."<init>":()V
#6 = Methodref #12.#25 // DynamicDispatch$Human.sayHello:()V
#7 = Class #26 // DynamicDispatch
#8 = Class #27 // java/lang/Object
#9 = Utf8 Woman
#10 = Utf8 InnerClasses
#11 = Utf8 Man
#12 = Class #28 // DynamicDispatch$Human
#13 = Utf8 Human
#14 = Utf8 <init>
#15 = Utf8 ()V
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 main
#19 = Utf8 ([Ljava/lang/String;)V
#20 = Utf8 SourceFile
#21 = Utf8 DynamicDispatch.java
#22 = NameAndType #14:#15 // "<init>":()V
#23 = Utf8 DynamicDispatch$Man
#24 = Utf8 DynamicDispatch$Woman
#25 = NameAndType #29:#15 // sayHello:()V
#26 = Utf8 DynamicDispatch
#27 = Utf8 java/lang/Object
#28 = Utf8 DynamicDispatch$Human
#29 = Utf8 sayHello
{
public DynamicDispatch();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0 public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: new #2 // class DynamicDispatch$Man
3: dup
4: invokespecial #3 // Method DynamicDispatch$Man."<init>":()V
7: astore_1
8: new #4 // class DynamicDispatch$Woman
11: dup
12: invokespecial #5 // Method DynamicDispatch$Woman."<init>":()V
15: astore_2
16: aload_1
17: invokevirtual #6 // Method DynamicDispatch$Human.sayHello:()V
20: aload_2
21: invokevirtual #6 // Method DynamicDispatch$Human.sayHello:()V
24: new #4 // class DynamicDispatch$Woman
27: dup
28: invokespecial #5 // Method DynamicDispatch$Woman."<init>":()V
31: astore_1
32: aload_1
33: invokevirtual #6 // Method DynamicDispatch$Human.sayHello:()V
36: return
LineNumberTable:
line 22: 0
line 23: 8
line 25: 16
line 26: 20
line 28: 24
line 29: 32
line 30: 36
}
SourceFile: "DynamicDispatch.java"
InnerClasses:
static #9= #4 of #7; //Woman=class DynamicDispatch$Woman of class DynamicDispatch
static #11= #2 of #7; //Man=class DynamicDispatch$Man of class DynamicDispatch
static abstract #13= #12 of #7; //Human=class DynamicDispatch$Human of class DynamicDispatch - 在这里需要说明一下invokevirtual指令的运行时解析过程大致分为以下几个步骤:
1.找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C 2.如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。 3.否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。 4.如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
- java代码
《深入理解Java虚拟机》- 重载与重写的更多相关文章
- 深入理解java虚拟机(5)---字节码执行引擎
字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...
- 深入理解Java虚拟机--中
深入理解Java虚拟机--中 第6章 类文件结构 6.2 无关性的基石 无关性的基石:有许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码(ByteCode),从而 ...
- 深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)
目录 1.类文件结构 1.1 Class类文件结构 1.2 魔数与Class文件的版本 1.3 常量池 1.4 访问标志 1.5 类索引.父索引与接口索引集合 1.6 字段表集合 1.7 方法集合 1 ...
- 深入理解Java虚拟机(字节码执行引擎)
深入理解Java虚拟机(字节码执行引擎) 本文首发于微信公众号:BaronTalk 执行引擎是 Java 虚拟机最核心的组成部分之一.「虚拟机」是相对于「物理机」的概念,这两种机器都有代码执行的能力, ...
- 《深入理解 Java 虚拟机》笔记整理
正文 一.Java 内存区域与内存溢出异常 1.运行时数据区域 程序计数器:当前线程所执行的字节码的行号指示器.线程私有. Java 虚拟机栈:Java 方法执行的内存模型.线程私有. 本地方法栈:N ...
- 深入理解Java虚拟机---学习感悟以及笔记
一.为什么要学习Java虚拟机? 这里我们使用举例来说明为什么要学习Java虚拟机,其实这个问题就和为什么要学习数据结构和算法是一个道理,工欲善其事,必先利其器.曾经的我经常害怕处理内存溢 ...
- 《深入理解Java虚拟机》(三)垃圾收集器与内存分配策略
垃圾收集器与内存分配策略 详解 3.1 概述 本文参考的是周志明的 <深入理解Java虚拟机>第三章 ,为了整理思路,简单记录一下,方便后期查阅. 3.2 对象已死吗 在垃圾收集器进行回收 ...
- jvm--深入理解java虚拟机 精华总结(面试)(转)
深入理解java虚拟机 精华总结(面试)(转) 原文地址:http://www.cnblogs.com/prayers/p/5515245.html 一.运行时数据区域 3 1.1 程序计数器 3 1 ...
- 2018.4.23 深入理解java虚拟机(转)
深入理解java虚拟机 精华总结(面试) 一.运行时数据区域 Java虚拟机管理的内存包括几个运行时数据内存:方法区.虚拟机栈.本地方法栈.堆.程序计数器,其中方法区和堆是由线程共享的数据区,其他几个 ...
- 深入理解Java虚拟机:垃圾收集器与内存分配策略
目录 3.2 对象已死吗 判断一个对象是否可被回收 引用类型 finalize() 回收方法区 3.3. 垃圾收集算法 1.Mark-Sweep(标记-清除)算法 2.Copying(复制)算法 3. ...
随机推荐
- bat 笔记
cmd删除非空文件夹 rd+空格+/s/q+空格+d:\filedir for语句的基本用法 在批处理文件中: FOR %%variable IN (command1) DO command2 [co ...
- python学习笔记:json与字典的转换(dump 和dumps load和loads的区别)
1. json序列化(字典转成字符串)方法: dumps:无文件操作 dump:序列化+写入文件 2. json反序列化(字符串转成字典)方法: loads:无文件操作 ...
- Django框架(二十六)—— Django rest_framework-分页器与版本控制
目录 分页器与版本控制 一.三种分页器 二.分页器 1.普通分页(PageNumberPagination) 2.偏移分页(LimitOffsetPagination) 3.加密分页(CursorPa ...
- python 模拟双色球输出
编写Python函数:完成一个双色球彩票的模拟生成过程, 其中前六个为红色球,数字范围1-33,不可重复.最后一个为蓝色球 1-16. import random #red_nums是采集红色球的数字 ...
- swoole安装异步reids
/usr/local/php/bin/phpize ./configure --with-php-config=/usr/local/php/bin/php-config --enable-async ...
- Spring Boot国际化支持
本章将讲解如何在Spring Boot和Thymeleaf中做页面模板国际化的支持,根据系统语言环境或者session中的语言来自动读取不同环境中的文字. 国际化自动配置 Spring Boot中已经 ...
- 廖雪峰的git学习笔记
安装完后,每个机器都要自报家门 Config--配置 global--全局参数 配置全局用户名 $git config --global user.name “Your Name” 配置邮箱 ...
- Hibernate4教程一:入门介绍
第一部分:Hibernate入门 Hibernate是什么 Hibernate是一个轻量级的ORM框架 ORM原理(Object Relational Mapping) ORM ...
- Vue--入门篇
一.v-model和单选按钮(radio) <div id="app"> <input name="sex" value="男&qu ...
- 如何取消IDEA的自动删除行尾空格?
使用IDEA,添加注释的时候敲空格,总是会把行尾空格删除导致代码跑到注释行,很不爽~~ 取消这个不爽的功能:File--Settings--Editor--General--Other--Strip ...