2016-08-24 88 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上的靜態方法。