2016-05-04 32 views
11

我玩弄泛型和發現,讓我驚訝的是,下面的代碼編譯:爲什麼在推斷數組類型時java類型不安全?

class A {} 
class B extends A {} 

class Generic<T> { 
    private T instance; 
    public Generic(T instance) { 
     this.instance = instance; 
    } 
    public T get(){ return instance; } 
} 

public class Main { 
    public static void main(String[] args) { 
     fArray(new B[1], new Generic<A>(new A())); // <-- No error here 
    } 

    public static <T> void fArray(T[] a, Generic<? extends T> b) { 
     a[0] = b.get(); 
    } 
} 

我希望T被推斷爲BA不延伸B。那爲什麼編譯器不會抱怨呢?

T似乎被推斷爲Object,因爲我也可以通過Generic<Object>

此外,當實際運行的代碼,它將引發對a[0] = b.get();線的ArrayStoreException

我沒有使用任何原始泛型類型。如果T實際上被推斷爲B,我覺得這個異常可以通過編譯時錯誤或者至少一個警告來避免。


當與List<...>等效進一步的測試:

public static void main(String[] args) { 
    fList(new ArrayList<B>(), new Generic<A>(new A())); // <-- Error, as expected 
} 

public static <T> void fList(List<T> a, Generic<? extends T> b) { 
    a.add(b.get()); 
} 

這不會產生與誤差:

The method fList(List<T>, Generic<? extends T>) in the type Main is not applicable for the arguments (ArrayList<B>, Generic<A>) 

一樣更通用的情況下:

public static <T> void fList(List<? extends T> a, Generic<? extends T> b) { 
    a.add(b.get()); // <-- Error here 
} 

的編譯器正確地認識到第一個?可能比第二個?更靠後。

例如如果第一個?B而第二個?A那麼這是不安全的。


那麼,爲什麼第一個例子沒有產生類似的編譯器錯誤呢?這只是一個疏忽嗎?還是有技術限制?

我可能會產生一個錯誤的唯一方法是通過顯式地提供類型:從2005年

Main.<B>fArray(new B[1], new Generic<A>(new A())); // <-- Not applicable 

我並沒有真正找到過我自己的研究任何東西,除了this article(仿製藥之前) ,其中談到陣列協方差的危險。

數組協變似乎暗示了一個解釋,但我想不出一個。


最新的JDK是1.8.0.0_91

+0

我不知道你是否會碰到Java的類型刪除泛型?只是一個想法,我沒有時間看看更接近的ATM。 – Ukko

回答

4

考慮這個例子:

class A {} 
class B extends A {} 

class Generic<T> { 
    private T instance; 
    public Generic(T instance) { 
     this.instance = instance; 
    } 
    public T get(){ return instance; } 
} 

public class Main { 
    public static void main(String[] args) { 
     fArray(new B[1], new Generic<A>(new A())); // <-- No error here 
    } 

    public static <T> void fArray(T[] a, Generic<? extends T> b) { 
     List<T> list = new ArrayList<>(); 
     list.add(a[0]); 
     list.add(b.get()); 
     System.out.println(list); 
    } 
} 

正如你所看到的,用來推斷類型參數的簽名是相同的,那是不同的唯一的事情就是fArray()只讀取數組元素,而不是寫他們,使的T -> A推理在運行時非常合理。

而且也沒有辦法讓編譯器告訴你的數組引用將在方法的實現的用途。

+0

@JornVernee是的,我超過它,但你得到的總體思路。當T - > A滿足所有條件時,編譯器沒有理由選擇'T - > B'。 – biziclop

+0

是的,這很不錯。但重點在於你希望增加額外的類型安全性,因爲''''''必須總是擴展''''''''''''(即數組類型)。就像你做'''A a = new A(); a1 = a;'''而不是'''a = new A(); a2 =(A)a;'''有時候第二個很好(當投射時),但要確保我們使用第一種方式,因爲它具有類型安全性。 –

+0

@JornVernee夠公平的。現在怎麼樣? :) – biziclop

2

I would expect T to be inferred to B. A does not extend B. So why doesn't the compiler complain about it?

T不是推斷爲B,它被推斷爲A。由於B延伸AB[]A[]一個亞型,因此該方法調用是正確的。

與泛型相反,數組的元素類型在運行時可用(它們是使用實例)。所以,當你嘗試做

a[0] = b.get(); 

運行時環境知道a實際上是一個B陣列,並且不能持有A

這裏的問題是Java的動態擴展。 Arrays自Java的第一個版本開始就存在,而泛型僅在Java 1.5中添加。一般來說,Oracle試圖使新的Java版本向後兼容,因此,在較新的版本中(例如,數組協方差)所產生的錯誤不會在新版本中得到糾正。

+0

就像我說:_「'''T'''似乎被推斷爲'''Object''',因爲我可以通過一個'''通用'''爲好。」 _但是你能告訴我_why_'''T'''被推斷爲'''Object'''?或者如果假設'''T'''被推測爲'''B''',那麼你能否告訴我一個失敗案例? –

+0

'T'不是推斷爲'Object',因爲這樣你不就能夠通過一個通用的''你的方法,因爲通用''不是一個子類的通用''。當然,如果你實際上傳遞了「Generic 」而不是「Generic 」,那麼「T」被推斷爲「Object」。 – Hoopje

相關問題