java - 混淆 Java 泛型错误与 Map 和 Collector

标签 java eclipse generics java-8 java-stream

不久前,我发现了以下有关使用 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/

相关文章:

java - 我被教导说并发条件不一定需要写在循环中,这与 oracle 文档所说的相反

java - ClassNotFoundException 即使包含该类的 jar 正确存在于类路径中

java - 实例化类型参数和重写方法

c# - 实现接口(interface)泛型函数

C#:如何在 xml 注释中引用 default(T) 和构造函数

java - 打印出三维数组并得到合并后的整数值

java - 从数据库中删除数据后如何刷新 ListView ?

java - 当结果不同时如何进行单元测试?

java - 使用 javascript 在上传文件时执行两个操作。文件和文件详细信息不会传递到 javascript 函数

java - 如何在 sourceforge.net 中搜索在 Eclipse 中构建的 Java 项目