为了便于讨论,让我们看一下下面的 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$class 和 B$class 上的静态方法,因此假设的转发兼容性实际上不起作用。