2015-07-03 113 views
5

執行提取表達式重構時,我偶然發現了Eclipse 4.4和Java 8生成45中的一些奇怪行爲,至少對我而言如此。下面的例子顯示了原始的和正確的代碼將提取物重構之前:使用泛型返回類型提取表達式後類型不匹配

import java.util.Map; 
import java.util.Set; 

public class MyMap<K, V> { 
    public void putAll(final Map<? extends K, ? extends V> mapToCopy) { 
     for (Map.Entry<? extends K, ? extends V> entry : mapToCopy.entrySet()) { 
     } 
    } 
} 

Eclipse的重構的結果是這樣的,導致下面的錯誤消息指entrySet在讀訪問循環聲明:

public void putAll(final Map<? extends K, ? extends V> mapToCopy) { 
     Set<?> entrySet = mapToCopy.entrySet(); 
     for (Map.Entry<? extends K, ? extends V> entry : entrySet) { 
                 ^^^^^^^^ 
     } 
    } 

Type mismatch: cannot convert 
    from element type capture#3-of ? 
    to Map.Entry<? extends K,? extends V> 

我改變了entrySet聲明的類型Set<Map.Entry<? extends K, ? extends V>>。這次,錯誤在聲明的初始化程序中顯示,說:

public void putAll(final Map<? extends K, ? extends V> mapToCopy) { 
     Set<Map.Entry<? extends K, ? extends V>> entrySet = mapToCopy.entrySet(); 
                  ^^^^^^^^^^^^^^^^^^^^ 
     for (Map.Entry<? extends K, ? extends V> entry : entrySet) { 
     } 
    } 

Type mismatch: cannot convert 
    from Set<Map.Entry<capture#1-of ? extends K,capture#2-of ? extends V>> 
    to Set<Map.Entry<? extends K,? extends V>> 

由於原始代碼的確編譯,所以我有點困惑。也許有人可以幫助我並給出解釋?提前致謝!

+1

請注意'Set <?擴展Map.Entry <?延伸K,?擴展V >> entrySet = mapToCopy.entrySet(); '會工作。 [JLS第14.4.2節](http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14.2)討論了增強中「Iterable」的翻譯爲'聲明。另請參閱[本答案](http://stackoverflow.com/a/16753901/5065475) –

+0

@AndyBrown:請您詳細說明「請注意,[...]將工作」,請。我需要修改什麼才能使其工作? – Marcus

+0

在java8中,還可以嘗試'map.forEach((key,value) - > {...})'。鍵/值被推斷爲適當的類型,如K/V的某些未知子類型。如果我們規定類型 - 'map.forEach((K key,V value) - > {...}),那麼API也是足夠靈活的' – ZhongYu

回答

0

讓我們第一次審查的原始來源:

public void putAll(final Map<? extends K, ? extends V> mapToCopy) { 
    for (Map.Entry<? extends K, ? extends V> entry : mapToCopy.entrySet()) { 
    } 
} 

內部(在運行時),這將被編譯並會工作:

public void putAll(final Map mapToCopy) { 
    for (Iterator<Map.Entry> iterator = mapToCopy.iterator; iterator.hasNext();) { 
    } 
} 

其中? extends K? extends V將與一些被替換類型擦除後的真實類型。編譯器會知道哪些類型是不兼容的,並且不會引發類型不兼容的Exception

在另一方面,如果你重構源此,

public void putAll(final Map<? extends K, ? extends V> mapToCopy) { 
    Set<Entry<? extends K, ? extends V>> entrySet = mapToCopy.entrySet(); 
                ^^^^^^^^ 
    for (Map.Entry<? extends K, ? extends V> entry : entrySet) { 

    } 
} 

那麼編譯器將沒有任何證據表明entrySet持有同一類型Map.Entry<? extends K, ? extends V>,只是因爲通配符(?)始終代表未知,即不能保證entrySet的輸入密鑰值的通配符與entry的密鑰值(來自循環)的通配符相同。由於不能百分之百確定類型是否兼容,編譯器會產生編譯時錯誤,即使**您可能有信心在運行系統中這些類型相同。

+0

感謝您的迴應。爲什麼你的第三個代碼片段在循環語句中顯示指示的錯誤?我無法重現這一點。在我的第三個代碼片段中,錯誤在聲明語句中指出。 – Marcus

+0

我認爲,問題與for循環無關。我詳細闡述了這個問題,並開始了一個新的線程[here](http://stackoverflow.com/questions/32143844/type-mismatch-when-using-map-entryset)。 – Marcus