Map<String, String> phoneBook = people.stream()
                                      .collect(toMap(Person::getName,
                                                     Person::getAddress));

我得到java.lang.IllegalStateException:当找到一个重复的元素时,重复键。

是否有可能忽略这种例外添加值到地图?

当有重复的键时,应该忽略重复的键继续执行。


当前回答

为了完整起见,这里介绍了如何将重复项“减少”到只有一个。

如果你同意最后一个:

  Map<String, Person> phoneBook = people.stream()
          .collect(groupingBy(x -> x.name, reducing(null, identity(), (first, last) -> last)));

如果你只想要第一个:

  Map<String, Person> phoneBook = people.stream()
          .collect(groupingBy(x -> x.name, reducing(null, identity(), (first, last) -> first != null ? first : last)));

如果你想要last but“address as String”(不使用identity()作为参数)。

  Map<String, String> phoneBook = people.stream()
          .collect(groupingBy(x -> x.name, reducing(null, x -> x.address, (first, last) -> last)));

因此,本质上,groupingBy与还原收集器配对,开始表现得非常类似于toMap收集器,有一些类似于它的mergeFunction…结果是一样的……

其他回答

正如在JavaDocs中所说:

如果映射的键包含重复项(根据 Object.equals(Object))时,当异常时抛出IllegalStateException 执行收集操作。如果映射的键可能有 重复,使用toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction)代替。

所以你应该使用toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction)来代替。只需提供一个合并函数,它将确定将哪个副本放入映射中。

例如,如果你不关心是哪个,打电话就可以了

Map<String, String> phoneBook = people.stream().collect(
        Collectors.toMap(Person::getName, Person::getAddress, (a1, a2) -> a1));

来自alaster的答案对我帮助很大,但如果有人试图对数据进行分组,我想添加有意义的信息。

例如,如果您有两个订单,每个订单的代码相同,但产品数量不同,并且您希望将数量相加,您可以执行以下操作:

List<Order> listQuantidade = new ArrayList<>();
listOrders.add(new Order("COD_1", 1L));
listOrders.add(new Order("COD_1", 5L));
listOrders.add(new Order("COD_1", 3L));
listOrders.add(new Order("COD_2", 3L));
listOrders.add(new Order("COD_3", 4L));

listOrders.collect(Collectors.toMap(Order::getCode, 
                                    o -> o.getQuantity(), 
                                    (o1, o2) -> o1 + o2));

结果:

{COD_3=4, COD_2=3, COD_1=9}

或者,在javadocs中,你可以组合地址:

 Map<String, String> phoneBook
     people.stream().collect(toMap(Person::getName,
                                   Person::getAddress,
                                   (s, a) -> s + ", " + a));

这可以通过使用collector的mergeFunction参数实现。toMap(keyMapper, valueMapper, mergeFunction):

Map<String, String> phoneBook = 
    people.stream()
          .collect(Collectors.toMap(
             Person::getName,
             Person::getAddress,
             (address1, address2) -> {
                 System.out.println("duplicate key found!");
                 return address1;
             }
          ));

mergeFunction是一个对与同一个键相关联的两个值进行操作的函数。address1对应于收集元素时遇到的第一个地址,address2对应于遇到的第二个地址:这个lambda只是告诉保留第一个地址而忽略第二个地址。

按对象分组

Map<Integer, Data> dataMap = dataList.stream().collect(Collectors.toMap(Data::getId, data-> data, (data1, data2)-> {LOG.info("Duplicate Group For :" + data2.getId());return data1;}));

感觉toMap经常工作,但并不总是java流的一个黑暗的弱点。好像他们应该把它叫做“ouniquemap”之类的…

最简单的方法是使用收集器。groupingBy而不是collections . tomap。

默认情况下它会返回一个List类型的输出,但是碰撞问题已经解决了,这也许是你想要的存在的倍数。

  Map<String, List<Person>> phoneBook = people.stream()
          .collect(groupingBy((x) -> x.name));

如果一个Set类型的地址集合与特定的名称相关联,groupingBy也可以做到这一点:

Map<String, Set<String>> phoneBook = people.stream()
          .collect(groupingBy((x) -> x.name, mapping((x) -> x.address, toSet())));

另一种方法是“开始”一个哈希或一个集…并仔细跟踪以确保键在输出流中不会重复。啊。这里有一个例子,碰巧在这种情况下幸存下来…