我对FileReader
在不同版本中binary incompatible的事实感到惊讶。现在,因为在Scala
中我们有默认方法实现,它与Java 8
几乎相同,为我们提供在Java代码中使用traits是否安全?我自己尝试使用它:
trait
但它拒绝编译。编译器关于不归咎于trait TestTrait {
def method(v : Int)
def concrete(v : Int) = println(v)
}
public class Test implements TestTrait{ // Compile-error. Implement concrete(Int)
@Override
public void method(int v) {
System.out.println(v);
}
}
的投诉。虽然我在concrete(Int)
中指定了实现。
答案 0 :(得分:7)
当Scala 2.11编译器编译一个特征时,它不会生成一个带有默认方法的接口,因为生成的代码必须使用Java 6.在Scala 2.12(需要Java 8)中它会,所以如果你编译您的Scala代码使用2.12编译器,我希望您能够以这种方式从Java中使用它(至少对于像这样的简单情况)。
请注意,像这样的更改正是使得不同的Scala版本二进制文件不兼容的原因:如果您尝试使用Scala 2.12中使用Scala 2.11编译的特征,它将尝试调用接口的默认方法,而不是那里。
答案 1 :(得分:2)
你的期望相互矛盾。
你很惊讶"看看Scala在主要版本之间是二进制不兼容的,这表明你期望相反:Scala 应该即使在主要版本之间也是二进制兼容的。
然而与此同时,你希望Scala使用一种特性编码,这种特性依赖于在设计Scala 2.11的二进制格式时甚至不存在的特性。 Scala 2.11的第一个候选版本,即不再允许更改的点,是Java 8甚至发布前两周。要求每个Scala用户在发布之前安装Java 8都是荒谬的。
因此,一方面,您期望完全二进制兼容性,即根本没有变化。另一方面,您希望使用最新和最好的功能,即尽可能快地更改。你不能兼得。你必须选择。
正如Alexey在他的回答中已经指出的那样,正是这样的改进,要求打破二进制兼容性。
如果您具有二进制兼容性,如果您找到更好的二进制表示,则无法更改二进制表示。当目标平台可用时,您无法使用它们的新功能。这是非常严格的限制,特别是对于像Scala这样的语言,它推动了可以在JVM上合理编码的边界。对于编译器设计者来说,迫使他们做出正确的事情是非常苛刻的。这是第一次。
以下是一些经过多年改变并破坏向后兼容性的事情:
MethodHandle
时使用MethodHandle
进行lambda的编码。他们无法在第一次"得到这个权利,因为那时候{{1}; 1}} s甚至不存在。default
中的interface
方法对特征进行编码。他们第一次无法正确地做到这一点#34;因为当时default
方法甚至不存在于Java中。如果Java平台获得正确的尾部调用或至少正确的尾部递归,我很确定,ABI将再次改变以利用这些功能。如果我们在JVM中获得Value Types,Scala中Value Classes的编码可能会改变。
然而,在dotc
, the compiler for Dotty中,团队正在尝试新的二进制兼容性方法:TASTy。 TASTy是Typed抽象语法树的序列化格式。我们的想法是保证TASTy的二进制兼容性,但不保证最终输出。 TASTy包含重新编译程序所需的所有信息,因此如果要组合由不同编译器编译的两个闭源库,这不是问题,因为您可以丢弃已编译的代码并从TASTy重新编译
TASTy将始终与已编译的代码一起发货。例如。对于Scala-JVM,序列化的TASTy将在.class
文件的元数据部分或.jar
中提供,用于Scala.js,在编译的源文件中的注释或二进制数组中,用于Scala-native在已编译的.dll
,.exe
,.so
,.dylib
等的元数据部分中。
回到关于特征的具体问题:
目前,单个特征编码为:
interface
$this
所以,下面的Scala代码:
trait A {
def foo(i: Int) = i + 1
def abstractBar(i: Int): Int
}
trait B {
def baz(i: Int) = i - 1
}
class C extends A with B {
override def abstractBar(i: Int) = i * i
}
将按如下方式编码:
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 i + 1; }
}
interface B {
int baz(int i);
}
abstract class B$class {
static void $init$(B $this) {}
static int baz(B $this, int i) { return i - 1; }
}
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 i * i; }
}
但是在针对Java 8的Scala 2.12中,它看起来更像是这样:
interface A {
static void $init$(A $this) {}
static int foo$(A $this, int i) { return i + 1; }
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 i - 1; }
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 i * i; }
}
正如您所看到的,保留了使用静态方法和转发器的旧设计,它们只是折叠到界面中。特征的具体方法现在已经作为static
方法移入接口本身,转发器方法在每个类中都没有合成,但是一次定义为default
方法,静态方法$init$
方法(表示特征体中的代码)也被移动到接口中,使得伴随静态类变得不必要。
它可能会像这样简化:
interface A {
static void $init$(A $this) {}
default int foo(int i) { return i + 1; };
int abstractBar(int i);
}
interface B {
static void $init$(B $this) {}
default int baz(int i) { return i - 1; }
}
class C implements A, B {
public C() {
A.$init$(this);
B.$init$(this);
}
@Override public int abstractBar(int i) { return i * i; }
}
我不确定为什么没有这样做。乍一看,当前编码可能会给我们一些前向兼容性:您可以使用由旧编译器编译的新编译器编译的traits,这些旧类将简单地覆盖它们继承的default
转发器方法从相同的界面。除此之外,转发器方法将尝试调用不再存在的A$class
和B$class
上的静态方法。