基于 Java 8 中的属性从对象列表中删除重复项
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29670116/
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
Remove duplicates from a list of objects based on property in Java 8
提问by Patan
I am trying to remove duplicates from a List of objects based on some property.
我正在尝试从基于某些属性的对象列表中删除重复项。
can we do it in a simple way using java 8
我们可以使用 java 8 以简单的方式做到这一点吗
List<Employee> employee
Can we remove duplicates from it based on id
property of employee. I have seen posts removing duplicate strings form arraylist of string.
我们可以根据id
员工的财产从中删除重复项吗?我见过从字符串数组列表中删除重复字符串的帖子。
采纳答案by Alexis C.
You can get a stream from the List
and put in in the TreeSet
from which you provide a custom comparator that compares id uniquely.
您可以从 中获取流List
并将其放入TreeSet
从中提供唯一比较 id 的自定义比较器。
Then if you really need a list you can put then back this collection into an ArrayList.
然后如果你真的需要一个列表,你可以把这个集合放回一个 ArrayList 中。
import static java.util.Comparator.comparingInt;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toCollection;
...
List<Employee> unique = employee.stream()
.collect(collectingAndThen(toCollection(() -> new TreeSet<>(comparingInt(Employee::getId))),
ArrayList::new));
Given the example:
举个例子:
List<Employee> employee = Arrays.asList(new Employee(1, "John"), new Employee(1, "Bob"), new Employee(2, "Alice"));
It will output:
它会输出:
[Employee{id=1, name='John'}, Employee{id=2, name='Alice'}]
Another idea could be to use a wrapper that wraps an employee and have the equals and hashcode method based with its id:
另一个想法可能是使用一个包装器来包装员工,并根据其 id 使用 equals 和 hashcode 方法:
class WrapperEmployee {
private Employee e;
public WrapperEmployee(Employee e) {
this.e = e;
}
public Employee unwrap() {
return this.e;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WrapperEmployee that = (WrapperEmployee) o;
return Objects.equals(e.getId(), that.e.getId());
}
@Override
public int hashCode() {
return Objects.hash(e.getId());
}
}
Then you wrap each instance, call distinct()
, unwrap them and collect the result in a list.
然后你包装每个实例,调用distinct()
,解开它们并将结果收集在一个列表中。
List<Employee> unique = employee.stream()
.map(WrapperEmployee::new)
.distinct()
.map(WrapperEmployee::unwrap)
.collect(Collectors.toList());
In fact, I think you can make this wrapper generic by providing a function that will do the comparison:
事实上,我认为您可以通过提供一个进行比较的函数来使这个包装器通用:
class Wrapper<T, U> {
private T t;
private Function<T, U> equalityFunction;
public Wrapper(T t, Function<T, U> equalityFunction) {
this.t = t;
this.equalityFunction = equalityFunction;
}
public T unwrap() {
return this.t;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
Wrapper<T, U> that = (Wrapper<T, U>) o;
return Objects.equals(equalityFunction.apply(this.t), that.equalityFunction.apply(that.t));
}
@Override
public int hashCode() {
return Objects.hash(equalityFunction.apply(this.t));
}
}
and the mapping will be:
映射将是:
.map(e -> new Wrapper<>(e, Employee::getId))
回答by Tho
Try this code:
试试这个代码:
Collection<Employee> nonDuplicatedEmployees = employees.stream()
.<Map<Integer, Employee>> collect(HashMap::new,(m,e)->m.put(e.getId(), e), Map::putAll)
.values();
回答by Holger
The easiest way to do it directly in the list is
直接在列表中执行的最简单方法是
HashSet<Object> seen=new HashSet<>();
employee.removeIf(e->!seen.add(e.getID()));
removeIf
will remove an element if it meets the specified criteriaSet.add
will returnfalse
if it did not modify theSet
, i.e. already contains the value- combining these two, it will remove all elements (employees) whose id has been encountered before
removeIf
如果一个元素满足指定的条件,将删除它Set.add
false
如果它没有修改Set
,即已经包含该值,则将返回- 结合这两者,它将删除之前遇到过 id 的所有元素(员工)
Of course, it only works if the list supports removal of elements.
当然,它只有在列表支持删除元素时才有效。
回答by zawhtut
Another version which is simple
另一个简单的版本
BiFunction<TreeSet<Employee>,List<Employee> ,TreeSet<Employee>> appendTree = (y,x) -> (y.addAll(x))? y:y;
TreeSet<Employee> outputList = appendTree.apply(new TreeSet<Employee>(Comparator.comparing(p->p.getId())),personList);
回答by Xiao Liu
If order does not matter and when it's more performant to run in parallel, Collect to a Map and then get values:
如果顺序无关紧要并且并行运行性能更高,则收集到地图然后获取值:
employee.stream().collect(Collectors.toConcurrentMap(Employee::getId, Function.identity(), (p, q) -> p)).values()
回答by Alex
There are a lot of good answers here but I didn't find the one about using reduce
method. So for your case, you can apply it in following way:
这里有很多很好的答案,但我没有找到关于使用reduce
方法的答案。因此,对于您的情况,您可以通过以下方式应用它:
List<Employee> employeeList = employees.stream()
.reduce(new ArrayList<>(), (List<Employee> accumulator, Employee employee) ->
{
if (accumulator.stream().noneMatch(emp -> emp.getId().equals(employee.getId())))
{
accumulator.add(employee);
}
return accumulator;
}, (acc1, acc2) ->
{
acc1.addAll(acc2);
return acc1;
});
回答by Sebastian D'Agostino
This worked for me:
这对我有用:
list.stream().distinct().collect(Collectors.toList());
You need to implement equals, of course
你当然需要实现equals
回答by Rolch2015
If you can make use of equals
, then filter the list by using distinct
within a stream (see answers above). If you can not or don't want to override the equals
method, you can filter
the stream in the following way for any property, e.g. for the property Name (the same for the property Id etc.):
如果您可以使用equals
,则通过distinct
在流中使用来过滤列表(请参阅上面的答案)。如果您不能或不想覆盖该equals
方法,您可以 filter
通过以下方式对任何属性进行流传输,例如对于属性 Name(属性 Id 等相同):
Set<String> nameSet = new HashSet<>();
List<Employee> employeesDistinctByName = employees.stream()
.filter(e -> nameSet.add(e.getName()))
.collect(Collectors.toList());
回答by navins
Another solution is to use a Predicate, then you can use this in any filter:
另一种解决方案是使用谓词,然后您可以在任何过滤器中使用它:
public static <T> Predicate<T> distinctBy(Function<? super T, ?> f) {
Set<Object> objects = new ConcurrentHashSet<>();
return t -> objects.add(f.apply(t));
}
Then simply reuse the predicate anywhere:
然后只需在任何地方重用谓词:
employees.stream().filter(distinctBy(e -> e.getId));
Note: in the JavaDoc of filter, which says it takes a stateless Predicte. Actually, this works fine even if the stream is parallel.
注意:在过滤器的 JavaDoc 中,它说它需要一个无状态的预测。实际上,即使流是并行的,这也能正常工作。
About other solutions:
关于其他解决方案:
1) Using .collect(Collectors.toConcurrentMap(..)).values()
is a good solution, but it's annoying if you want to sort and keep the order.
1)使用.collect(Collectors.toConcurrentMap(..)).values()
是一个很好的解决方案,但如果你想排序和保持顺序就很烦人。
2) stream.removeIf(e->!seen.add(e.getID()));
is also another very good solution. But we need to make sure the collection implemented removeIf, for example it will throw exception if we construct the collection use Arrays.asList(..)
.
2)stream.removeIf(e->!seen.add(e.getID()));
也是另一个非常好的解决方案。但是我们需要确保集合实现了removeIf,例如如果我们构造集合 use 就会抛出异常Arrays.asList(..)
。