04 | JVM是如何执行方法调用的?(上)
郑雨迪
该思维导图由 AI 生成,仅供参考
前不久在写代码的时候,我不小心踩到一个可变长参数的坑。你或许已经猜到了,它正是可变长参数方法的重载造成的。(注:官方文档建议避免重载可变长参数方法,见[1]的最后一段。)
我把踩坑的过程放在了文稿里,你可以点击查看。
当时情况是这样子的,某个 API 定义了两个同名的重载方法。其中,第一个接收一个 Object,以及声明为 Object…的变长参数;而第二个则接收一个 String、一个 Object,以及声明为 Object…的变长参数。
这里我想调用第一个方法,传入的参数为 (null, 1)。也就是说,声明为 Object 的形式参数所对应的实际参数为 null,而变长参数则对应 1。
通常来说,之所以不提倡可变长参数方法的重载,是因为 Java 编译器可能无法决定应该调用哪个目标方法。
在这种情况下,编译器会报错,并且提示这个方法调用有二义性。然而,Java 编译器直接将我的方法调用识别为调用第二个方法,这究竟是为什么呢?
带着这个问题,我们来看一看 Java 虚拟机是怎么识别目标方法的。
重载与重写
在 Java 程序里,如果同一个类中出现多个名字相同,并且参数类型相同的方法,那么它无法通过编译。也就是说,在正常情况下,如果我们想要在同一个类中定义名字相同的方法,那么它们的参数类型必须不同。这些方法之间的关系,我们称之为重载。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
本文深入探讨了JVM方法调用的执行过程,以及Java编译器在选择重载方法时的逻辑。作者详细介绍了方法的重载和重写,以及Java虚拟机中的静态绑定和动态绑定。文章还提到了重载方法在编译过程中的识别和选择规则,以及子类中重载和重写方法的关系。此外,作者指出方法重写是多态的一种体现方式,允许子类在继承父类功能的同时拥有自己独特的行为。通过实例分析,读者可以快速了解JVM方法调用的执行过程,以及Java编译器在选择重载方法时的逻辑。文章内容深入浅出,适合对JVM方法调用过程感兴趣的读者阅读。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入拆解 Java 虚拟机》,新⼈⾸单¥59
《深入拆解 Java 虚拟机》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(111)
- 最新
- 精选
- Thomas置顶看明白了......这篇真好2018-07-27941
- jiaobuchongจุ๊บ参考老师最后的例子,写了博客总结了一下:https://blog.csdn.net/jiaobuchong/article/details/83722193,欢迎拍砖。
作者回复: 赞!
2018-11-06247 - 曲东方Merchant类中actionPrice方法返回值类型为Number NaiveMerchant类中actionPrice方法返回值类型为Double NaiveMerchant类生成的字节码中有两个参数类型相同返回值类型不同的actionPrice方法 Method actionPrice:(DLCustomer;)Ljava/lang/Double; Method actionPrice:(DLCustomer;)Ljava/lang/Number; // 桥接到返回值为double的方法 flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC 方法返回值不同为何也要产生桥接方法呢? 为了保证重写语义? 不知为何javac在编译 NaiveMerchant naiveMerchant = new NaiveMerchant(); Number number = naiveMerchant.actionPrice(1d, null) // 特意要求Number类型的返回值(方法描述符) 时,总invokevirtual到Method NaiveMerchant.actionPrice:(DLCustomer;)Ljava/lang/Double,这又是为什么呢? 附jdk版本 java version "1.8.0_172" Java(TM) SE Runtime Environment (build 1.8.0_172-b11) GraalVM 1.0.0-rc5 (build 25.71-b01-internal-jvmci-0.46, mixed mode)
作者回复: 1 对的,为了保证重写语义。 2 生成的桥接方法还有一个acc_synthetic标记,代表对程序不可见。因此javac不能直接选取那个方法。
2018-08-117 - vimfun老师,public final 或 public static final 的方法,是不是在 虚拟机中解析为静态绑定的
作者回复: 静态方法都是静态绑定。调用的目标方法是public final 的话,HotSpot虚拟机也会静态绑定。但这属于优化,其它虚拟机不一定这么做。
2018-07-276 - 杨春鹏老师,关于方法调用的字节码指令中的invokespecial:调用实现接口的默认方法。 我测试了一下,发现子类中调用实现接口的默认方法还是使用的invokeinrerface。
作者回复: 多谢指出!这里我指的是使用super关键字调用所实现接口的默认方法。
2018-07-2734 - 小兵invokespecial:用于调用私有实例方法、构造器,以...和所实现接口的默认方法。 这里所实现接口的默认方法具体是指什么?
作者回复: 接口的default方法,可能这样写比较清楚些
2018-12-0622 - 王侦老师,最后那个例子能不能重新整理一下?说明一下操作步骤。不知道怎么操作!是写在一个文件还是多个文件?而且编译时报两个错误:一个是VIP要有一个类,一个是NaiveMerchant报错?
作者回复: 多个文件。我这边代码实际上是分开的,上传时合成一个代码框了。回头我改一下。
2018-10-262 - 陈树义文章开头的例子,我在JDK8环境下写了个例子测试,发现貌似和文中所说的不一致。 不知道是不是因为JDK版本问题,还是我例子有问题?
作者回复: 我在java8和java10里测得的都一样
2018-07-272 - 易水寒给下面某人解释一个问题,final[3][4]指的是参考下面的链接3,4链接,论文当中引用了别人的东西都这么写
作者回复: 谢谢!我之后会统一一下引用方式,方便在文中就能点击。
2018-09-261 - Go Ashton如果没有找到,在 C 所直接实现或间接实现的接口中搜索” 能否有个例子,什么是直接实现?什么是间接实现? 可能我知道这是什么,但叫法不同
作者回复: C implements Interface1,Interface1 extends Interface2,C直接实现Interface1,间接实现Interface2。 如果你知道更加通用的叫法,麻烦留言告诉我哈
2018-09-1821
收起评论