2016-01-26 15 views
7

我在回顧Java泛型上的一個Oracle路徑,標題爲「Effects of Type Erasure and Bridge Methods」,並且我無法說服自己給出解釋。好奇的是,我在本地測試了這些代碼,甚至無法重現線索解釋的行爲。下面是相關代碼:甲骨文在Java泛型方面的一個潛在問題

public class Node<T> { 
    public T data; 

    public Node(T data) { this.data = data; } 

    public void setData(T data) { 
     System.out.println("Node.setData"); 
     this.data = data; 
    } 
} 

public class MyNode extends Node<Integer> { 
    public MyNode(Integer data) { super(data); } 

    public void setData(Integer data) { 
     System.out.println("MyNode.setData"); 
     super.setData(data); 
    } 
} 

甲骨文線索稱該代碼段以下行爲:

MyNode mn = new MyNode(5); 
Node n = mn;   // A raw type - compiler throws an unchecked warning 
n.setData("Hello");  
Integer x = mn.data; // Causes a ClassCastException to be thrown. 

這段代碼看起來應該像類型擦除後執行以下操作:

MyNode mn = new MyNode(5); 
Node n = (MyNode)mn;   // A raw type - compiler throws an unchecked warning 
n.setData("Hello"); 
Integer x = (String)mn.data; // Causes a ClassCastException to be thrown. 

我不明白在這裏使用的演員或行爲。當我試圖運行此代碼在本地使用的IntelliJ的Java 7,我得到了這種行爲:

MyNode mn = new MyNode(5); 
Node n = mn;   // A raw type - compiler throws an unchecked warning 
n.setData("Hello");  // Causes a ClassCastException to be thrown. 
Integer x = mn.data; 

換句話說,JVM將不允許StringsetData()使用。這對我來說實際上很直觀,並且與我對泛型的理解一致。由於MyNodemnInteger構建,因此編譯器應將每次調用setData()Integer一起投射,以確保類型安全(即傳入Integer)。

有人可以在Oracle蹤跡中的這個明顯的錯誤?

回答

3

您誤解了Oracle頁面。如果你一直讀到最後,你會發現它說明發生了什麼是你描述的。

這不是一個寫得很好的頁面;作者說,當他們的意思是「如果有這種情況發生了等等,但是我們認爲那不是這種情況」,那麼「等等發生了」。他們的語言過於寬鬆。

頁面 - 橋接方法 - 的要點是解釋當您預測的行爲(基於泛型設計+實現)是他們在開始時「建議」時的真實行爲。

+0

我覺得說的頁面是它的語言鬆散的是給它太多的功勞。所以你所說的是,調用'n.setData(「Hello」)實際上是觸發編譯器生成的橋接方法,在調用super方法之前,它會將傳遞給它的每個對象都轉換爲'Integer'。那麼這解釋了我所想的一切,謝謝你的見解。 –

+1

我傾向於嚴厲批評不良文檔,但我已經習慣於發佈可怕文檔的軍團,尤其是對於API。具有諷刺意味的是,Java在年輕時擁有一些最好的文檔。現在......我試圖讓自己對自己的輕蔑:)。 – Adam

1

好吧,這是在路徑中解釋。

理論,當類Node被編譯,它的基類型T被擦除到Object

因此,在現實中,它被編譯成類似

class Node { 
    public Object data; 

    public Node(Object data) {this.data = data; } 

    public void setData(Object data) { 
     System.out.println("Node.setData"); 
     this.data = data; 
    } 
} 

然後創建一個子類MyNode,有它自己的setData(Integer data)。就Java而言,這是setData方法的過載,而不是覆蓋它。每個MyNode對象有兩個setData方法。一個是setData(Object)它繼承自Node,另一個是setData(Integer)。因此,基本上,如果您使用原始類型,並且您撥打setData,並且其中任何引用不是Integer,那麼Java對此的正常解釋就是調用過載setData(Object)

這不會導致轉讓問題,因爲data被宣佈爲Object,而不是Integer。只有當您嘗試將數據分配回Integer參考時,問題纔會發生。 Java的這種簡單的行爲會導致MyNode對象被不適當的數據「污染」。

但是,正如線索所言,編譯器添加了一個「橋接」方法,使得子類的行爲更像您直覺地認爲它的方式。它增加了一個覆蓋setData(Object)MyNode,以便您不能調用原始的,非安全的Node.setData(Object)。在這種壓倒一切的橋接方法中,有一個明確的轉換爲Integer,可確保您無法將非整數引用分配給data

這就是您在實際編譯和運行示例時看到的行爲。

如果您在MyNode.class文件運行javap -p,你會看到,事實上,它有兩個setData方法:

class MyNode extends Node<java.lang.Integer> { 
    public MyNode(java.lang.Integer); 
    public void setData(java.lang.Integer); 
    public void setData(java.lang.Object); 
}