代码之家  ›  专栏  ›  技术社区  ›  Justin Ardini

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

  •  44
  • Justin Ardini  · 技术社区  · 15 年前

    我已经和scala玩了一段时间了,我知道特性可以作为界面和抽象类的scala等价物。如何将特性编译成Java字节码?

    我发现了一些简短的解释,说明在可能的情况下,编写的特性与Java接口完全相同,并且与另外一个类接口。然而,我仍然不理解斯卡拉是如何实现类线性化的,这是Java中没有的一个特性。

    有没有一个很好的解释如何将特性编译成Java字节码?

    4 回复  |  直到 8 年前
        1
  •  65
  •   skiwi    11 年前

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

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

    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
  •  5
  •   Jörg W Mittag    8 年前

    为了便于讨论,让我们看看下面的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 包含的抽象声明 全部的 特征的方法(抽象的和具体的)
    • 一个抽象静态类,包含所有特性的具体方法的静态方法,并使用一个额外的参数 $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 ???; }
    }
    

    然而,斯卡拉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 ???; }
    }
    

    如您所见,带有静态方法和转发器的旧设计已经保留,它们只是折叠到接口中。特性的具体方法现在已经作为 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 ???; }
    }
    

    我不知道为什么不这样做。乍一看,当前的编码可能会给我们带来一些前向兼容性:您可以使用用新编译器编译的特性和用旧编译器编译的类,这些旧类只会覆盖 违约 它们从接口继承的转发器方法具有相同的方法。但是,转发器方法将尝试调用静态方法 A$class B$class 它已经不存在了,所以假设的远期兼容性实际上不起作用。

        3
  •  1
  •   user1338062    12 年前

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

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

    报价:

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

        4
  •  0
  •   VonC    8 年前

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

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

    特性编码的一些变化在2.12周期后期出现,并且 Inliner不适合以最佳方式支持它。

    在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 T.m$(this) }
    }
    

    如果为内联选择了特征方法,则2.12.0内联将 将其主体复制到静态超级访问器中 T.m$ 从那里到 混音转发器 C.m .

    这将提交特殊情况的inliner:

    • 我们不内联到静态超级访问器和混合转发器中。
    • 相反,当内联一个mixin转发器的调用时,inliner还通过两个转发器跟踪,并内联特征方法体。