2016-03-26 22 views
8
public class Box<T> { 
    private T element; 

    public T getElement() { 
     return element; 
    } 

    public void setElement(T element) { 
     this.element = element; 
    } 
} 

public class Test { 

    public static void main(String[] args) { 
     List<Box> l = new ArrayList<>(); //Just List of Box with no specific type 
     Box<String> box1 = new Box<>(); 
     box1.setElement("aa"); 
     Box<Integer> box2 = new Box<>(); 
     box2.setElement(10); 

     l.add(box1); 
     l.add(box2); 

     //Case 1 
     Box<Integer> b1 = l.get(0); 
     System.out.println(b1.getElement()); //why no error 

     //Case 2 
     Box<String> b2 = l.get(1); 
     System.out.println(b2.getElement()); //throws ClassCastException 

    } 
} 

列表l包含Box類型的元素。在情況1中,我得到第一個元素爲Box<Integer>,第二種情況下,列表中的第二個元素爲Box<String>。 ClassCastException在第一種情況下不引發。Java - 以字符串形式獲取泛型對象泛型類型拋出異常

當我試圖調試時,element's類型b1b2分別StringInteger

它與類型擦除有關嗎?

Ideone link

+0

我的猜測是'b1'在運行時被認爲是'Box ',但這確實是一個奇怪的行爲。 –

回答

4

確切地說,問題是PrintStream#println

讓我們來看看使用javap -c Test.class編譯後的代碼:

72: invokevirtual #12  // Method blub/Box.getElement:()Ljava/lang/Object; 
75: invokevirtual #13  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 

正如你所看到的編譯器擦除的類型,也省略了塑像爲Integer,因爲沒有必要在這裏。編譯器已經將使用過載的methoded鏈接到PrintStream#(Object)。 它,由於JLS rule §5.3

方法調用轉換被施加到在方法或構造調用(§8.8.7.1§15.9§15.12)每個參數值:參數表達式的類型必須被轉換爲相應參數的類型。

方法調用上下文允許使用以下中的一種:

  • 的標識轉換(§5.1.1
  • 加寬原語轉換(§5.1.2
  • 加寬引用轉換(§5.1.5
  • 一個裝箱轉換(§5.1.7),可選地接着加寬參考轉換
  • 開箱轉換(§5.1.8),可選擇後跟一個擴展原始轉換。

第三個規則是從一個子類型的超類型轉化:

拓寬參考轉換從任何參考型S存在於任何引用類型T,提供S是一個子類型(§4.10)T.

和校驗之前完成,如果該類型可以是裝箱(第五檢查:「取消裝箱轉換」)。因此,編譯器檢查IntegerObject的子類型,因此它必須調用#println(Object)(如果您檢查被調用的重載版本,IDE會告訴您相同的內容)。

第二個版本,從另一方面:

95: invokevirtual #12  // Method blub/Box.getElement:()Ljava/lang/Object; 
98: checkcast  #14  // class java/lang/String 
101: invokevirtual #15  // Method java/io/PrintStream.println:(Ljava/lang/String;)V 

checkcast檢查檢索式Box#getElement的真的是String。這是必要的,因爲你告訴編譯器它將是String(由於通用類型Box<String> b2 = l.get(1);)並且它將方法PrintStream#(String)鏈接起來。檢查失敗時提到ClassCastException,因爲Integer不能轉換爲String

1

在編譯時編譯器知道的類型和System.out.println(..)調用鏈接到與正確的參數類型的方法。在第一種情況下,編譯器將呼叫解析爲println(Object)。因爲b1.getElement()返回ObjectStringObject,該方法調用是正確的並不引起異常。在第二種情況下,由於Box<String>,編譯器將呼叫解析爲println(String),但b2.getElement()返回Integer。這不能轉換爲字符串,並引發ClassCastException

+1

所以system.out.println(10)不起作用,在你看來。 –

+0

'b.getElement()'返回一個'Integer'。我認爲你的意思是在調用參數時使用Object的println版本。 – user7

3

好吧,這裏的問題是,B2是錯誤標記爲Box<String>時,它實際上Box<Integer>(BOX2的類型) - 所以b2.getElement()的類型爲字符串,即使它實際上包含一個整數。編譯器試圖調用重載的println方法,該方法接受一個String而不是使用Object的方法,因此您得到一個ClassCastException。對象版本的println會將其參數顯式轉換爲一個String(通過調用toString()),但該方法的String版本不會這樣做。

根本問題是使用原始類型而不是完全指定列表l的類型參數 - 它應該是List<Box<?>>。那麼你應該有b1b2作爲Box並且System.out.println的右邊的重載將被選擇。

+0

因此,當我嘗試調用'b1.getElement()'時,不會發生此問題的原因是帶有Object參數的println被調用? – user7

+0

是的,b1也是不正確的類型,但在運行時它並不重要,因爲它被轉換爲Object。 – sisyphus

0

未定義Integer參數的println方法,因此您的代碼將調用println(Object object),該對象將調用object.toString()以獲取需要打印的字符串。沒有類型檢查,因爲一切都是對象。

在第二種情況下,你的代碼想要調用println(String someString),並且因爲它會檢查someString是否真的是一個String,並且它不是它會拋出一個異常。