2011-05-12 93 views
3

下面的代碼編譯,但如果我取消註釋註釋行,它不,我很困惑爲什麼。 HashMap的確擴展了AbstractMap,並且宣告map的第一行編譯得很好。Java通用問題

import java.util.AbstractMap; 
import java.util.HashMap; 
import java.util.Map; 

public class Test { 

    public static void main(String args[]) { 
     Map<String, ? extends AbstractMap<String, String>> map = new HashMap<String, HashMap<String, String>>(); 
     //map.put("one", new HashMap<String, String>()); 
    } 
} 

而且,我知道了 「正確的方式」 是這樣的:

import java.util.HashMap; 
import java.util.Map; 

public class Test { 

    public static void main(String args[]) { 
     Map<String, Map<String, String>> map = new HashMap<String, Map<String, String>>(); 
     map.put("one", new HashMap<String, String>()); 
    } 
} 

回答

7

第一個代碼是不安全的 - 想象你實際上寫:

HashMap<String, ConcurrentHashMap<String, String>> strongMap = 
    new HashMap<String, ConcurrentHashMap<String, String>>(); 
Map<String, ? extends AbstractMap<String, String>> map = strongMap; 

現在:

map.put("one", new HashMap<String, String>()); 
ConcurrentHashMap<String, String> x = strongMap.get("one"); 

我們應該有一個ConcurrentHashMap - 但實際上我們只有HashMap

這實際上是簡單了很多解釋,如果我們減少仿製藥量怎麼回事?你的情況是真的相當於到(比方說):

List<? extends Fruit> list = new List<Apple>(); 
list.add(new Apple()); 

這看起來不錯,直到你考慮它是在有效期當量(至於編譯而言)到:

List<Apple> apples = new ArrayList<Apple>(); 
List<? extends Fruit> list = apples; 
list.add(new Orange()); 
Apple apple = list.get(0); // Should be okay... but element 0 is an Orange! 

這顯然是不OK唉。編譯器必須以相同的方式處理這兩者,因此它們都是無效的。

+0

嗨jon。你可以解釋嗎 ?爲什麼不安全?在我看來,橙色的例子應該工作。它應該添加一個橙色的水果名單。 – Jeb 2011-05-12 06:47:35

+0

,最重要的是如果我們想要一個通用的水果清單,解決方案是什麼? – Jeb 2011-05-12 06:48:30

+0

@ user450602:但它不是*實際上*水果清單 - 這是一個列表'。由於類型擦除,Java無法分辨,但這就是它被創建的原因。我將編輯該示例以更多地展示它爲什麼不起作用。 – 2011-05-12 06:48:36

0

如果你想真正的技術解釋,我會建議通過這些幻燈片閱讀component based software對這一問題的全貌,因爲你的小問題已經進行了大規模的巨大分歧:)

的實質是逆變協方差。在these slides中搜索關於面向對象設計及其在大型系統中的侷限性。

你的問題是,這些與一些仿製藥混合混合在:)

也就是說,你的合同(一些東西,是東西比水果更具體的水果)指定列表必須是能夠保留全部種水果,但是你創建一個只能容納某種水果的列表(蘋果或比蘋果更具體),根據我應該提出一個編譯器錯誤,但是Java編譯器太漂亮了,仿製藥沒有正確實現用Java語言(從表面上看,從內省的角度來看)。

容器實例對於容器類型/類可以是協變的或不變的,但實例的Containee必須是容器類型/類的不變量。

一個具體的例子:

List<Fruit> list = new ArrayList<Fruit>(); 

的一般示例:

ConatainerType<ElementOfList> list = new MoreSpecificContainerType<ElementOfList>(); 

ElementOfList必須滿足兩個協方差和逆變因爲對象都可以被放入(協方差)和檢索(逆變)而且只留下不變性,即相同類型/類別。

這是一個很長的答案,但我希望它能幫助有人在未來提出類似的問題。