不久前,我发现了以下有关使用 Java 8 初始化 map 的更简洁方法的信息:http://minborgsjavapot.blogspot.com/2014/12/java-8-initializing-maps-in-smartest-way.html .
使用这些准则,我在一个应用程序中实现了以下类:
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()));
在“collect”调用中,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()));
同样,为什么这里需要类型提示,而其他应用程序不需要?唯一的区别是另一个应用程序从函数返回映射,而新代码将映射分配给局部变量。我还对其进行了修改,以便将其传递给另一个方法(这是最初的需要),而不是存储到局部变量中。这并没有改变添加类型提示的需要。
最佳答案
问题是 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
作为 Long
的常见类型和Integer
,而是类似“INT#1 extends Number,Comparable<? extends INT#2>
INT#2 extends Number,Comparable<?>
”
您没有发布允许我们确定您的具体情况下参数类型的声明,但我很确定这是您的变体之间的区别,在第一个变体中,要么所有参数都有相同类型或推断的公共(public)父类(super class)型与您所需的结果类型完全匹配,而在第二种情况下,参数具有不同类型或所需结果类型的子类型。
请注意,即使
Map<String,Number> map = Stream.of(entry("foo", 42), entry("bar", 100))
.collect(entriesToMap());
不起作用,因为推断的流类型是 Stream<Map.Entry<String,Integer>>
您的 Collection 家不接受 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>
,但也接受编译器推断为 Integer
的基类型的交集类型和Long
.
但我建议另一种选择,不要让每个客户重复 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
的情况下工作。在工厂方法的通用签名中。但为了获得最大的灵活性,仍然建议使用这些通配符。
关于java - 混淆 Java 泛型错误与 Map 和 Collector,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46652386/