在将具有默认实现的方法添加到特征时,我对向后兼容性感到困惑。喜欢:
以前的版本
trait Foo
新版本
trait Foo {
def verifyConsistency: Option[String] = ??? // provide default implementation
}
迁移管理器将此添加报告为二进制不兼容。那是对的吗?
在将具有默认实现的方法添加到特征时,我对向后兼容性感到困惑。喜欢:
以前的版本
trait Foo
新版本
trait Foo {
def verifyConsistency: Option[String] = ??? // provide default implementation
}
迁移管理器将此添加报告为二进制不兼容。那是对的吗?
嗯,是的,它是正确的。
当您定义 traitFoo
时,它将在后台创建一个(JVM)接口Foo
和一个(JVM)类Foo$class
,其中所有方法实现都定义为静态方法。相应的 java 代码看起来像这样(对于您的新定义Foo
):
interface Foo {
Option<String> verifyConsistency();
}
class Foo$class {
static Option<String> verifyConsistency(Foo self) {
Predef.???();
}
}
当您混入Foo
一个具体的类Bar
时,在 JVM 级别发生的事情是Bar
扩展接口Foo
,并且它verifyConsistency
通过简单地将调用转发到以下位置来实现方法Foo$class
:
class Bar implements Foo {
Option<String> verifyConsistency() {
return Foo$class.verifyConsistency(this); // simple forwarding
}
}
之所以这样做,是因为JVM对象模型不支持多重继承。不能简单地将特征实现放在要扩展的类中,因为您只能在 JVM 上扩展单个类。
这种情况的好处是,每次具体类混合一个特征时,该类都会为特征的每个成员定义“存根”方法(这些方法只是转发到实际实现,这是一个静态方法)。
一个后果是,如果您向 trait 添加一个新方法,即使您定义了一个实现也不够:需要重新编译混合该 trait 的具体类(以便将新方法的存根添加到类中) . 如果您不重新编译这些类,您的程序将无法运行,因为您现在将拥有一个假定为具体(非抽象)并扩展相应接口但实际上错过了新方法的实现的类。
在您的情况下,这意味着拥有扩展接口Foo
但没有任何实现的具体类verifyConsistency
。
因此二进制不兼容。