2011-09-28 50 views
7

這是我之前的問題的後續,但由於前一個線程很長,我決定啓動另一個線程,涉及幾乎相同的主題。Java泛型:關於類型捕獲和使用泛型方法生成推理的問題

public class GenericMethodInference { 

static <T> void test1(T t1, T t2) {} 
static <T> void test3(T t1, List <T> t2) {} 
static <T> void test4(List <T> t1, List <T> t2) {} 

public static void main(String [] args) { 

    List <Object> c = new LinkedList<Object>(); 
    List <? extends Object> d = new ArrayList<Integer>(); 
    List e = new ArrayList<Integer>(); 

    test1("Hello", new Integer(1)); // ok clause (1) 
    GenericMethodInference.<Object>test1("Hello", new Integer(1)); // ok clause (2) 
    test3("Hello", c); // ok clause (3) 
    test4(d,d) // clause (4) Error due to different type capture generated 

} 

注意:如果您將光標移到每一個條款,你會看到正在生成的推理和基於Eclipse顯示:

一個。條款(1)將產生<?擴展對象> test1 <?擴展Object,?擴展對象>
b。條款(2)將產生確切的實際類型參數
c中定義的內容。第(3)會產生<對象> TEST3 <對象,列出<對象>>

問題:

  1. 爲什麼第(1)沒有產生<對象>?由於< Object>的工作原理如條款(2)所示,爲什麼<?擴展Object>而不是生產?
  2. 爲什麼從句(3)產生< Object>而不是<?擴展對象>?
  3. 由於條款(4)使用相同的變量,爲什麼2個不同類型的捕獲生成事件,儘管使用的參數是相同的變量d?
+0

「如果將光標移動到每個子句上」 - 哪個IDE請好? (更新:感謝編輯) –

+0

Eclipse,他說;) –

+0

什麼是第4條? –

回答

4

爲什麼從句(1)沒有產生< Object>?由於< Object>的工作原理如條款(2)所示,爲什麼<?擴展Object>而不是生產?

這是三者中最好的問題。我的想法是,編譯器/ Eclipse不想假設Object必然是在StringInteger之間推斷的類型T,所以它起到安全作用。如@bringer128指出的那樣,StringInteger也都實現了SerializableComparable - 因此這些類型也是該方法的推斷類型的候選。

值得一提的是,下面的代碼給出了編譯器錯誤「式的非法啓動」:

GenericMethodInference.<? extends Object>test1("Hello", new Integer(1)); 

這是因爲它是無效的指定通配符作爲方法的類型參數。因此,您在工具提示中看到的事實與編譯器/ Eclipse的工具的微妙之處有關,以報告此信息 - 它僅確定T處於其範圍內,而不是它所處的範圍。請記住,Java的泛型實現僅僅是爲了程序員的方便/完整性。一旦編譯成字節碼,type erasure將會清除T的任何概念。因此,在檢查時,編譯器只需確保可以推斷出a有效的T,但不一定是它是什麼。


爲什麼條款(3)產生<對象>,而不是<?擴展對象>?

因爲在這種情況下,一個List<Object>在一個List<T>預計傳遞的事實告訴編譯器T正是Object


由於條款(4)使用相同的變量,爲什麼eventhough所使用的參數而生成2種不同類型的捕獲是相同的變量d?

編譯器認爲d實際上是指同一個對象,即使在評估參數之間也是不安全的。例如:

test4(d,(d = new ArrayList<String>())); 

在這種情況下,一個List<Integer>將被傳遞到第一參數,和一個List<String>到第二 - 無論從d。由於這種情況是可能的,所以編譯器更容易安全地玩。

+1

僅供參考對於第1章,它們具有通用的父接口Serializable和Comparable。編譯器總是會嘗試推斷他們可以找到的最具體的接口,並且如果能夠幫助它的話,不喜歡解析Object。 – Bringer128

+0

@ Bringer128 - 偉大的一點,我會補充說我的答案。 –

+1

呵呵,我在IntelliJ中測試了這個方法,重新定義方法爲'static List test1(T t1,T t2){return null;}'然後用IDE生成一個變量(Ctrl + Alt + v)關於方法的返回值。我認爲Netbeans可以做類似的事情。 – Bringer128

2

test1()的情況實際上是相當險惡的。參見JLS3 15.12.2.7。

我們不應該知道類型推斷的細節 - 在大多數情況下,直覺與算法一致。唉,情況並非總是如此,就像看似簡單的test1()例子一樣。

我們有約束是T :> StringT :> Integer( 「:>」 是指超類型)

這導致T=lub(String,Integer)lub指 「最小上界」。

由於String <: Comparable<String>Integer <: Comparable<Integer>,這導致lci({Comparable<String>, Comparable<Integer>}),這將產生Comparable<? extends lub(String,Integer)>,即Compable<? extends T>

最後,我們有T = Serializable & Compable<? extends T>,自引用定義! Spec稱之爲「無限類型」:

上述過程有可能產生無限類型。這是允許的,Java編譯器必須識別這些情況並使用循環數據結構適當地表示它們。

讓我們來看看如何javac的代表它:(javac的7)

static <T> T test1(T t1, T t2) {} 

public static void main(String[] args) 
{ 
    Void x = test1("Hello", new Integer(1)); 
} 

error: incompatible types 
required: Void 
found: INT#1 
where INT#1,INT#2 are intersection types: 
INT#1 extends Object,Serializable,Comparable<? extends INT#2> 
INT#2 extends Object,Serializable,Comparable<?> 

不似乎是正確的;它不是真正的遞歸;看起來javac在lub()中檢測到遞歸併放棄,導致不太具體的類型Comparable<?>