【问题标题】:How are Scala traits compiled into Java bytecode?Scala 特征是如何编译成 Java 字节码的?
【发布时间】:2011-02-03 04:17:33
【问题描述】:

我已经玩过 Scala 一段时间了,我知道特质可以充当接口和抽象类的 Scala 等价物。特征究竟是如何编译成 Java 字节码的?

我发现了一些简短的解释,说明特征尽可能像 Java 接口一样编译,否则与附加类进行接口。但是,我仍然不明白 Scala 是如何实现类线性化的,这是 Java 中没有的功能。

有没有很好的资料可以解释特征如何编译成 Java 字节码?

【问题讨论】:

标签: scala bytecode


【解决方案1】:

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

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

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 中的静态 bar 方法是如何被调用的?这个魔法是由编译器在 Foo 特征被混合到的类中完成的。

class Baz extends Foo

变成...

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

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

【讨论】:

  • 谢谢!最后 2 位是我不清楚的地方。很好的解释。
  • 我冒昧地修复了 scala resp java 代码的语法高亮,这个功能在这个答案发布时甚至可能不可用。
  • 我认为最近的 Scala (2.11) 生成 Foo$class 作为抽象,即:public abstarct class Foo$class
【解决方案2】:

为了便于讨论,让我们看一下下面的 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 中,这个类不是抽象的,但实例化它没有意义)
  • 在继承层次结构中混入特征的每个点,特征中所有具体方法的合成转发器方法转发到静态类的静态方法

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

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 ???; }
}

如您所见,保留了带有静态方法和转发器的旧设计,它们只是折叠到界面中。 trait 的具体方法现在已作为static 方法移入接口本身,转发器方法不是在每个类中合成,而是定义一次为default 方法,以及静态$init$ 方法(表示trait body) 也被移到了接口中,因此不需要附带的静态类。

大概可以这样简化:

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 ???; }
}

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

【讨论】:

  • 由于abstract override,无法按照您建议的方式完成。给定abstract class A { def x: Int }; trait T { override def x = 2 }; trait S { override abstract def x = super.x * 2 },如果不将代码从S 复制到B 并破坏前向兼容,您将如何实现class B extends A with T with S?静态方法引用相同的实现,忽略虚拟调度,这就是我们需要的。目前它们只是绕过 virt 调度的invokespecial 指令,但可能需要更改,这允许它。
【解决方案3】:

一个很好的解释是:

The busy Java developer's guide to Scala: Of traits and behaviors - Traits in the JVM

引用:

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

【讨论】:

    【解决方案4】:

    在Scala 12和Java 8的上下文中,你可以在commit 8020cd6看到另一种解释:

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

    特征编码的一些变化出现在 2.12 周期的后期,而 inliner 没有适应以最好的方式支持它。

    在 2.12.0 中,具体的 trait 方法被编码为

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

    如果选择一个 trait 方法进行内联,2.12.0 内联会 将其主体复制到静态超级访问器T.m$,然后从那里复制到 mixin 转发器C.m

    这个提交是内联的特殊情况:

    • 我们不会内联静态超级访问器和 mixin 转发器。
    • 相反,当内联对 mixin 转发器的调用时,内联器还会通过两个转发器并内联 trait 方法体。

    【讨论】:

      猜你喜欢
      • 2018-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多