当前位置: 首页 > 知识库问答 >
问题:

我如何使用Stream API合并然后区分集合?[副本]

朱运诚
2023-03-14
class MyObject {
  String foo;
  MyObject( String foo ) {
    this.foo = foo;
  }
  public String getFoo() { return foo; }
}


Collection<MyObject> listA = Arrays.asList("a", "b", "c").stream().map(MyObject::new)
        .collect(Collectors.toList());

Collection<MyObject> listB = Arrays.asList("b", "d").stream().map(MyObject::new)
        .collect(Collectors.toList());


// magic

我如何合并和消除列表的重复,以便得到的列表应该是包含“a”,“b”,“c”,“d”的MyObjects?

注意:这是对我们实际需要进行重复数据删除的方法的简化,这些方法实际上是hibernate加载的实体的复杂DTO,但是这个示例应该充分展示了目标。

共有1个答案

暨曾笑
2023-03-14

JDK开发人员讨论了这样的特性(参见JDK-8072723),并且可能包含在Java-9中(尽管没有保证)。我开发StreamEx库已经有了这样的特性,所以您可以使用它:

List<MyObject> distinct = StreamEx.of(listA).append(listB)
                                  .distinct(MyObject::getFoo).toList();

Streamex类是一个增强的Stream,它与JDK流完全兼容,但有许多附加操作,包括distinct(函数),它允许您为distinct操作指定键提取器。在内部,它非常类似于@fge提出的解决方案

您还可以考虑编写自定义收集器,该收集器将结合获取不同的对象和将它们存储到列表中:

public static <T> Collector<T, ?, List<T>> distinctBy(Function<? super T, ?> mapper) {
    return Collector.<T, Map<Object, T>, List<T>> of(LinkedHashMap::new,
        (map, t) -> map.putIfAbsent(mapper.apply(t), t), (m1, m2) -> {
            for(Entry<Object, T> e : m2.entrySet()) {
                m1.putIfAbsent(e.getKey(), e.getValue());
            }
            return m1;
        }, map -> new ArrayList<>(map.values()));
}

这个收集器将结果中间地收集到map ,其中Key是提取的键,Element是相应的流元素。为了确保在所有重复的元素中保留第一个元素,使用LinkedHashMap。最后,您只需要将此映射的values()转储到列表中。所以现在你可以写:

List<MyObject> distinct = Stream.concat(listA.stream(), listB.stream())
                                .collect(distinctBy(MyObject::getFoo));

如果您不关心结果集合是否为list,甚至可以删除new arrayList<>()步骤(只需使用map::values作为完成器)。如果您不关心顺序,还可以进行更多的简化:

public static <T> Collector<T, ?, Collection<T>> distinctBy(Function<? super T, ?> mapper) {
    return Collector.<T, Map<Object, T>, Collection<T>> of(HashMap::new,
            (map, t) -> map.put(mapper.apply(t), t), 
            (m1, m2) -> { m1.putAll(m2); return m1; }, 
            Map::values);
}

StreamEx库中也提供了这样的收集器(保留顺序并返回列表)。

 类似资料:
  • 我有两个分支:和。我刚刚将合并回中,并且完成了该分支。我应该删除它还是让它坐着?删除它是否会导致数据丢失?

  • 我们介绍一下merge的特殊选项:squash 用这个选项指定分支的合并,就可以把所有汇合的提交添加到分支上。 主要使用的场合: 汇合主题分支的提交,然后合并提交到目标分支。

  • 问题内容: 我有一个通用名称,正在尝试找出如何对其中包含的项目进行排序。我已经尝试了一些方法,但是我无法使它们正常工作。 问题答案: 集合本身没有预定义的顺序,因此您必须将它们转换为。然后您可以使用一种形式的

  • 问题内容: 除了a 和Java 可以两次包含相同的元素外,a 和Java 之间在实践上还有什么区别吗?它们具有相同的方法。 (例如,是否给我更多选择来使用接受s但不接受s的库?) 编辑: 我可以认为至少有5种不同的情况来判断这个问题。其他人还能提出更多建议吗?我想确保我了解这里的微妙之处。 设计接受或参数的方法。更通用,并接受更多输入可能性。(如果我正在设计特定的类或接口,那么对我的消费者会更好,

  • 问题内容: 有没有有效的方法来合并具有交集的集合。例如: 预期结果是: 所有具有交集(公共分量)的集合都应合并。例如: 因此,这两个集合应该合并: 不幸的是我没有任何有效的解决方案。 更新:结果集的顺序并不重要。 问题答案: @ mkrieger1在注释中提到的一种实现连接组件算法的有效方法是将集合列表转换为一组可散列的冻结集,以便在迭代时找到与当前集合相交的冻结集。从池中删除它: 鉴于,将变为:

  • sunion key1 key2...keyN 返回所有给定key的并集 sunionstore dstkey key1...keyN 同sunion,并同时保存并集到dstkey下