《深入理解Java虚拟机》- 重载与重写
Posted chenscript
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《深入理解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虚拟机-----java内存区域以及内存溢出异常