从基于自定义字段的列表中删除重复项

3
我有以下信息列表。
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的连接值。

我想使用Java8 Streams处理此列表。目前我不知道如何基于自定义字段删除重复项。我认为无法使用distinct(),因为无法更改equals/hashcode方法,因为逻辑仅适用于此特定情况。

我该如何实现这个?


请展示一下你尝试过什么? - Shyam Baitmangalkar
到目前为止,我已经将TheInfo信息封装到TheWrapper中,并基于fieldOnefieldTwo实现了equalshascode,并基于id实现了Comparable。完成这些后,我将TheInfo列表映射到TheWrapper,对其进行排序,应用distinct(),再将其映射回TheInfo,并收集为一个列表。我目前正在测试它。 - Bilal Mirza
尽管我在之前的评论中提到的实现起作用了,但重写的 equals 方法并没有遵守 compareTo 的契约。 - Bilal Mirza
2个回答

4
假设您已经拥有:
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的TheInfo实例的Map。在Map上调用values()会得到一个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());

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


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将包含由两个字符串列表组成的最小元素作为键。您可以提取map.values()并将其返回到新集合或任何需要它们的地方。

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