反编译Scala代码:为什么派生类中有两个重写的方法?
class A
{
private var str: String = "A"
val x: A = this
override def toString(): String = str
def m1(other: AnyRef): AnyRef = {
println("This is A.m1(AnyRef)")
other
}
}
class B extends A {
private var str: String = "B"
var z: Int = 0
override val x: B = this
override def m1(other: AnyRef): B = {
println("This is B.m1(AnyRef)")
this
}
}
上述代码的B类反编译为:
public class test$B extends test$A {
private java.lang.String str;
private int z;
private final test$B x;
private java.lang.String str();
private void str_$eq(java.lang.String);
public int z();
public void z_$eq(int);
public test$B x();
public test$B m1(java.lang.Object);
public java.lang.Object m1(java.lang.Object);
public test$A x();
public test$B();
}
我无法理解为什么反编译代码中有两个方法m1
的“版本”。
根据我的理解,B.m1
仅覆盖A.m1
和public java.lang.Object m1(java.lang.Object)
属于A
,不应该
在班级B
。
答案 0 :(得分:8)
这是一种合成桥接方法。
在Java字节码中,方法仅覆盖具有完全相同签名的方法。如果B没有Object m1(Object)
的任何实例,那么任何调用它的尝试都会调用A中的实现,这不是你想要的。因此,编译器会插入一个简单地调用B m1(Object)
的合成桥接方法。此行为并非特定于Scala - 它也发生在纯Java中。
您可以通过检查反汇编更详细地查看它。如果我编译并反汇编以下代码
class A
{
def m1(other: AnyRef): AnyRef = {
println("This is A.m1(AnyRef)")
other
}
}
class B extends A {
override def m1(other: AnyRef): B = {
println("This is B.m1(AnyRef)")
this
}
}
B的相关部分是
.method public m1 : (Ljava/lang/Object;)LB;
.code stack 2 locals 2
L0: getstatic Field scala/Predef$ MODULE$ Lscala/Predef$;
L3: ldc 'This is B.m1(AnyRef)'
L5: invokevirtual Method scala/Predef$ println (Ljava/lang/Object;)V
L8: aload_0
L9: areturn
L10:
.end code
.methodparameters
other final
.end methodparameters
.end method
.method public bridge synthetic m1 : (Ljava/lang/Object;)Ljava/lang/Object;
.code stack 2 locals 2
L0: aload_0
L1: aload_1
L2: invokevirtual Method B m1 (Ljava/lang/Object;)LB;
L5: areturn
L6:
.end code
.methodparameters
other final
.end methodparameters
.end method
如您所见,方法m1 (Ljava/lang/Object;)Ljava/lang/Object;
只是将参数转发给m1 (Ljava/lang/Object;)LB;
。
答案 1 :(得分:3)
您会看到两种方法,因为一种是#34;真正的一种",第二种是为支持covariant return types而生成的桥接方法。
来自JavaDoc for Class.getMethod:
虽然Java语言禁止类声明具有相同签名但返回类型不同的多个方法,但Java虚拟机却没有。虚拟机中增加的灵活性可用于实现各种语言功能。例如,协变返回可以使用桥接方法实现;桥方法和被覆盖的方法将具有相同的签名但返回类型不同。
网桥方法将设置标记ACC_BRIDGE and ACC_SYNTHETICS。这实际上与Scala无关,因为您可以轻松地看到是否编译了以下两个类:
class A {
public Object m1(int i) { return i; }
}
class B extends A {
@Override public String m1(int a) { return "hey " + a; }
}
如果现在使用javap -v
反编译B.class
,您将看到方法的不同标志:
public java.lang.String m1(int);
descriptor: (I)Ljava/lang/String;
flags: ACC_PUBLIC
[...some lines omitted...]
public java.lang.Object m1(int);
descriptor: (I)Ljava/lang/Object;
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC