Вопрос-ответ

Java 8 Distinct by property

Java 8, различающаяся по свойству

В Java 8 как я могу отфильтровать коллекцию с помощью Stream API, проверяя четкость свойства каждого объекта?

Например, у меня есть список Person объектов, и я хочу удалить людей с одинаковыми именами,

persons.stream().distinct();

Будет использоваться проверка равенства по умолчанию для Person объекта, поэтому мне нужно что-то вроде,

persons.stream().distinct(p -> p.getName());

К сожалению, distinct() метод не имеет такой перегрузки. Можно ли сделать это кратко, не изменяя проверку на равенство внутри Person класса?

Переведено автоматически
Ответ 1

Считайте, что distinct это фильтр с отслеживанием состояния. Вот функция, которая возвращает предикат, который поддерживает состояние относительно того, что он видел ранее, и которая возвращает, был ли данный элемент замечен впервые:

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}

Затем вы можете написать:

persons.stream().filter(distinctByKey(Person::getName))

Обратите внимание, что если поток упорядочен и выполняется параллельно, это сохранит произвольный элемент из числа дубликатов, а не первый, как это происходит distinct().

(Это, по сути, то же самое, что и мой ответ на этот вопрос: Java Lambda Stream Distinct() по произвольному ключу?)

Ответ 2

Альтернативой было бы разместить людей на карте, используя имя в качестве ключа:

persons.collect(Collectors.toMap(Person::getName, p -> p, (p, q) -> p)).values();

Обратите внимание, что Person которое сохраняется, в случае повторяющегося имени, будет первым встреченным.

Ответ 3

Вы можете обернуть объекты person в другой класс, который сравнивает только имена персон. После этого вы разворачиваете обернутые объекты, чтобы снова получить поток person. Потоковые операции могут выглядеть следующим образом:

persons.stream()
.map(Wrapper::new)
.distinct()
.map(Wrapper::unwrap)
...;

Класс 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();
}
}
Ответ 4

Другое решение, использующее 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()));
2023-11-20 22:53 java collections java-8 java-stream