特征和接口二进制兼容吗?

时间:2016-08-24 09:28:55

标签: java scala interface traits

我对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)中指定了实现。

2 个答案:

答案 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上合理编码的边界。对于编译器设计者来说,迫使他们做出正确的事情是非常苛刻的。这是第一次。

以下是一些经过多年改变并破坏向后兼容性的事情:

  • 在Java 7中使用MethodHandle时使用MethodHandle进行lambda的编码。他们无法在第一次"得到这个权利,因为那时候{{1}; 1}} s甚至不存在。
  • (在即将发布的2.12中。)lambda的编码,再次,以便它们与Java 8的编码相同。他们第一次没有得到这个权利",因为那时lambdas甚至不存在于Java中。
  • (在即将发布的2.12中。)使用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$classB$class上的静态方法。