2016-08-24 30 views
1

我对Scalabinary incompatible跨越不同版本的事实感到惊讶。现在,因为在Java 8中我们有默认的方法实现,它与我们提供的trait几乎相同,在Java代码中使用特征是否安全?我试图自己这样使用它:特征和接口是否兼容?

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)的投诉。虽然我在TestTrait中指定了实现。

+0

只有没有具体成员的特征才能与Java互操作。删除你的具体方法的实施,它会阻止工作。 – Samar

回答

6

当Scala 2.11编译器编译一个特征时,它不会生成带默认方法的接口,因为生成的代码必须与Java 6一起工作。在Scala 2.12(它需要Java 8)时,它会用2.12编译器编译你的Scala代码,我希望你能够以这种方式从Java中使用它(至少对于这样的简单情况)。请注意,像这样的变化正是使不同的Scala版本二进制不兼容的原因:如果您尝试使用Scala 2.12中的Scala 2.11编译的特征,它会尝试调用接口的默认方法,这些方法并不存在。

+0

我尝试使用2.12.0-M5的程序使用2.11.4编译的特征。它工作得很好:[链接](http://stackoverflow.com/questions/39139589/why-could-we-use-traits-compiled-with-2-11-from-2-12)。为什么?我使用了两个使用旧版本配置的maven项目。 – user3663882

2

您的预期存在矛盾。

你是“惊讶”地看到,Scala是主要版本之间的二进制兼容,这表明你期望的恰恰相反:是斯卡拉应该是二进制兼容的,甚至主要版本之间。

然而,同时你希望Scala能够使用一种编码来依赖于Scala 2.11二进制格式设计时所不具备的特性。 Java 8发布之前的两周,第一个Scala 2.11的候选版本,即不允许更改的地方。要求每个Scala用户在发布之前安装Java 8将是荒谬的。

所以,一方面,你期望完全的二进制兼容性,即根本没有改变。另一方面,您希望使用最新和最好的功能,即尽可能快地进行更改。你不能拥有两个。你必须选择。

而且,正如阿列克谢已经在他的回答中指出,这是正是改进这个样子,那需要破坏二进制兼容性。

如果您具有二进制兼容性,如果找出更好的二进制表示形式,则无法更改二进制表示形式。当目标平台可用时,您无法利用目标平台的新功能。这是非常严格的,特别是对于像Scala这样的语言来说,它推动了JVM上合理编码的边界。编译器设计人员迫使他们第一次得到“一切正常”将会非常棘手。

这里是已经改变了多年来和破碎的向后兼容性有两件事情:

  • lambda表达式的编码,使用MethodHandle S,当他们在Java 7中,他们不能有加“第一次得到这个权利“,因为当时MethodHandle s甚至不存在。
  • (在即将推出的2.12中).Lamdas的编码,再次,以便它们与Java 8的编码相同。他们不可能“第一次得到这个权利”,因为那时lambda甚至不存在于Java中。
  • (在即将推出的2.12中)。使用default方法在interface s中编码性状。他们不可能“第一次得到这个权利”,因为当时在Java中还没有存在default方法。

如果Java平台得到适当的尾部调用或至少正确的尾递归,我敢肯定,ABI会再次改变以利用这些功能。如果我们在JVM中获得值类型,那么Scala中的值类的编码可能会改变。

然而,在dotc, the compiler for Dotty,球队正在尝试一种新的方法,以二进制兼容性:TASTy。 TASTy是键入抽象语法树的序列化格式。这个想法是保证TASTy的二进制兼容性,但不是最终的输出。 TASTy包含重新编译程序的所有必要信息,因此如果要合并由不同编译器编译的两个闭源库,这不成问题,因为您可以丢弃已编译的代码并从TASTy重新编译。

TASTy将随编译后的代码一起发货。例如。对于Scala-JVM,序列化的TASTy将在.class文件或.jar的元数据部分中提供,对于已编译的源文件中的注释或二进制数组中的Scala.js,编译后的.dll的元数据部分中的Scala本机,.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; } 
} 

但在斯卡拉2.12瞄准的Java 8,它看起来更像是这样的:

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; } 
} 

我不知道为什么没有这样做。乍一看,当前的编码可能会给我们一些前向兼容性:您可以使用由新编译器编译的特性,并使用由旧编译器编译的类,这些旧类将简单地覆盖它们从接口继承的default转发器方法相同的。除此之外,转发方法将尝试调用不再存在的A$classB$class上的静态方法。