2017-10-09 47 views
2

前一段時間,我發現有關使用Java 8初始化映射的更清晰方式的以下信息:http://minborgsjavapot.blogspot.com/2014/12/java-8-initializing-maps-in-smartest-way.html使用Map和Collector混淆java泛型錯誤

使用這些準則,我已採取了下列類在一個應用程序:

public class MapUtils { 
    public static <K, V> Map.Entry<K, V> entry(K key, V value) { 
     return new AbstractMap.SimpleEntry<>(key, value); 
    } 

    public static <K, U> Collector<Map.Entry<K, U>, ?, Map<K, U>> entriesToMap() { 
     return Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()); 
    } 

    public static <K, U> Collector<Map.Entry<K, U>, ?, ConcurrentMap<K, U>> entriesToConcurrentMap() { 
     return Collectors.toConcurrentMap((e) -> e.getKey(), (e) -> e.getValue()); 
    } 
} 

在這種應用中,我實施了這樣的代碼:

public Map<String, ServiceConfig> serviceConfigs() { 
    return Collections.unmodifiableMap(Stream.of(
      entry("ActivateSubscriber", new ServiceConfig().yellowThreshold(90).redThreshold(80)), 
      entry("AddAccount", new ServiceConfig().yellowThreshold(90).redThreshold(80).rank(3)), 
      ... 
      ). 
      collect(entriesToMap())); 
} 

此代碼工作完全正常。

在一個不同的應用程序中,我將MapUtils類複製到一個包中,並將該類導入到一個類中,就像我在其他應用程序中那樣。

我輸入了以下引用此:

 Map<String, USLJsonBase> serviceRefMap = 
    Collections.unmodifiableMap(Stream.of(
      entry("CoreService", coreService), 
      entry("CreditCheckService", creditCheckService), 
      entry("PaymentService", paymentService), 
      entry("AccountService", accountService), 
      entry("OrdercreationService", orderCreationService), 
      entry("ProductAndOfferService", productAndOfferService), 
      entry("EquipmentService", equipmentService), 
      entry("EvergentService", evergentService), 
      entry("FraudCheckService", fraudCheckService) 
      ). 
      collect(entriesToMap())); 

在「收」的呼叫,Eclipse是告訴我下面的:

The method collect(Collector<? super Map.Entry<String,? extends USLJsonBase>,A,R>) in the type Stream<Map.Entry<String,? extends USLJsonBase>> is not applicable for the arguments (Collector<Map.Entry<Object,Object>,capture#1-of ?,Map<Object,Object>>) 

需要什麼簡單的,完全不顯着變化讓這個工作?

更新

我認爲這增加了一絲類型可能做到這一點,但我不明白,爲什麼在其他應用程序的使用並不需要這個。

我改變了參考這個,現在不給我一個編譯錯誤:

Map<String, USLJsonBase> serviceRefMap = 
    Collections.unmodifiableMap(Stream.<Map.Entry<String, USLJsonBase>>of(
      entry("CoreService", coreService), 
      entry("CreditCheckService", creditCheckService), 
      entry("PaymentService", paymentService), 
      entry("AccountService", accountService), 
      entry("OrdercreationService", orderCreationService), 
      entry("ProductAndOfferService", productAndOfferService), 
      entry("EquipmentService", equipmentService), 
      entry("EvergentService", evergentService), 
      entry("FraudCheckService", fraudCheckService) 
      ). 
      collect(entriesToMap())); 

再次,爲什麼這裏需要的類型提示,而不是在其他應用程序?唯一的區別是另一個應用程序正在從一個函數返回地圖,新代碼將地圖分配給一個局部變量。我也修改了它,以便不將它存儲到局部變量中,而是將它傳遞給另一種方法(這是最初的需要)。這並沒有改變添加類型提示的需要。

+0

要麼將​​流首先分配給變量,要麼添加類型提示。或者,在Java 9中,使用'Map.of'。 –

+0

檢查我的答案在愚蠢(我不相信是一個愚蠢的,將重新從實際的計算機)。這裏有一個Javac標誌,它將打開類型推斷算法的擴展調試信息。這可能會提供有關差異的線索。 –

+0

任何想法如何讓Eclipse使用該標誌? –

回答

3

問題是Stream.of(…).collect(…)是一個方法調用鏈,目標類型不通過這樣的鏈傳播。因此,當您將結果分配給參數化的Map時,這些類型參數將被考慮用於collect調用(和調用嵌套entriesToMap()),但不會調用Stream.of(…)調用。

因此,爲了推斷通過Stream.of(…)創建的流的類型,只考慮參數的類型。當所有參數具有相同的類型時,這很有效。

Map<String,Integer> map = Stream.of(entry("foo", 42), entry("bar", 100)) 
           .collect(entriesToMap()); 

沒有問題,但當參數具有不同的類型時很少做所期望的事情,例如,

Map<String,Number> map = Stream.of(entry("foo", 42L), entry("bar", 100)) 
           .collect(entriesToMap()); 

失敗,因爲編譯器不能由此推斷Number共同類型LongInteger,而是像「INT#1 extends Number,Comparable<? extends INT#2>INT#2 extends Number,Comparable<?>

你沒有張貼,使我們確定的聲明在你的具體情況下的參數類型,但我很確定這是你的變體之間的差異,在第一個,或者所有參數具有相同的類型或推斷的公共超類型完全匹配你想要的結果類型,而在第二種情況下,參數具有不同的類型或子類型的期望結果類型。

注意,即使

Map<String,Number> map = Stream.of(entry("foo", 42), entry("bar", 100)) 
           .collect(entriesToMap()); 

不起作用,因爲推斷流類型爲Stream<Map.Entry<String,Integer>>,你的收藏家不接受生產Map<String,Number>

這導致解決方案放寬收集器的通用簽名。

public static <K, U> 
Collector<Map.Entry<? extends K, ? extends U>, ?, Map<K, U>> entriesToMap() { 
    return Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()); 
} 

這解決了這兩個例子中,不僅接受Map.Entry<String,Integer>用於Map<String,Number>,還接受該交點鍵入推斷爲基礎類型的IntegerLong編譯器。


但我建議的替代,而不是讓每個客戶端重複Stream.of(…).collect(…)一步都沒有。與new factory methods of Java 9比較。因此,通過這種模式的啓發重構的方法將看起來像:

public static <K, V> Map.Entry<K, V> entry(K key, V value) { 
    return new AbstractMap.SimpleImmutableEntry<>(key, value); 
} 

@SafeVarargs 
public static <K, V> Map<K,V> mapOf(Map.Entry<? extends K, ? extends V>... entries) { 
    return Stream.of(entries) 
      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 
} 

@SafeVarargs 
public static <K, V> ConcurrentMap<K,V> concurrentMapOf(
             Map.Entry<? extends K, ? extends V>... entries) { 
    return Stream.of(entries) 
      .collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)); 
} 

可以使用更簡單:

Map<String,Integer> map1 = mapOf(entry("foo", 42), entry("bar", 100)); 
Map<String,Number> map2 = mapOf(entry("foo", 42), entry("bar", 100)); 
Map<String,Number> map3 = mapOf(entry("foo", 42L), entry("bar", 100)); 

注意,因爲這種用法包括嵌套調用的唯一(無鏈條),目標型推理在整個表達式中起作用,即,即使在工廠方法的通用簽名中沒有? extends也可以工作。但仍然建議使用這些通配符以獲得最大的靈活性。

+0

傑出的解釋。關於你對工作和失敗案例類型的猜測,工作案例的值都是相同類型的,在失敗案例中,所有值都是地圖值類型的子類的實例。 –