使用Map和Collector

时间:2017-10-09 17:59:13

标签: java eclipse generics java-8 java-stream

不久之前,我发现了以下有关使用Java 8初始化地图的更简洁方法的信息: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()));

在“收集”调用中,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()));

同样,为什么这里需要类型提示,但在其他应用程序中却没有?唯一的区别是另一个应用程序从函数返回映射,新代码将映射分配给局部变量。我也修改了它,以便不是存储到局部变量,而是将它传递给另一个方法(这是原始需要)。这并没有改变添加类型提示的需要。

1 个答案:

答案 0 :(得分:3)

问题是logbackConfigurationFile是一个方法调用链,目标类型不会通过这样的链传播。因此,当您将结果分配给参数化的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()); 推断为NumberLong的常见类型,而是“Integer INT#1 extends Number,Comparable<? extends INT#2>”< / p>

您没有发布允许我们确定特定情况下参数类型的声明,但我很确定这是您的变体之间的差异,在第一个中,要么所有参数都有相同类型或推断的公共超类型与您期望的结果类型完全匹配,而在第二种情况下,参数具有不同类型或所需结果类型的子类型。

请注意,即使是

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()); } 的{​​{1}},还接受编译器推断为Map.Entry<String,Integer>Map<String,Number>的基本类型的交集类型。< / p>

但我建议另外一种方法,不要让每个客户重复Integer步骤。与new factory methods of Java 9比较。因此,受此模式启发的重构方法将如下所示:

Long

可以使用得更简单:

Stream.of(…).collect(…)

请注意,由于此用法仅包含嵌套调用(无链),因此目标类型推断在整个表达式中起作用,即甚至在工厂方法的通用签名中没有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)); } 的情况下也可以使用。但仍然建议使用这些通配符以获得最大的灵活性。