反编译Scala代码:为什么派生的class中有两个被重写的方法?

Decompile Scala code: why there are two overridden methods in the derived class?

反编译Scala代码:为什么派生中有两个重写的方法class?

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
    }
}

class 上述代码的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();
}

我无法理解为什么反编译代码中有两个"versions"方法m1。 根据我的理解,B.m1 只是覆盖 A.m1public java.lang.Object m1(java.lang.Object) 属于 A,不应该 在 class B

这是一种合成桥法。

在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;

您看到两种方法,因为一种是 "the real one",第二种是 桥接方法 生成以支持 covariant return types

来自JavaDoc for Class.getMethod:

while the Java language forbids a class to declare multiple methods with the same signature but different return types, the Java virtual machine does not. This increased flexibility in the virtual machine can be used to implement various language features. For example, covariant returns can be implemented with bridge methods; the bridge method and the method being overridden would have the same signature but different return types.

桥接方法将设置标志 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