Well, you can just compile
class B {
def b = { ??? }
}
trait A extends B {
def a = { ??? }
}
and check the bytecode:
> javap -c -l -p B
Compiled from "B.scala"
public class B {
public scala.runtime.Nothing$ b();
Code:
0: getstatic #16 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: invokevirtual #19 // Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
6: areturn
LineNumberTable:
line 2: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this LB;
public B();
Code:
0: aload_0
1: invokespecial #25 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 4: 0
line 1: 4
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LB;
}
> javap -c -l -p A
Compiled from "B.scala"
public interface A {
public static scala.runtime.Nothing$ a$(A);
Code:
0: aload_0
1: invokespecial #15 // InterfaceMethod a:()Lscala/runtime/Nothing$;
4: areturn
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 $this LA;
public scala.runtime.Nothing$ a();
Code:
0: getstatic #22 // Field scala/Predef$.MODULE$:Lscala/Predef$;
3: invokevirtual #25 // Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
6: areturn
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this LA;
public static void $init$(A);
Code:
0: return
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 $this LA;
}
As you see scalac
make A
just an interface with default methods (they are available since Java 8). You might wonder, what would happen if you wanted to call some B
method in A
since we don't see that A extends B
in bytecode:
trait A extends B {
def a = { b; ??? }
}
now A changed to:
> javap -c -l -p A
Compiled from "B.scala"
public interface A {
public static scala.runtime.Nothing$ a$(A);
Code:
0: aload_0
1: invokespecial #15 // InterfaceMethod a:()Lscala/runtime/Nothing$;
4: areturn
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 $this LA;
public scala.runtime.Nothing$ a();
Code:
0: aload_0
1: checkcast #18 // class B
4: invokevirtual #21 // Method B.b:()Lscala/runtime/Nothing$;
7: athrow
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 this LA;
public static void $init$(A);
Code:
0: return
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 $this LA;
}
As we can see the code uses checkcast
s to cast this
to B
and then calls 'B's method - this means that scalac
will have to make sure that then you instantiate A
it will be something that also extends B
! So let's check what will happen when we do this:
class B {
def b = { ??? }
}
trait A extends B {
def a = { b; ??? }
}
class C extends A
and the C is
> javap -c -l -p C
Compiled from "B.scala"
public class C extends B implements A {
public scala.runtime.Nothing$ a();
Code:
0: aload_0
1: invokestatic #16 // InterfaceMethod A.a$:(LA;)Lscala/runtime/Nothing$;
4: areturn
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LC;
public C();
Code:
0: aload_0
1: invokespecial #22 // Method B."<init>":()V
4: aload_0
5: invokestatic #26 // InterfaceMethod A.$init$:(LA;)V
8: return
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LC;
}
As you can see this is very context-dependent how things end up with bytecode - the compiler will juggle things around to make it fit things in the end, but only if it will know how you use it.
Therefore if you want interoperability with Java, stick to simple trait
s and class
es as your common interface, and let Scala instantiate more complex implementations. Doing this yourself from Java might bite you in many unexpected ways.