Scala特性如何被编译成Java字节码?

我已经玩了一段时间了,我知道特质可以作为Scala等同于接口和抽象类。 特征如何被编译成Java字节码?

我发现了一些简短的解释,说明特征在可能的情况下完全像Java接口一样编译,否则就与其他类进行接口。 但是,我仍然不明白Scala如何实现类的线性化,这是Java中没有的function。

有没有一个很好的来源解释如何特征编译为Java字节码?

我不是专家,但这是我的理解:

特征被编译成一个接口和相应的类。

 trait Foo { def bar = { println("bar!") } } 

成为相当于…

 public interface Foo { public void bar(); } public class Foo$class { public static void bar(Foo self) { println("bar!"); } } 

这留下了问题:如何调用Foo $ class中的静态栏方法? 这个魔术是由Foo特征混合到的类中的编译器完成的。

 class Baz extends Foo 

变得像…

 public class Baz implements Foo { public void bar() { Foo$class.bar(this); } } 

类线性化只是根据语言规范中定义的线性化规则来实现方法的适当版本(在Xxxx $ class类中调用静态方法)。

为了讨论,让我们看看下面的Scala示例,使用多个特征,抽象的和具体的方法:

 trait A { def foo(i: Int) = ??? def abstractBar(i: Int): Int } trait B { def baz(i: Int) = ??? } class C extends A with B { override def abstractBar(i: Int) = ??? } 

目前(即截至Scala 2.11),单一特征被编码为:

  • 一个包含所有特征方法(包括抽象和具体)的抽象声明的interface
  • 一个抽象的静态类,包含所有trait的具体方法的静态方法,并带一个额外的参数$this (在旧版本的Scala中,这个类不是抽象的,但实例化它没有意义)
  • 在特征所在的inheritance层次结构中的每个点上,都是转发到静态类的静态方法的特征中的所有具体方法的合成转发方法

这种编码的主要优点是没有具体成员的特征(与同一个接口同构)实际上编译成一个接口。

 interface A { int foo(int i); int abstractBar(int i); } abstract class A$class { static void $init$(A $this) {} static int foo(A $this, int i) { return ???; } } interface B { int baz(int i); } abstract class B$class { static void $init$(B $this) {} static int baz(B $this, int i) { return ???; } } class C implements A, B { public C() { A$class.$init$(this); B$class.$init$(this); } @Override public int baz(int i) { return B$class.baz(this, i); } @Override public int foo(int i) { return A$class.foo(this, i); } @Override public int abstractBar(int i) { return ???; } } 

但是,Scala 2.12需要Java 8,因此可以在接口中使用默认方法和静态方法,结果如下所示:

 interface A { static void $init$(A $this) {} static int foo$(A $this, int i) { return ???; } default int foo(int i) { return A.foo$(this, i); }; int abstractBar(int i); } interface B { static void $init$(B $this) {} static int baz$(B $this, int i) { return ???; } default int baz(int i) { return B.baz$(this, i); } } class C implements A, B { public C() { A.$init$(this); B.$init$(this); } @Override public int abstractBar(int i) { return ???; } } 

正如您所看到的,静态方法和转发器的旧devise已被保留,它们只是被折叠到界面中。 这个trait的具体方法现在已经作为static方法进入了接口本身,转发器方法并不是在每个类中合成,而是一次定义为default方法,静态$init$方法(表示trait主体中的代码)已经移动到界面中,使得伴随的静态类是不必要的。

它可能可以这样简化:

 interface A { static void $init$(A $this) {} default int foo(int i) { return ???; }; int abstractBar(int i); } interface B { static void $init$(B $this) {} default int baz(int i) { return ???; } } class C implements A, B { public C() { A.$init$(this); B.$init$(this); } @Override public int abstractBar(int i) { return ???; } } 

我不确定为什么没有这样做。 乍一看,当前的编码可能会给我们一些前向兼容性:你可以使用一个新的编译器编译的特性与旧的编译器编译的类,那些旧的类将简单地覆盖它们从接口inheritance的default转发器方法相同的。 除此之外,转发器方法将尝试调用不再存在的A$classB$class的静态方法,因此假设前向兼容性实际上不起作用。

对此的一个很好的解释是:

繁忙的Java开发人员指南Scala:特性和行为 – JVM中的特性

引用:

在这种情况下,它 [编译器] 将特征中定义的方法实现和字段声明放到实现特征的类中

在Scala 12和Java 8的上下文中,您可以在提交8020cd6中看到另一个解释:

更好的内联支持2.12特征编码

对性状编码的一些改变在2.12周期后期出现,内联不能以最好的方式支持。

在2.12.0具体特征方法被编码为

 interface T { default int m() { return 1 } static int m$(T $this) { <invokespecial $this.m()> } } class C implements T { public int m() { return Tm$(this) } } 

如果为内联select了特征方法,2.12.0内联将它的主体复制到静态超级访问器Tm$ ,然后从那里进入混合转发器Cm

这个提交特别的例子是内联:

  • 我们不会内联到静态超级访问器和mixin转发器中。
  • 相反,当内联调用一个mixin转发器时,内联器也通过两个转发器跟随,并且内联trait方法体。