Java 8 Streams reduce去除重复项并保留最近的条目。

7

I have a Java bean, like

class EmployeeContract {
    Long id;
    Date date;
    getter/setter
}

如果我有一个很长的列表,其中我们按id重复但日期不同,例如:
1, 2015/07/07
1, 2018/07/08
2, 2015/07/08
2, 2018/07/09

我该如何缩减列表,只保留最近日期的条目,例如:
1, 2018/07/08
2, 2018/07/09

最好使用Java 8...

我开始尝试了以下代码:

contract.stream()
         .collect(Collectors.groupingBy(EmployeeContract::getId, Collectors.mapping(EmployeeContract::getId, Collectors.toList())))
                    .entrySet().stream().findFirst();

这让我得到了各个组内的映射,但是我不知道如何将其收集到结果列表中 - 我的流技能并不太强,恐怕无法解决...


2
我想回答这个问题,但是它关闭得太快了... yourList.stream() .collect(Collectors.toMap( EmployeeContract::getId, Function.identity(), BinaryOperator.maxBy(Comparator.comparing(EmployeeContract::getDate).reversed())) ) .values(); - Eugene
3
与其使用BinaryOperator.maxBy(…….reversed()),你可以使用BinaryOperator.minBy(…)。不过在这种情况下,看起来提问者想要使用maxBy而不需要.reversed() - Holger
2
@Holger,鉴于这个(values())会返回一个 Collection<EmployeeContract> 而不是一个确切的 List<EmployeeContract>,是否有一种简洁的解决方法? - Naman
4
如果确实需要一个 List,你可以a) 将整个表达式包装在 new ArrayList<>(…) 中,或者b) 将收集器包装在 Collectors.collectingAndThen(…, m -> new ArrayList<>(m.values()))中。 - Holger
1
使用 LocalDate 表示仅包含日期而不包含时间和时区的值。永远不要使用 Date(一个糟糕的类,现在已经过时)。 - Basil Bourque
显示剩余2条评论
4个回答

12

好的,我将以回答的形式在此处放置我的评论:

 yourList.stream()
         .collect(Collectors.toMap(
                  EmployeeContract::getId,
                  Function.identity(),
                  BinaryOperator.maxBy(Comparator.comparing(EmployeeContract::getDate)))
            )
         .values();

如果您真的关心这一点,这将为您提供一个Collection而不是List


1
你可以按照以下两个步骤完成:

List<EmployeeContract> finalContract = contract.stream() // Stream<EmployeeContract>
        .collect(Collectors.toMap(EmployeeContract::getId, 
                EmployeeContract::getDate, (a, b) -> a.after(b) ? a : b)) // Map<Long, Date> (Step 1)
        .entrySet().stream() // Stream<Entry<Long, Date>>
        .map(a -> new EmployeeContract(a.getKey(), a.getValue())) // Stream<EmployeeContract>
        .collect(Collectors.toList()); // Step 2

第一步:确保将日期与映射到id的最新日期进行比较。
第二步:将这些键值对映射到最终的List<EmployeeContract>,作为结果。

为什么在 Date 已经实现了 Comparable 接口的情况下,还要使用 (a, b) -> a.after(b) ? a : b) 呢? - Eugene
没有特别的原因,@Eugene,我只是在研究“日期”API以进行比较,并发现使用“after”在可读性方面更好一些。 - Naman
1
好的回答,非常感谢你提供了逐步解释! - Nestor Milyaev

1

为了补充现有的回答,因为您正在询问:

如何将其收集到结果列表中

以下是一些选项:

  • Wrap the values() into an ArrayList:

    List<EmployeeContract> list1 = 
        new ArrayList<>(list.stream()            
                            .collect(toMap(EmployeeContract::getId,                                                                          
                                           identity(),
                                           maxBy(comparing(EmployeeContract::getDate))))
                            .values());
    
  • Wrap the toMap collector into collectingAndThen:

    List<EmployeeContract> list2 = 
        list.stream()
            .collect(collectingAndThen(toMap(EmployeeContract::getId,
                                             identity(),
                                             maxBy(comparing(EmployeeContract::getDate))),
                     c -> new ArrayList<>(c.values())));
    
  • Collect the values to a new List using another stream:

    List<EmployeeContract> list3 = 
        list.stream()
            .collect(toMap(EmployeeContract::getId,
                           identity(),
                           maxBy(comparing(EmployeeContract::getDate))))
            .values()
            .stream()
            .collect(toList());
    

0

使用vavr.io,您可以这样做:

var finalContract = Stream.ofAll(contract) //create io.vavr.collection.Stream
            .groupBy(EmployeeContract::getId)
            .map(tuple -> tuple._2.maxBy(EmployeeContract::getDate))
            .collect(Collectors.toList()); //result is list from java.util package

不确定理解与java.util.stream中的经典Stream有什么区别? - azro
在 io.vavr 集合中,对象是不可变的,并且它们具有 map()、filter() 等方法。 - jker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接