Java 8 按属性区分
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23699371/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java 8 Distinct by property
提问by RichK
In Java 8 how can I filter a collection using the Stream
API by checking the distinctness of a property of each object?
在 Java 8 中,如何Stream
通过检查每个对象的属性的独特性来使用API过滤集合?
For example I have a list of Person
object and I want to remove people with the same name,
例如,我有一个Person
对象列表,我想删除同名的人,
persons.stream().distinct();
Will use the default equality check for a Person
object, so I need something like,
将对对象使用默认的相等性检查Person
,所以我需要类似的东西,
persons.stream().distinct(p -> p.getName());
Unfortunately the distinct()
method has no such overload. Without modifying the equality check inside the Person
class is it possible to do this succinctly?
不幸的是,该distinct()
方法没有这样的重载。不修改Person
类内部的相等性检查是否可以简洁地做到这一点?
采纳答案by Stuart Marks
Consider distinct
to be a stateful filter. Here is a function that returns a predicate that maintains state about what it's seen previously, and that returns whether the given element was seen for the first time:
考虑distinct
是一个有状态的过滤器。这是一个函数,它返回一个谓词,该谓词维护关于它之前看到的内容的状态,并返回给定元素是否第一次被看到:
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}
Then you can write:
然后你可以写:
persons.stream().filter(distinctByKey(Person::getName))
Note that if the stream is ordered and is run in parallel, this will preserve an arbitraryelement from among the duplicates, instead of the first one, as distinct()
does.
请注意,如果流是有序的并并行运行,这将保留重复项中的任意元素,而不是第一个,就像distinct()
这样。
(This is essentially the same as my answerto this question: Java Lambda Stream Distinct() on arbitrary key?)
(这与我对这个问题的回答基本相同:Java Lambda Stream Distinct() on任意键?)
回答by nosid
You can wrap the person objects into another class, that only compares the names of the persons. Afterwards, you unwrap the wrapped objects to get a person stream again. The stream operations might look as follows:
您可以将人对象包装到另一个类中,该类只比较人的姓名。之后,您解开包装的对象以再次获得人员流。流操作可能如下所示:
persons.stream()
.map(Wrapper::new)
.distinct()
.map(Wrapper::unwrap)
...;
The class Wrapper
might look as follows:
该类Wrapper
可能如下所示:
class Wrapper {
private final Person person;
public Wrapper(Person person) {
this.person = person;
}
public Person unwrap() {
return person;
}
public boolean equals(Object other) {
if (other instanceof Wrapper) {
return ((Wrapper) other).person.getName().equals(person.getName());
} else {
return false;
}
}
public int hashCode() {
return person.getName().hashCode();
}
}
回答by wha'eve'
An alternative would be to place the persons in a map using the name as a key:
另一种方法是使用名称作为键将人员放置在地图中:
persons.collect(Collectors.toMap(Person::getName, p -> p, (p, q) -> p)).values();
Note that the Person that is kept, in case of a duplicate name, will be the first encontered.
请注意,如果名称重复,则保留的 Person 将是第一个遇到的人。
回答by Holger
The easiest way to implement this is to jump on the sort feature as it already provides an optional Comparator
which can be created using an element's property. Then you have to filter duplicates out which can be done using a statefull Predicate
which uses the fact that for a sorted stream all equal elements are adjacent:
实现这一点的最简单方法是跳转到排序功能,因为它已经提供了一个Comparator
可以使用元素属性创建的可选项。然后,您必须过滤掉重复项,这可以使用 statefull 来完成,该 statefullPredicate
使用以下事实:对于已排序的流,所有相等的元素都是相邻的:
Comparator<Person> c=Comparator.comparing(Person::getName);
stream.sorted(c).filter(new Predicate<Person>() {
Person previous;
public boolean test(Person p) {
if(previous!=null && c.compare(previous, p)==0)
return false;
previous=p;
return true;
}
})./* more stream operations here */;
Of course, a statefull Predicate
is not thread-safe, however if that's your need you can move this logic into a Collector
and let the stream take care of the thread-safety when using your Collector
. This depends on what you want to do with the stream of distinct elements which you didn't tell us in your question.
当然,有状态Predicate
不是线程安全的,但是如果这是您的需要,您可以将此逻辑移入 aCollector
并让流在使用Collector
. 这取决于您想对您在问题中没有告诉我们的不同元素流做什么。
回答by josketres
There's a simpler approach using a TreeSet with a custom comparator.
使用带有自定义比较器的 TreeSet 有一种更简单的方法。
persons.stream()
.collect(Collectors.toCollection(
() -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName()))
));
回答by Garrett Smith
Building on @josketres's answer, I created a generic utility method:
基于@josketres 的回答,我创建了一个通用的实用方法:
You could make this more Java 8-friendly by creating a Collector.
您可以通过创建一个Collector使其更适合 Java 8 。
public static <T> Set<T> removeDuplicates(Collection<T> input, Comparator<T> comparer) {
return input.stream()
.collect(toCollection(() -> new TreeSet<>(comparer)));
}
@Test
public void removeDuplicatesWithDuplicates() {
ArrayList<C> input = new ArrayList<>();
Collections.addAll(input, new C(7), new C(42), new C(42));
Collection<C> result = removeDuplicates(input, (c1, c2) -> Integer.compare(c1.value, c2.value));
assertEquals(2, result.size());
assertTrue(result.stream().anyMatch(c -> c.value == 7));
assertTrue(result.stream().anyMatch(c -> c.value == 42));
}
@Test
public void removeDuplicatesWithoutDuplicates() {
ArrayList<C> input = new ArrayList<>();
Collections.addAll(input, new C(1), new C(2), new C(3));
Collection<C> result = removeDuplicates(input, (t1, t2) -> Integer.compare(t1.value, t2.value));
assertEquals(3, result.size());
assertTrue(result.stream().anyMatch(c -> c.value == 1));
assertTrue(result.stream().anyMatch(c -> c.value == 2));
assertTrue(result.stream().anyMatch(c -> c.value == 3));
}
private class C {
public final int value;
private C(int value) {
this.value = value;
}
}
回答by frhack
We can also use RxJava(very powerful reactive extensionlibrary)
Observable.from(persons).distinct(Person::getName)
or
或者
Observable.from(persons).distinct(p -> p.getName())
回答by Craig P. Motlin
You can use the distinct(HashingStrategy)
method in Eclipse Collections.
您可以distinct(HashingStrategy)
在Eclipse Collections 中使用该方法。
List<Person> persons = ...;
MutableList<Person> distinct =
ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));
If you can refactor persons
to implement an Eclipse Collections interface, you can call the method directly on the list.
如果可以重构persons
实现一个Eclipse Collections接口,就可以直接调用列表上的方法。
MutableList<Person> persons = ...;
MutableList<Person> distinct =
persons.distinct(HashingStrategies.fromFunction(Person::getName));
HashingStrategyis simply a strategy interface that allows you to define custom implementations of equals and hashcode.
HashingStrategy只是一个策略接口,允许您定义 equals 和 hashcode 的自定义实现。
public interface HashingStrategy<E>
{
int computeHashCode(E object);
boolean equals(E object1, E object2);
}
Note: I am a committer for Eclipse Collections.
注意:我是 Eclipse Collections 的提交者。
回答by Wojciech Górski
Extending Stuart Marks's answer, this can be done in a shorter way and without a concurrent map (if you don't need parallel streams):
扩展 Stuart Marks 的答案,这可以以更短的方式完成,无需并发映射(如果您不需要并行流):
public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
final Set<Object> seen = new HashSet<>();
return t -> seen.add(keyExtractor.apply(t));
}
Then call:
然后调用:
persons.stream().filter(distinctByKey(p -> p.getName());