我已经玩了Scala一段时间了,我知道traits可以作为Scala等同于接口和抽象类。如何将特征编译成Java字节码?
我找到了一些简短的解释,说明特征在可能的情况下编译完全类似于Java接口,否则与其他类接口。但是,我仍然不明白Scala如何实现类线性化,这是Java中没有的功能。
有没有一个很好的资料来解释traits如何编译成Java字节码?
答案 0 :(得分:66)
我不是专家,但这是我的理解:
将特征编译成一个接口和相应的类。
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 $类中的静态bar方法?这种魔力是由Foo特征混合的类中的编译器完成的。
class Baz extends Foo
变得类似......
public class Baz implements Foo {
public void bar() { Foo$class.bar(this); }
}
类线性化只是根据语言规范中定义的线性化规则实现了方法的适当版本(在Xxxx $ class类中调用静态方法)。
答案 1 :(得分: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) = ???
}
目前(即Scala 2.11),单个特征编码为:
interface
$this
(在旧版本的Scala中,这个类不是抽象的,但它没有#&# 39;有意义地实例化它)这种编码的主要优点是没有具体成员(与接口同构)的特性 被编译到接口。
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 ???; }
}
正如您所看到的,保留了使用静态方法和转发器的旧设计,它们只是折叠到界面中。特征的具体方法现在已经作为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 ???; }
}
我不确定为什么没有这样做。乍一看,当前编码可能会给我们一些前向兼容性:您可以使用由旧编译器编译的新编译器编译的traits,这些旧类将简单地覆盖它们继承的default
转发器方法从相同的界面。除此之外,转发器方法将尝试调用不再存在的A$class
和B$class
上的静态方法,因此假设的向前兼容性实际上并不起作用。
答案 2 :(得分:1)
对此的一个非常好的解释是:
The busy Java developer's guide to Scala: Of traits and behaviors - Traits in the JVM
引用:
在这种情况下, [编译器] 将特征中定义的方法实现和字段声明放入实现特征的类
答案 3 :(得分:1)
在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 inliner会 将其主体复制到静态超级访问器
T.m$
,并从那里进入 mixin转发器C.m
。此提交特殊情况内联:
- 我们不会内联到静态超级访问者和mixin转发器。
- 相反,在内联调用mixin转发器时,内联器也会跟踪这两个转发器并内联特征方法体。