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

2

8

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.

Sinew answered 24/2, 2018 at 22:45 Comment(0)
G
8

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

Glacis answered 24/2, 2018 at 23:19 Comment(0)
U
3

You see two methods because one is "the real one", and the second one is a bridge method generated to support covariant return types.

From the 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.

The bridge method will have flags ACC_BRIDGE and ACC_SYNTHETICS set. This has actually nothing to do with Scala, as you can easily see if you compile the two following classes:

class A {
  public Object m1(int i) { return i; }
}

class B extends A {
  @Override public String m1(int a) { return "hey " + a; }
}

If you now use javap -v to decompile B.class you will see the different flags of the methods:

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
Unseam answered 24/2, 2018 at 23:42 Comment(1)
That's very helpful. Thank you very much.Sinew

© 2022 - 2024 — McMap. All rights reserved.