问题描述
我从this question得知Scala为trait
生成了诸如
trait A {
def a = { ... }
}
类似于以下Java代码的结构
public interface A {
public void a();
}
public class A$class {
public static void a(A self) { ... }
}
但是,在Scala中,trait
可以扩展class
:
class B {
def b = { ??? }
}
trait A extends B {
def a = { ??? }
}
在接口不能从类继承的Java等效语言中,如何翻译?
是否为B
生成了其他接口?
Scala功能对Java互操作性有影响吗?
解决方法
好吧,你可以编译
class B {
def b = { ??? }
}
trait A extends B {
def a = { ??? }
}
并检查字节码:
> 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;
}
您看到scalac
使得A
只是具有默认方法的接口(自Java 8开始可用)。您可能想知道,如果您想在B
中调用某个A
方法会发生什么,因为在字节码中看不到A extends B
:
trait A extends B {
def a = { b; ??? }
}
现在A更改为:
> 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;
}
我们可以看到,代码使用checkcast
将this
强制转换为B
,然后调用'B的方法-这意味着scalac
必须确保然后您实例化A
,它将扩展B
!因此,让我们检查一下这样做会发生什么:
class B {
def b = { ??? }
}
trait A extends B {
def a = { b; ??? }
}
class C extends A
C是
> 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;
}
正如您所看到的,这与上下文如何以字节码结尾是非常相关的-编译器将弄乱一切以使其最终适应,但前提是它知道您将如何使用它。
因此,如果要与Java互操作,请坚持使用简单的trait
和class
作为通用接口,并让Scala实例化更复杂的实现。用Java自己做这件事可能会以许多意想不到的方式咬你。