使用Map和Collector混淆java泛型错误

问题描述:

前一段时间,我发现有关使用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使用该标志? –

问题是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

杰出的解释。关于你对工作和失败案例类型的猜测,工作案例的值都是相同类型的,在失败案例中,所有值都是地图值类型的子类的实例。 –