2015-04-24 102 views
2

我正在學習Java泛型,我正在閱讀Naftalin和Wadler編寫的(非常好的)一本書,並且他談到了捕獲通配符的通配符,比如實現在Collections.reverse:Java泛型在通用方法中捕獲通配符

public static <T> void reverse(List<T> list){ 
    List<T> temp=new ArrayList<>(list); 
    for(int i=0;i<list.size();i++) 
     list.set(i,temp.get(list.size()-1-i)); 
} 

他說,在集合類中的方法是使用通配符簡單實現:

public static void reverse(List<?> list){ 
    //code.. 
} 

,但使用第一種方法體是行不通的:

public static void reverse(List<?> list){ 
    List<Object> temp=new ArrayList<Object>(list); //legal 
    for(int i=0;i<list.size();i++) 
     list.set(temp.get(list.size()-1-i));  //illegal 
} 

這是行不通的,因爲它試圖把一個對象類型元素的列表 類型未知(?),它是一切擴展對象(這是..well,一切)

因此調用從第二第一種方法應該做的伎倆:

public static void reverse1(List<?> list){ 
    reverse2(list); 
} 

public static <T> void reverse2(List<T> list){ 
    List<T> temp=new ArrayList<T>(list); 
    for(int i=0;i<list.size();i++) 
     list.set(i,temp.get(list.size()-1-i)); 
} 

至此,繼在方法調用發生的事情,例如傳遞

List<String> myList 

1)List<String> myList被上澆鑄到本地變量String<?> list(字符串延伸對象,這是上限通配符的,使得List<String>亞型List<?>

2)list現在傳遞給reverse2()和參數T是推斷爲? extends Object,現在我怎麼可以使用這個作爲參數,當我實例化新ArrayList<T>() ???這在Java代碼中顯然是非法的,所以其他事情必須發生,請問你是什麼?

感謝

盧卡

+0

我沒有看到這裏的任何東西正在實例化'ArrayList '。請圍繞代碼片段(以及任何需要'<' and '>'被正確渲染的代碼文本)用反引號(又名反引號'''')。或者您可以突出顯示文本並按下「{}」按鈕。 – rgettman

+0

@rgettman完成。我通過類型參數,因爲我調用的方法..我不明白我怎麼能寫方法體傳遞?作爲類型參數。 – Luca

回答

3

T參數在reverse2()推斷爲? extends Object,並使用通配符,?不進行實例化。

推理只會發生在調用reverse2()的方法中。例如,如果您撥打Collections.emptyList(),什麼是類型參數?在這個例子中,它是未知的,但它通常可以在調用現場推斷:

List<String> empty = Collections.emptyList(); 

被推斷爲是Collections.<String>emptyList()調用(顯式的形式)。

在你的情況下,T沒有限制,所以任何類型都是兼容的。但是,如果類型變量被聲明爲T extends String,則通配符?將過於寬泛以至於不能滿足該限制,並且該調用將是非法的。


好吧,我得到它,所以它是什麼T接?我的意思是,推斷T是什麼?

T是在reverse2()類型變量,並且如我如上所述,類型推斷髮生在呼叫者,而不是被叫方,所以T不是「推斷」是任何東西。

也許你的意思是什麼類型顯示在編譯的字節碼?在這種情況下,沒有聲明T類型的變量;永遠不會使用T,也不會進行類型檢查。因此,考慮以下人爲的例子:

final class Reverse { 

    static <T extends String> void reverse(List<T> list) { 
    List<T> tmp = new ArrayList<>(list); 
    for (int i = 0; i < list.size(); ++i) 
     list.set(i, tmp.get(list.size() - 1 - i)); 
    } 

} 

現在,調用該方法的客戶端:

final class Test { 

    public static void main(String... argv) { 
    List<String> list = Arrays.asList("A", "B", "C"); 
    Reverse.reverse(list); 
    System.out.println(list); 
    } 

} 

編譯這些類一起運行Test,你會得到[C, B, A],符合市場預期。現在,無需重新編譯Test改變reverse()方法的簽名並重新編譯只有Reverse類:

static <T extends Integer> void reverse(List<T> list) 

重新運行Test會產生相同的結果,而不是失敗!

現在改變reverse()方法的實現,並再次,重新編譯只有Reverse類:

static <T extends Integer> void reverse(List<T> list) { 
    List<T> tmp = new ArrayList<>(list); 
    for (int i = 0; i < list.size(); ++i) { 
    T el = tmp.get(list.size() - 1 - i); 
    list.set(i, el); 
    } 
} 

這一次,運行Test會產生你預期的失敗最後一次:

 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 

那是因爲T現在被實際參考:

T el = tmp.get(list.size() - 1 - i); 

向該分配編譯器將插入鑄造到上限類型參數,在這種情況下是Integer的:

T el = (Integer) tmp.get(list.size() - 1 - i); 

如果類型T不受限制(其上限是Object)無演員被執行,因爲它永遠不會失敗。

+0

好的,我知道了,那麼T是什麼呢?我的意思是,推斷T是什麼? – Luca

+0

好吧,我瞭解一些東西,但我不能誠實地得到一切....我無法理解這種泛型的內部工作原理... – Luca

+0

@erickson static void reverse(List list) 這是編譯時錯誤 – shikhar

0

list現在傳遞給reverse2()和參數T被推斷爲是? extends Object,[&hellip;]。

reverse2被調用時,通配符是捕獲到新鮮的類型的變量,其是不再通配符 *的類型T被推斷爲這個捕獲類型。

類型系統確保我們只能以安全的方式使用此功能。

例如,假設我們有哪些接受列表的方法:

static <T> void swap2(List<T> list1, List<T> list2) { 
    List<T> temp = new ArrayList<>(list1); 
    list1.clear(); 
    list1.addAll(list2); 
    list2.clear(); 
    list2.addAll(temp); 
} 

如果我們試圖用通配符來調用這個捕捉:

static void swap(List<?> list1, List<?> list2) { 
    swap2(list1, list2); // <- doesn't compile 
} 

這將無法編譯,因爲編譯器知道它不能推斷list1list2在傳遞到swap之前具有相同的類型。

reverse2是類型安全的,因爲它將元素添加到列表中,該元素最初在列表中開始。


*技術證明是5.1.10 Capture Conversion

如果Ti是形式?通配符類型參數,然後Si清爽型可變 [&hellip;]。

+0

我不知道所有這些捕獲通配符的東西,我發現很難理解,因爲我剛開始學習泛型... 在哪裏可以找到關於這個主題容易理解的文檔? – Luca

+0

可能[Angelika Langer的常見問題](http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html)。當然[官方教程](https://docs.oracle.com/javase/tutorial/java/generics/index.html)。但是我沒有意識到這是全面的,正確的,並且仍然容易理解。 (我認爲StackOverflow更多的是這樣的方式,但是它的知識存在一些缺陷。)捕獲的技術推理實際上非常複雜......我在閱讀完JLS後才完全理解它,這非常令人生畏。 – Radiodef

+0

好的,所以我會在稍後的時間把它用在我對語言的知識是有聲的時候......現在我只想了解我在做什麼來編寫一些體面的代碼。 謝謝。 – Luca