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

Java 特征和接口是二进制兼容的吗?,java,scala,interface,traits,Java,Scala,Interface,Traits,我只是有点惊讶于,Scala跨越了不同的版本。现在,因为在Java8中,我们有默认的方法实现,这与trait提供给我们的在Java代码中使用trait安全吗?我试着自己使用它,因为: trait TestTrait { def method(v : Int) def concrete(v : Int) = println(v) } public class Test implements TestTrait{ // Compile-error. Implement concrete(I

我只是有点惊讶于,
Scala
跨越了不同的版本。现在,因为在
Java8
中,我们有默认的方法实现,这与
trait
提供给我们的在Java代码中使用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)
。尽管在Scala 2.11编译器编译trait时,我在
TestTrait
中指定了实现,但它不会使用默认方法生成接口,因为生成的代码必须与Java 6一起使用。在Scala2.12(需要Java8)中,它会,所以如果您使用2.12编译器编译Scala代码,我希望您能够以这种方式从Java使用它(至少对于这样一个简单的情况)


请注意,这样的更改正是使不同的Scala版本二进制不兼容的原因:如果您尝试使用Scala 2.12中使用Scala 2.11编译的特性,它将尝试调用接口的默认方法,而这些方法并不存在

Scala 2.11编译器编译trait时,不会生成带有默认方法的接口,因为生成的代码必须使用Java 6。在Scala2.12(需要Java8)中,它会,所以如果您使用2.12编译器编译Scala代码,我希望您能够以这种方式从Java使用它(至少对于这样一个简单的情况)


请注意,这样的更改正是使不同的Scala版本二进制不兼容的原因:如果您尝试使用Scala 2.12中使用Scala 2.11编译的特性,它将尝试调用接口的默认方法,而这些方法并不存在

您的期望值相互矛盾

您会“惊讶”地看到Scala在主要版本之间是二进制不兼容的,这表明您的预期正好相反:即使在主要版本之间,Scala也应该是二进制兼容的

但同时,你也希望Scala使用一种特征编码,这种特征依赖于Scala 2.11二进制格式设计时甚至不存在的特性。Scala 2.11的第一个候选版本,也就是说,在Java 8发布之前的两周,不允许再进行任何更改。要求每个Scala用户在Java8发布之前就安装它是荒谬的

因此,一方面,您期望完全的二进制兼容性,即完全没有更改。另一方面,您希望使用最新和最强大的功能,即尽快进行更改。你不能两者兼得。你必须选择

而且,正如Alexey在他的回答中指出的,正是这样的改进需要打破二进制兼容性

如果你有二进制兼容性,你不能改变你的二进制表示,如果你找到一个更好的。而且,当目标平台的新功能可用时,您无法使用它们。这是非常严格的,特别是对于Scala这样的语言,它突破了JVM上可以合理编码的界限。如果编译器设计者第一次就强迫他们“一切正常”,那将是非常苛刻的

以下是几年来发生了变化并破坏了向后兼容性的几件事情:

  • 在Java7中添加lambda时,使用
    MethodHandle
    s对lambda进行编码。他们不可能“第一次就做到了”,因为那时
    MethodHandle
    s根本不存在
  • (在接下来的2.12中)lambdas的编码,同样,它们与Java8的编码相同。他们不可能“第一次就做到了”,因为那时lambdas甚至不存在于Java中
  • (在接下来的2.12节中)使用
    接口
    s中的
    默认
    方法对性状进行编码。他们不可能“第一次就做到这一点”,因为当时Java中甚至不存在
    default
    方法
如果Java平台曾经得到正确的尾部调用或至少是正确的尾部递归,我很确定,ABI将再次改变以利用这些特性。如果我们在JVM中获得值类型,Scala中值类的编码可能会改变

然而,在中,团队正在尝试一种新的二进制兼容性方法:。TASTy是类型化抽象语法树的序列化格式。其思想是保证二进制兼容性是为了美味,但不是为了最终输出。TASTy包含重新编译程序所需的所有信息,因此,如果您想要合并由不同编译器编译的两个封闭源代码库,这不是问题,因为您可以扔掉已编译的代码,从TASTy重新编译

TASTy将始终与编译后的代码一起提供。例如,对于Scala JVM,序列化的TASTy将在
.class
文件或
.jar
的元数据部分中提供,对于Scala.js,将在编译的源文件中的注释或二进制数组中提供,对于Scala,将在编译的
.dll
.exe
.so
.dylib
的元数据部分提供,等等

回到你关于特质的具体问题:

目前,单个特征编码为:

  • 包含所有trait方法(抽象和具体)的抽象声明的
    接口
  • 一个静态类,包含所有traits的具体方法的静态方法,使用一个额外的参数
    $this
  • 在继承层次结构中混合trait的每一点上,trait中所有具体方法的合成转发器方法将转发到静态类的静态方法
下面是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
}
将按如下方式编码:

接口A{
int foo(int i);
intAbstractBar(intI);
}
A.