让我们用我的对象作为前缀,等于实现不是我需要过滤的方式,所以 distinct
本身不起作用。
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,但这个例子应该充分展示了目标。
JDK 开发人员讨论了此类功能(请参阅 JDK-8072723),并且可能包含在 Java-9 中(但不保证)。 StreamEx我开发的库已经有这样的功能,所以你可以使用它:
List<MyObject> distinct = StreamEx.of(listA).append(listB)
.distinct(MyObject::getFoo).toList();
StreamEx
类是增强的 Stream
它与 JDK Stream 完全兼容,但有许多额外的操作,包括 distinct(Function)
它允许您为不同的操作指定 key 提取器。在内部,它与@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>
中其中Key是提取出来的Key,Element是对应的流元素。为了确保在所有重复元素中准确地保留第一个出现的元素,LinkedHashMap
用来。最后你只需要拿 values()
这张 map 并将它们转储到列表中。所以现在你可以写:
List<MyObject> distinct = Stream.concat(listA.stream(), listB.stream())
.collect(distinctBy(MyObject::getFoo));
如果您不关心生成的集合是否为列表,您甚至可以删除 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);
}
这样的收集器(保留顺序并返回 List
)也是 available在 StreamEx 库中。