在Java 8中,我如何使用流API通过检查每个对象的属性的清晰度来过滤一个集合?
例如,我有一个Person对象列表,我想删除同名的人,
persons.stream().distinct();
将对Person对象使用默认的相等性检查,所以我需要这样的东西,
persons.stream().distinct(p -> p.getName());
不幸的是,distinct()方法没有这样的重载。如果不修改Person类内部的相等检查,是否可以简洁地做到这一点?
在Java 8中,我如何使用流API通过检查每个对象的属性的清晰度来过滤一个集合?
例如,我有一个Person对象列表,我想删除同名的人,
persons.stream().distinct();
将对Person对象使用默认的相等性检查,所以我需要这样的东西,
persons.stream().distinct(p -> p.getName());
不幸的是,distinct()方法没有这样的重载。如果不修改Person类内部的相等检查,是否可以简洁地做到这一点?
当前回答
另一个解决方案,使用Set。也许不是理想的解决方案,但有效吗
Set<String> set = new HashSet<>(persons.size());
persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());
或者如果可以修改原始列表,可以使用removeIf方法
persons.removeIf(p -> !set.add(p.getName()));
其他回答
另一个支持这个的库是jOOλ,它的Seq.distinct(Function<T,U>)方法:
Seq.seq(persons).distinct(Person::getName).toList();
实际上,它所做的事情与公认的答案几乎相同。
处理null的顶部答案的变体:
public static <T, K> Predicate<T> distinctBy(final Function<? super T, K> getKey) {
val seen = ConcurrentHashMap.<Optional<K>>newKeySet();
return obj -> seen.add(Optional.ofNullable(getKey.apply(obj)));
}
在我的测试中:
assertEquals(
asList("a", "bb"),
Stream.of("a", "b", "bb", "aa").filter(distinctBy(String::length)).collect(toList()));
assertEquals(
asList(5, null, 2, 3),
Stream.of(5, null, 2, null, 3, 3, 2).filter(distinctBy(x -> x)).collect(toList()));
val maps = asList(
hashMapWith(0, 2),
hashMapWith(1, 2),
hashMapWith(2, null),
hashMapWith(3, 1),
hashMapWith(4, null),
hashMapWith(5, 2));
assertEquals(
asList(0, 2, 3),
maps.stream()
.filter(distinctBy(m -> m.get("val")))
.map(m -> m.get("i"))
.collect(toList()));
您可以在Eclipse Collections中使用distinct(HashingStrategy)方法。
List<Person> persons = ...;
MutableList<Person> distinct =
ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));
如果可以重构人员以实现Eclipse Collections接口,则可以直接调用列表上的方法。
MutableList<Person> persons = ...;
MutableList<Person> distinct =
persons.distinct(HashingStrategies.fromFunction(Person::getName));
HashingStrategy只是一个策略接口,允许您定义equals和hashcode的自定义实现。
public interface HashingStrategy<E>
{
int computeHashCode(E object);
boolean equals(E object1, E object2);
}
注意:我是Eclipse Collections的提交者。
这就像一个魅力:
按唯一键对数据进行分组,形成映射。 返回映射的每个值的第一个对象(可以有多个具有相同名称的人)。
persons.stream()
.collect(groupingBy(Person::getName))
.values()
.stream()
.flatMap(values -> values.stream().limit(1))
.collect(toList());
有很多方法,这一个也会有帮助-简单,干净和清晰
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(11, "Ravi"));
employees.add(new Employee(12, "Stalin"));
employees.add(new Employee(23, "Anbu"));
employees.add(new Employee(24, "Yuvaraj"));
employees.add(new Employee(35, "Sena"));
employees.add(new Employee(36, "Antony"));
employees.add(new Employee(47, "Sena"));
employees.add(new Employee(48, "Ravi"));
List<Employee> empList = new ArrayList<>(employees.stream().collect(
Collectors.toMap(Employee::getName, obj -> obj,
(existingValue, newValue) -> existingValue))
.values());
empList.forEach(System.out::println);
// Collectors.toMap(
// Employee::getName, - key (the value by which you want to eliminate duplicate)
// obj -> obj, - value (entire employee object)
// (existingValue, newValue) -> existingValue) - to avoid illegalstateexception: duplicate key
Output - toString()重载
Employee{id=35, name='Sena'}
Employee{id=12, name='Stalin'}
Employee{id=11, name='Ravi'}
Employee{id=24, name='Yuvaraj'}
Employee{id=36, name='Antony'}
Employee{id=23, name='Anbu'}