Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Decompile Scala code: why there are two overridden methods in the derived 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 of the above code is decompiled as:

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();
}

I cannot understand why there are two "versions" of method m1 in the decompiled code. From my understanding, B.m1 just overrides A.m1 and public java.lang.Object m1(java.lang.Object) belongs to A and should not be in class B.

like image 545
CodingNow Avatar asked Feb 24 '18 22:02

CodingNow


1 Answers

This is a synthetic bridge method.

In Java bytecode, methods only override methods with the exact same signature. If B did not have any instance of Object m1(Object), then any attempts to call it would call the implementation in A instead, which is not what you want. Therefore, the compiler inserts a synthetic bridge method which simply calls B m1(Object). This behavior is not specific to Scala - it happens in pure Java as well.

You can look at it in more detail by examining the disassembly. If I compile and diassemble the following code

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

The relevant parts of B are

.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 

As you can see, the method m1 (Ljava/lang/Object;)Ljava/lang/Object; simply forwards the arguments to m1 (Ljava/lang/Object;)LB;.

like image 129
Antimony Avatar answered Oct 15 '22 19:10

Antimony