博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JVM执行方法调用(一)- 重载与重写
阅读量:5813 次
发布时间:2019-06-18

本文共 3194 字,大约阅读时间需要 10 分钟。

回顾Java语言中的重载与重写,并且看看JVM是怎么处理它们的。

重载Overload

定义:

  • 在同一个类中有多个方法,它们的名字相同,但是参数类型不同。
  • 或者,父子类中,子类有一个方法与父类非私有方法名字相同,但是参数类型不同。那么子类的这个方法对父类方法构成重载。

JVM是怎么处理重载的?其实是编译阶段编译器就已经决定好调用哪一个重载方法。看下面代码:

class Overload {  void invoke(Object obj, Object... args) { }  void invoke(String s, Object obj, Object... args) { }  void test1() {    // 调用第二个 invoke 方法    invoke(null, 1);      }  void test2() {    // 调用第二个 invoke 方法    invoke(null, 1, 2);   }  void test3() {    // 只有手动绕开可变长参数的语法糖,才能调用第一个invoke方法    invoke(null, new Object[]{1});   }}

上面的注释告诉了我们结果,那么怎么才能证明上面的注释呢?我们利用javap观察字节码可以知道。

$ javac Overload.java$ javap -c Overload.javaCompiled from "Overload.java"class Overload {  ...  void invoke(java.lang.Object, java.lang.Object...);    Code:       0: return  void invoke(java.lang.String, java.lang.Object, java.lang.Object...);    Code:       0: return  void test1();    Code:      ...      10: invokevirtual #4  // Method invoke:(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V      13: return  void test2();    Code:      ...      17: invokevirtual #4  // Method invoke:(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V      20: return  void test3();    Code:      ...      13: invokevirtual #5  // Method invoke:(Ljava/lang/Object;[Ljava/lang/Object;)V      16: return}

这里面有很多JVM指令,你暂且不用关心,我们看test1test2test3方法调用的是哪个方法:

void test1();    Code:      ...      10: invokevirtual #4  // Method invoke:(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V      13: return

invoke是方法名,(Ljava/lang/String;Ljava/lang/Object;[Ljava/lang/Object;)V则是方法描述符。这里翻译过来就是void invoke(String, Object, Object[]),Java的可变长参数实际上就是数组,所以等同于void invoke(String, Object, Object...)。同理,test2调用的是void invoke(String, Object, Object...)test3调用的是void invoke(Object, Object...)。关于方法描述符的详参和。

所以重载方法的选择是在编译过程中就已经决定的,下面是编译器的匹配步骤:

  1. 不允许自动拆装箱,不允许可变长参数,尝试匹配
  2. 如果没有匹配到,则允许自动拆装箱,不允许可变长参数,尝试匹配
  3. 如果没有匹配到,则允许自动拆装箱,允许可变长参数,尝试匹配

注意:编译器是根据实参类型来匹配,实参类型和实际类型不是一个概念

如果在一个步骤里匹配到了多个方法,则根据形参类型来找最贴切的。在上面的例子中第一个invoke的参数是Object, Object...,第二个invoke的参数是String, Object, Object...,两个方法的第一个参数StringObject的子类,因此更为贴切,所以invoke(null, 1, 2)会匹配到第二个invoke方法上。

重写Override

Java语言中的定义:

  • 子类方法有一个方法与父类方法的名字相同且参数类型相同。
  • 父类方法的返回值可以替换掉子类方法的返回值。也就是说父类方法的返回值类型:

    • 要么和子类方法返回值类型一样。
    • 要么是子类方法返回值类型的父类。
  • 两者都是非私有、非静态方法。

(更多详细信息可参考,这里除了有更精确详细的重写的定义,同时包含了范型方法的重写定义。)

但是JVM中对于重写的定义则有点不同:

  • 子类方法的名字与方法描述符与父类方法相同。
  • 两者都是非私有、非静态方法。

(更多详细信息可参考)

注意上面提到的方法描述符,前面讲过方法描述符包含了参数类型及返回值,JVM要求这两个必须完全相同才可以,但是Java语言说的是参数类型相同但是返回值类型可以不同。Java编译器通过创建Bridge Method来解决这个问题,看下面代码:

class A {  Object f() {    return null;  }}class C extends A {  Integer f() {    return null;  }}

然后用javap查看编译结果:

$ javac Override.java$ javap -v C.classclass C extends A...{  java.lang.Integer f();    descriptor: ()Ljava/lang/Integer;    flags:    Code:      stack=1, locals=1, args_size=1         0: aconst_null         1: areturn  ...  java.lang.Object f();    descriptor: ()Ljava/lang/Object;    flags: ACC_BRIDGE, ACC_SYNTHETIC    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokevirtual #2                  // Method f:()Ljava/lang/Integer;         4: areturn      LineNumberTable:        line 7: 0}

可以看到编译器替我们创建了一个Object f()的Bridge Method,它调用的是Integer f(),这样就构成了JVM所定义的重写。

思维导图

clipboard.png

参考文档

广告

clipboard.png

转载地址:http://nstbx.baihongyu.com/

你可能感兴趣的文章
01 awk工具的使用
查看>>
UIImagePickerController拍照与摄像
查看>>
Maven--(一个坑)在settings.xml文件中添加mirrors导致无法新建Maven项目
查看>>
linux日志:syslogd和klogd及syslog
查看>>
Python模块学习笔记— —time与datatime
查看>>
python调用windows api
查看>>
linux添加somebody到组
查看>>
Linux内核中的printf实现【转】
查看>>
第四章 mybatis批量insert
查看>>
Dom4j生成xml
查看>>
rsync算法原理和工作流程分析
查看>>
Java并发框架——什么是AQS框架
查看>>
pthread_cleanup_push
查看>>
【数据库】
查看>>
spring框架中的@Import注解
查看>>
How to set the initial value of a select element using AngularJS ng-options & track by
查看>>
feginclient和ribbon的重试策略
查看>>
从一次线上故障思考Java问题定位思路
查看>>
Win配置Apache+mod_wsgi+django环境+域名
查看>>
第四届中国汽车产业信息化技术创新峰会将于6月在沪召开
查看>>