Scala traits如何编译成Java字节码?

Jus*_*ini 45 scala bytecode

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

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

是否有一个很好的来源解释traits如何编译为Java字节码?

Mit*_*ins 66

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

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

trait Foo {
  def bar = { println("bar!") }
}
Run Code Online (Sandbox Code Playgroud)

变得相当于......

public interface Foo {
  public void bar();
}

public class Foo$class {
  public static void bar(Foo self) { println("bar!"); }
}
Run Code Online (Sandbox Code Playgroud)

这留下了一个问题:如何调用Foo $类中的静态bar方法?这个魔法是由Foo特征混入的类中的编译器完成的.

class Baz extends Foo
Run Code Online (Sandbox Code Playgroud)

变得像......

public class Baz implements Foo {
  public void bar() { Foo$class.bar(this); }
}
Run Code Online (Sandbox Code Playgroud)

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


Jör*_*tag 5

为了便于讨论,让我们看看下面的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) = ???
}
Run Code Online (Sandbox Code Playgroud)

目前(即Scala 2.11),单个特征编码为:

  • 一个interface包含抽象声明所有性状的方法(包括抽象和具体)
  • 一个抽象的静态类,包含所有特征的具体方法的静态方法,采用一个额外的参数$this(在旧版本的Scala中,这个类不是抽象的,但实例化它没有意义)
  • 在混合特征的继承层次结构中的每个点,特征中所有具体方法的合成转发器方法转发给静态类的静态方法

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

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 ???; }
}
Run Code Online (Sandbox Code Playgroud)

但是,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 ???; }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,保留了静态方法和转发器的旧设计,它们只是折叠到界面中.特征的具体方法现在已作为static方法移入接口本身,转发器方法不是在每个类中合成,而是作为default方法定义一次,静态$init$方法(表示特征体中的代码)已移入界面也是如此,使伴侣静态类不必要.

它可能会像这样简化:

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 ???; }
}
Run Code Online (Sandbox Code Playgroud)

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