You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This discussion was converted from issue #8 on June 09, 2023 03:12.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
重写方法(Override)
相信平时习惯写 Java 代码的同学必然不会对
@Override
感到陌生了,其含义就是当前类的这个方法是对父类同名方法的 重写。Java 作为面向对象语言,其中最典型的特点就是 多态性。
而作为多态的重要特性,重写是子类对从父类继承而来的功能进行改造、引入自身特性的方式。
同名,同参,非私有,非静态(静态的不行,相当于子类隐藏了父类方法)... 以上就是构成重写的条件。
显然这里面是不包括方法 返回类型 的,也就是说如果子类中出现了满足以上几点,且与父类方法相同、仅是返回类型不同的方法,是不能通过编译的:
被认为子类已经存在该方法,同名同参方法不允许同时出现在子类中。
这要求子类方法的返回类型,至少是要与父类方法的返回类型 兼容(比如子类方法的返回类型是父类方法的返回类型的子类)的,才能实现重写:
这样就没问题了。
其中方法的入参、出参类型被称为 方法描述符(Method Descriptor),在 JVM 规范中有以下描述:
然而 JVM 层面却没有这种限制,即同名同参不同返回类型的方法是可以出现在同一个类中的(字节码),因为它是基于类和方法名、方法描述符来识别方法的。
换而言之,在 JVM 层面判定子类方法对父类构成重写,还需要加上 相同的返回类型 这个条件。
桥接方法(Bridge Method)
存在 Java 层面实现了重写,但在 JVM 层面却不是重写的情况:
比如上面的子类方法返回类型是
Integer
,父类方法的返回类型是Number
。另一种是使用了泛型参数:
这两种情况在 Java 层面都是重写,在 JVM 中却不是重写(1 是返回类型不同,2 是参数不同)。
返回类型不同
对
Test1
的代码进行编译,再反编译SubClass
:可见在第 18 行中有提示:
参考 JVM 规范可知该方法是桥接方法(ACC_BRIDGE),而且由编译器自动生成(ACC_SYNTHETIC)。
这是编译器为了保持 Java 重写的语义,在编译时自动生成的方法,使之在 JVM 层面也实现了重写。
由于桥接方法的描述符与子类方法描述符(第 2 行)不同,JVM 可据此定位到具体方法。
泛型方法
在
Test2
中子类重写了父类的一个泛型方法,编译器也会在子类生成桥接方法。编译
Test2
的代码:反编译
SuperClass
:可见泛型参数
T arg
在擦除后被修改成Object
:再反编译
SubClass
:编译器生成的桥接方法(第 16 行)的描述符是与父类方法一致的:
因此运行子类方法,实际上是执行桥接方法:
invokevirtual
指令在代码中调用子类方法时会执行桥接方法:
编译
Test
的代码(加入上述main
方法)。反编译
Test1
:上述代码在编译时对
helloWorld
方法的调用会被编译为invokevirtual
指令。invokevirtual
指令是 JVM 用于调用非私有实例方法的,在执行时通常需要根据调用者的动态类型来确定具体的目标方法(除非在编译时就可以确定具体的类型)。符号引用
在
SubClass
字节码的常量池中,可以看到helloWorld
方法:这是指向
helloWorld
的非接口符号引用(Methodref,如果某个方法是由实现接口而来的,则为接口符号引用 InterfaceMethodref)。要执行符号引用的字节码,需要先根据方法名称和描述符查找到具体的类、转换为实际引用。
其查找的顺序是:子类(即本类)-> 父类(直到
Object
类)-> 子类实现的接口(直接或间接)。如果在最后一步中有多个符合条件的目标方法,则取其中一个。
在示例的
SubClass
中已经有helloWorld
方法,因此可以直接取用。参考
Beta Was this translation helpful? Give feedback.
All reactions