我已经玩过Scala一段时间了,我知道traits可以作为Scala的接口和抽象类的等效物。那么traits到底是如何编译成Java字节码的呢?
我找到了一些简短的解释,它们指出当可能时,traits会被编译成与Java接口完全相同的形式,否则会编译成包含另一个类的接口。然而,我仍然不明白Scala如何实现类线性化,这是Java没有的特性。
是否有好的资源可以解释traits如何编译成Java字节码?
我不是专家,但这是我的理解:
Traits会被编译成对应的接口和类。
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中的静态条形方法是如何被调用的?这个魔法是由编译器在混入Foo特质的类中完成的。
class Baz extends Foo
变成像这样...
public class Baz implements Foo {
public void bar() { Foo$class.bar(this); }
}
类的线性化只是根据语言规范中定义的线性化规则实现方法(在Xxxx$class类中调用静态方法)的适当版本。
Foo$class
生成为抽象类,即:public abstract class Foo$class
。 - David Sorokotrait 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开始),单个trait被编码为:
interface
$this
(在早期版本的Scala中,这个类不是抽象的,但实例化它没有意义)这种编码的主要优点是,没有具体成员的trait(与接口同构)实际上被编译为接口。
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 ???; }
}
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 ???; }
}
default
转发方法。但是,转发方法将尝试调用A$class
和B$class
上的静态方法,而这些方法已经不存在了,因此这种假想的向前兼容性实际上并不起作用。在2.12周期的后期进行了一些特质编码的更改,但内联器并没有以最佳方式支持它。
在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) }
}
javap
命令一样,您需要添加-c
标志才能使其输出字节码。否则它只会显示方法定义的摘要。或者使用-v
获取更多信息。 - Nick