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

Scala特性是如何编译成Java字节码的?,scala,bytecode,Scala,Bytecode,我已经使用Scala一段时间了,我知道traits可以充当接口和抽象类的Scala等价物。trait是如何编译成Java字节码的 我发现了一些简短的解释,说明了在可能的情况下,所述特征的编译与Java接口完全相同,而在其他情况下,则与附加类的接口完全相同。然而,我仍然不明白Scala是如何实现类线性化的,这是Java中不可用的特性 有没有好的资料可以解释traits是如何编译成Java字节码的?我不是专家,但我的理解是: Traits被编译成一个接口和相应的类 特色食品{ def bar={pr

我已经使用Scala一段时间了,我知道traits可以充当接口和抽象类的Scala等价物。trait是如何编译成Java字节码的

我发现了一些简短的解释,说明了在可能的情况下,所述特征的编译与Java接口完全相同,而在其他情况下,则与附加类的接口完全相同。然而,我仍然不明白Scala是如何实现类线性化的,这是Java中不可用的特性


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

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

Traits被编译成一个接口和相应的类

特色食品{ def bar={printlnbar!} } 成为…的等价物

公共接口{ 公共禁区; } 公共类Foo$类{ 公共静态void barFoo self{printlnbar!;} } 这就留下了一个问题:Foo$类中的静态bar方法是如何被调用的?这个魔术是由Foo特性混合到的类中的编译器完成的

类Baz扩展了Foo 变得有点像

公共类Baz实现了Foo{ 公共无效条{Foo$class.barthis;} }
根据语言规范中定义的线性化规则,类线性化只实现调用Xxxx$Class类中静态方法的方法的适当版本。

对此的一个很好的解释如下:

引述:

在本例中,它[编译器]将trait中定义的方法实现和字段声明放到实现trait的类中


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

特征A{ def fooi:Int=??? def abstractBari:Int:Int } 性状B{ def bazi:Int=??? } C类用B扩展A{ 覆盖def abstractBari:Int=??? } 目前,即从Scala 2.11开始,单个性状编码为:

包含trait的所有抽象和具体方法的抽象声明的接口 一个抽象静态类,包含trait的所有具体方法的静态方法,并带有一个额外的参数$this在Scala的旧版本中,这个类不是抽象的,但是实例化它是没有意义的 在继承层次结构中混合trait的每一点上,trait中所有具体方法的合成转发器方法将转发到静态类的静态方法 这种编码的主要优点是,没有与接口同构的具体成员的特征实际上被编译为接口

接口A{ int fooint i; 国际抽象巴林特一号; } 抽象类A$类{ 静态void$init$A$this{} 静态int fooA$this,int i{return???;} } 接口B{ int bazint i; } 抽象类B$类{ 静态void$init$B$this{} 静态int bazB$this,int i{return???;} } 类C实现了A、B{ 公共C{ $class.$init$this; B$class.$init$this; } @重写公共int bazint i{返回B$class.bazithis,i;} @重写公共int fooint i{返回$class.foothis,i;} @重写公共int abstractBarint i{return???;} } 然而,Scala 2.12需要Java 8,因此能够在接口中使用默认方法和静态方法,结果看起来更像这样:

接口A{ 静态void$init$A$this{} 静态int foo$A$this,int i{return???;} 默认int fooint i{返回A.foo$this,i;}; 国际抽象巴林特一号; } 接口B{ 静态void$init$B$this{} 静态int baz$B$this,int i{return???;} 默认int bazint i{返回B.baz$this,i;} } 类C实现了A、B{ 公共C{ A.$init$this; B.$init$this; } @重写公共int abstractBarint i{return???;} } 如您所见,使用静态方法和转发器的旧设计已经保留,它们只是被折叠到界面中。trait的具体方法现在已经作为静态方法移动到接口本身中,转发器方法不是在每个类中合成的,而是作为默认方法定义一次,表示trait主体中代码的静态$init$方法也被移动到接口中,使伴生静态类变得不必要

可以这样简化:

接口A{ 静态void$init$A$this{} 默认int fooint i{return???;}; 国际抽象巴林特一号; } 接口B{ 静态void$init$B$this{} 默认int bazint i{return???;} } 类C实现了A、B{ 公共C{ A.$init$this; B.$init$this; } @重写公共int abstractBarint i{return???;} } 我不知道为什么没有这样做。乍一看,当前的编码可能会给我们带来一些向前兼容性:您可以使用traits compi
由一个新的编译器和一个旧编译器编译的类组成,这些旧类将简单地用相同的方法覆盖它们从接口继承的默认转发器方法。除此之外,转发器方法将尝试调用不再存在的$class和B$class上的静态方法,因此假设的转发兼容性实际上不起作用。

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

更好的内联支持2.12特征编码 特质编码的一些变化在2.12周期的后期出现,而 inliner不适合以最佳方式支持它

在2.12.0中,具体特征方法编码为

如果为内联选择了trait方法,则2.12.0内联将 将其主体复制到静态超级访问器T.m$,然后从那里复制到 mixin转运公司C.m

这将提交内联线的特殊情况:

我们不嵌入静态超级访问器和混合转发器。 相反,当内联mixin转发器的调用时,内联器也会跟随两个转发器,并内联trait方法体。
谢谢最后两位是我不清楚的地方。很好的解释。我自由地修复了scala resp java代码的语法突出显示,这项功能在发布回答时可能还不可用。我认为最近的scala 2.11将Foo$类生成为抽象类,即:public abstract class Foo$class我真的不想在这里被翻转,但请尝试使用类文件反汇编程序。要获得更高层次的概述,请参阅博文2009-02-09。这提供了更高层次的概述:我还没有尝试使用javap。我很欣赏这个链接,我希望得到更多的细节,但这是一个很好的起点。请注意,与javap命令一样,您需要添加-c标志以使其输出字节码。否则,它只显示方法定义的摘要。或者使用-v来获取更多信息。这里可能有一些好的答案,因为抽象覆盖,所以无法按照您建议的方式完成。给定抽象类A{defx:Int};性状T{override def x=2};trait S{override abstract def x=super.x*2},在不将代码从S复制到B并中断前向兼容的情况下,如何实现类B用T和S扩展A?静态方法引用相同的实现,忽略虚拟分派,这就是我们需要的。目前,它们都是一条invokespecial指令,绕过virt分派,但这可能需要更改,这允许它进行修改。
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) }
}