如何为 JVM 目标编译扩展 类 的 Scala 特征?
How are scala traits that extend classes compiled for a JVM target?
我从 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 起可用)。你可能想知道,如果你想在 A
中调用一些 B
方法会发生什么,因为我们在字节码中看不到 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
s 将 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;
}
如您所见,这非常 context-dependent 字节码是如何结束的 - 编译器将处理各种事情以使其最终适合所有内容,但前提是它知道您如何使用它。
因此,如果您希望与 Java 进行互操作,请坚持使用简单的 trait
s 和 class
es 作为您的通用接口,并让 Scala 实例化更复杂的实现。从 Java 自己做这件事可能会以许多意想不到的方式对你造成伤害。
我从 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 起可用)。你可能想知道,如果你想在 A
中调用一些 B
方法会发生什么,因为我们在字节码中看不到 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
s 将 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;
}
如您所见,这非常 context-dependent 字节码是如何结束的 - 编译器将处理各种事情以使其最终适合所有内容,但前提是它知道您如何使用它。
因此,如果您希望与 Java 进行互操作,请坚持使用简单的 trait
s 和 class
es 作为您的通用接口,并让 Scala 实例化更复杂的实现。从 Java 自己做这件事可能会以许多意想不到的方式对你造成伤害。