《深入理解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. ...
随机推荐
- 2018 ICPC Asia Singapore Regional A. Largest Triangle (计算几何)
题目链接:Kattis - largesttriangle Description Given \(N\) points on a \(2\)-dimensional space, determine ...
- c++计算1到100以内的质数
自考c++实践的时候,有个求计算1-100的质数的问题,没搞出来 由于考试使用的是Dev-C++开发工具,为了下次考试做准备,改用该工具,直接下载安装即可,不会涉及到什么破解等 下载地址:https: ...
- Sqlplus常用指令
一.ORACLE的启动和关闭1.在单机环境下2.在双机环境下二.Oracle数据库有哪几种启动方式1.startup nomount 非安装启动,这种方式启动下可执行:重建控制文件.重建数据库2.st ...
- Python面试题之这两个参数是什么意思:*args,**kwargs?我们为什么要使用它们?
如果我们不确定要往函数中传入多少个参数,或者我们想往函数中以列表和元组的形式传参数时,那就使要用*args: 如果我们不知道要往函数中传入多少个关键词参数,或者想传入字典的值作为关键词参数时,那就要使 ...
- Msf+Donut执行任意可执行文件
地址:donut:https://github.com/TheWover/donut 介绍:是一个shellcode生成工具,可以将.NET程序集等转换为shellcode. 使用:donut.exe ...
- dubbo-源码阅读之容器启动
dubbo Main 例子 public class Start { public static void main(String[] args) throws Exception { com.ali ...
- 视频专家之路【四】:ffmpeg简单实战之获取属性
本文是听了雷宵骅大神的课之后的总结,部分内容借用了其PPT的内容,如有侵权请告知删除. 雷宵骅大神的博客为:https://blog.csdn.net/leixiaohua1020 本节的目的正式开始 ...
- vue Inline JavaScript is not enabled. Is it set in your options?
Vue 全家桶 vue全面介绍--全家桶.项目实例 https://www.cnblogs.com/nogodie/p/9853660.html vue项目 Inline JavaScript is ...
- vue 父到子动态传值 子组件实时渲染
近期项目中需要一个功能,根据选择不同的 团队片区 id 展示不同的数据,团队id 在父组件 数据在子组件中展示. 根据不同的团队 动态渲染数据总览. 父组件: <Cards ref=" ...
- 【NIO】IO与NIO的区别
一.概念 NIO即New IO,这个库是在JDK1.4中才引入的.NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多.在Java API中提供了两套N ...