从列表中删除重复项,其中复制逻辑基于自定义字段

时间:2017-02-03 04:26:35

标签: java java-stream

我有以下信息列表

public class TheInfo {
    private int id;
    private String fieldOne;
    private String fieldTwo;
    private String fieldThree;
    private String fieldFour;

   //Standard Getters, Setters, Equals, Hashcode, ToString methods
}

该列表需要以

的方式处理
  1. 在重复项中,选择ID最小的项,然后删除其他项。在这种特殊情况下,当fieldOnefieldTwo的值相等时,条目被视为重复。
  2. 获取fieldThreefieldFour的连接值。
  3. 我想处理这个列表Java8 Streams。目前,我不知道如何根据自定义字段删除重复项。我想我不能使用distinct(),因为我不能改变equals / hashcode方法,因为逻辑只适用于这种特定情况。

    我怎样才能做到这一点?

2 个答案:

答案 0 :(得分:3)

假设你有

List<TheInfo> list;

你可以使用

List<TheInfo> result = new ArrayList<>(list.stream().collect(
    Collectors.groupingBy(info -> Arrays.asList(info.getFieldOne(), info.getFieldOne()),
        Collectors.collectingAndThen(
            Collectors.minBy(Comparator.comparingInt(TheInfo::getId)),
            Optional::get))).values());

groupingBy收集器根据函数生成组,其结果确定相等性。列表已经为一系列值实现了这一点,因此Arrays.asList(info.getFieldOne(), info.getFieldOne())生成一个合适的密钥。在Java 9中,您很可能会改为使用List.of(info.getFieldOne(), info.getFieldOne())

groupingBy的第二个参数是确定如何处理组的另一个收集器,Collectors.minBy(…)将根据比较器将它们折叠为最小元素,Comparator.comparingInt(TheInfo::getId)是获取组的正确比较器具有最小id的元素。

不幸的是,minBy收集器会生成一个Optional,如果没有元素,它将为空,但是因为我们知道这些组不能为空(不会创建没有元素的组)首先,我们可以无条件地在可选项上调用get来检索实际值。这就是Collectors.collectingAndThen(…, Optional::get)中收集此收集器的内容。

现在,分组的结果是从函数创建的密钥到具有最小id的Map实例的TheInfo映射。在values()上拨打Map即可Collection<TheInfo>,因为您需要List,所以最后new ArrayList<>(collection)会产生toMap

考虑到这一点,这可能是List<TheInfo> result = new ArrayList<>(list.stream().collect( Collectors.toMap( info -> Arrays.asList(info.getFieldOne(), info.getFieldOne()), Function.identity(), BinaryOperator.minBy(Comparator.comparingInt(TheInfo::getId)))).values()); 收集器更易于使用的情况之一,尤其是因为组合元素的合并不会从可变缩减中受益:

import dask
dask.set_options(get=dask.threaded.get)

这使用相同的函数来确定键,另一个函数确定单个值,如果一个组有多个元素,它只是一个身份函数和一个将被调用的简化函数。这将再次成为根据ID比较器返回最小值的函数。

答案 1 :(得分:2)

使用流,如果为其提供适当的分类器,则只需使用收集器即可处理它:

private static <T> T min(T first, T second, Comparator<? super T> cmp) {
  return cmp.compare(first, second) <= 0 ? first : second;
}

private static void process(Collection<TheInfo> data) {
  Comparator<TheInfo> cmp = Comparator.comparing(info -> info.id);

  data.stream()
      .collect(Collectors.toMap(
                info -> Arrays.asList(info.fieldOne, info.fieldTwo), // Your classifier uses a tuple. Closest thing in JDK currently would be a list or some custom class. I chose List for brevity.
                info -> info, // or Function.identity()
                (a, b) -> min(a, b, cmp) // what do we do with duplicates. Currently we take min according to Comparator.
              ));
}

上面的流将被收集到Map<List<String>, TheInfo>中,其中包含最小元素,其中包含两个字符串列表作为键。您可以提取map.values()然后返回新的集合或者您需要的任何内容。