有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java为什么不支持收集器。toMap报告值而不是重复键错误上的键?

这确实是一个关于一个小细节的问题,但我的印象是这里出了问题。如果使用Collectors.toMap-method添加重复键,它会抛出一个异常,并显示消息“duplicate key”。为什么报告的是值而不是键?或者两者都有?这真是误导,不是吗

这里有一个小测试来证明这种行为:

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;
        }
    }
}

共 (4) 个答案

  1. # 1 楼答案

    如果您遇到了这个问题,并且被迫在项目中使用Java8,那么您必须创建自己的收集器以获得有意义的错误消息

    下面是一个收集器示例,它给您提供了一条与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));
    }
    

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

    Duplicate key 'key3' (attempted merging values value3a and value3b)

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

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

    感谢Holger指出原始答案对并行流不适用

  2. # 2 楼答案

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

    正如其他答案已经指出的那样,这是一个将在Java9中修复的错误。这个错误产生的原因是toMap依赖于^{},后者具有签名

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

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

    当为Java 8实现^{}时,人们忽略了第一个参数是而不是键,但更糟糕的是,它不是直接修复的

    如果您试图使用overloaded ^{} collector提供一个替代合并,您会注意到。此时,键值根本不在范围内。Java 9开发人员必须更改整个toMap实现,才能提供一条异常消息来报告受影响的密钥

  4. # 4 楼答案

    在Java8中,不允许一次将两个流(或同一个流迭代两次)中的重复值合并到映射中。 您可以使用收集器。分组方式&收藏家。映射将结果合并到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()
                                                                    )
                                                                )
                                                );