为什么Collectors.toMap在重复键错误上报告值而不是键?

时间:2016-10-14 09:27:23

标签: java java-8 java-stream collectors

这真是一个关于细微问题的问题,但我的印象是在这里弄错了。如果使用Collectors.toMap-method添加重复键,则会抛出一条带有“重复键”消息的异常。为什么报告的价值而不是关键?甚至两个?这真的很误导,不是吗?

这是一个证明行为的小测试:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class TestToMap {

    public static void main(String[] args) {

        List<Something> list = Arrays.asList(
            new Something("key1", "value1"),
            new Something("key2", "value2"),
            new Something("key3", "value3a"),
            new Something("key3", "value3b"));

        Map<String, String> map = list.stream().collect(Collectors.toMap(o -> o.key, o -> o.value));
        System.out.println(map);
    }

    private static class Something {
        final String key, value;

        Something(final String key, final String value){
            this.key = key;
            this.value = value;
        }
    }
}

7 个答案:

答案 0 :(得分:17)

这被报告为一个错误,请参阅commit fixing this,并在Java 9中修复。阅读{{3}},新的异常消息将是

String.format("Duplicate key %s (attempted merging values %s and %s)", k, u, v)

其中k是重复键,uv是映射到同一键的两个冲突值。

答案 1 :(得分:7)

正如其他答案已经说明的那样,这是一个将在Java 9中修复的错误。错误产生的原因是,toMap依赖于具有签名的Map.merge

V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)

如果key之前没有映射,则此方法将插入value - key映射,否则将评估remappingFunction以计算新值值。因此,如果不允许重复键,则可以直接提供remappingFunction,无条件地抛出异常,然后就完成了。但是......如果你看一下函数签名,你会注意到这个函数只接收要合并的两个,而不是 key

当为Java 8实现throwingMerger时,忽略了第一个参数是而不是密钥,但更糟糕的是,它不是直接修复的。

如果您尝试使用overloaded toMap collector提供替代合并,您会注意到。此时关键值不在范围内。 Java 9开发人员必须更改无重复案例的整个toMap实现,以便能够提供报告受影响密钥的异常消息...

答案 2 :(得分:3)

这是Jdk 8中的一个已知错误。抛出的消息应该显示至少两个存在密钥冲突的值,或者理想情况下显示发生冲突的密钥。附件是相同

link

答案 3 :(得分:0)

如果您遇到此问题并被迫在项目中使用Java 8,则必须创建自己的收集器才能获得有意义的错误消息。

下面您可以找到一个收集器的示例,该示例为您提供了一条有意义的消息,类似于使用Java 9所获得的消息:

private static class Something {
    private final String k;
    private final String v;

    public Something(String k, String v) {
        this.k = k;
        this.v = v;
    }
}

public static void main(String[] args) throws IOException, ParseException {
    List<Something> list = Arrays.asList(
            new Something("key1", "value1"),
            new Something("key2", "value2"),
            new Something("key3", "value3a"),
            new Something("key3", "value3b"));

    Collector<Something, ?, Map<String, String>> eloquentCollector =
            Collector.of(HashMap::new, (map, something) -> putUnique(map, something.k, something.v),
                    (m1, m2) -> { m2.forEach((k, v) -> putUnique(m1, k, v)); return m1; });

    Map<String, String> map = list.stream().collect(eloquentCollector);
    System.out.println(map);
}

private static <K,V> void putUnique(Map<K,V> map, K key, V v1){
    V v2 = map.putIfAbsent(key, v1);
    if(v2 != null) throw new IllegalStateException(
            String.format("Duplicate key '%s' (attempted merging incoming value '%s' with existing '%s')", key, v1, v2));
}

如果运行此代码,您将看到以下错误消息,指向重复的键并合并值:

  

重复键“ key3”(尝试合并值value3a和value3b)

如果您在以上parallelStream()的示例中使用,您应该得到相同的异常消息,例如:

Map<String, String> map = list.parallelStream().collect(eloquentCollector);

要感谢Holger指出并行流的原始答案不正确。

答案 4 :(得分:0)

在Java 8中,不允许一次将来自两个流(或具有相同strem的迭代两次)的重复值合并到Map中。 您可以使用 Collectors.groupingBy Collectors.mapping 将结果合并到Map<Object, Set<Object>>Map<Object, List<Object>>进行处理。

    List<Employee> employees = new ArrayList<>();
    employees.add(new Employee("Sachin", 40));
    employees.add(new Employee("Sachin", 30));   
    employees.add(new Employee("Rahul", 30));

    Map<Integer, Set<String>> map = employees.stream()
                                    .collect(
                                            Collectors.groupingBy(
                                                                Employee::getAge, 
                                                                Collectors.mapping(
                                                                        Employee::getName,
                                                                        Collectors.toSet()
                                                                )
                                                            )
                                            );

答案 5 :(得分:0)

是的。它已在 Java 9 上修复。 Java 8 中的消息。

Exception in thread "main" java.lang.IllegalStateException: Duplicate key HELLO IM NOT A KEY I AM A VALUE #1
    at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
    at java.util.HashMap.merge(HashMap.java:1253)
    at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at compass.issues.CollectorsToMapMessageIsWrong.main(CollectorsToMapMessageIsWrong.java:31)

Java >=9 消息是不言自明的。

例如在 Java 15 中我可以得到。

Exception in thread "main" java.lang.IllegalStateException: Duplicate key 1 (attempted merging values HELLO IM NOT A KEY I AM A VALUE #1 and HELLO IM NOT A KEY I AM A VALUE #2)
    at java.base/java.util.stream.Collectors.duplicateKeyException(Collectors.java:133)
    at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180)
    at java.base/java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
    at java15.CollectorsToMapMessageIsWrong.main(CollectorsToMapMessageIsWrong.java:31)

答案 6 :(得分:0)

BinaryOperator<Long> mergeFunction = (oldValue, newValue) -> newValue;
                Map<String, Long> dataTableIdMap = dataTableRepository.findByClientId(clientid)
                                    .stream()
                                    .collect(Collectors.toMap(DataTable::getAbbreviation, DataTable::getId, mergeFunction));