Java 8: Уникальные элементы по свойству (Distinct by property)
Как в Java 8 отфильтровать коллекцию, используя API Stream, проверяя уникальность свойства каждого объекта?
У меня есть список объектов типа Person
, и я хочу удалить людей с одинаковыми именами.
Используя метод:
persons.stream().distinct();
Я получаю стандартную проверку на равенство для объектов Person
, поэтому мне нужно что-то вроде:
persons.stream().distinct(p -> p.getName());
К сожалению, метод distinct()
не имеет такой перегрузки. Возможно ли сделать это кратко, не изменяя проверку на равенство внутри класса Person
?
5 ответ(ов)
Альтернативным решением будет поместить объекты Person
в карту, используя имя в качестве ключа:
persons.collect(Collectors.toMap(Person::getName, p -> p, (p, q) -> p)).values();
Обратите внимание, что в случае дублирования имени в карту будет помещён первый встреченный объект Person
.
Вы можете обернуть объекты Person
в другой класс, который будет сравнивать только имена этих людей. После этого вы сможете "разобрать" обертки, чтобы снова получить поток объектов Person
. Операции со(stream) могут выглядеть следующим образом:
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();
}
}
Таким образом, вы сможете эффективно фильтровать уникальные объекты Person
по их именам.
Можно упростить задачу, используя TreeSet
с пользовательским компаратором. Вот пример кода:
persons.stream()
.collect(Collectors.toCollection(
() -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName()))
));
В этом примере мы создаем TreeSet
, который автоматически сортирует объекты класса Person
по имени. Это позволяет легко собрать элементы в упорядоченный набор, избегая дублирования и сохраняя уникальность.
Вы можете использовать коллектор groupingBy
следующим образом:
persons.collect(Collectors.groupingBy(p -> p.getName()))
.values()
.forEach(t -> System.out.println(t.get(0).getId()));
Если вам нужно создать другой стрим, вы можете использовать следующий код:
persons.collect(Collectors.groupingBy(p -> p.getName()))
.values()
.stream()
.map(l -> l.get(0));
Этот подход позволяет сгруппировать объекты по их именам и затем, в зависимости от ваших нужд, извлекать из групп нужные данные.
Вы можете использовать более современный стиль Java 8 для группировки и извлечения первых элементов из каждой группы. Вот как это можно сделать, используя Collectors.toMap
вместе с Stream
API:
List<Person> result = persons.stream()
.collect(Collectors.toMap(
Person::getName, // Группируем по имени
Function.identity(), // Значение - сам объект Person
(existing, replacement) -> existing // В случае дубликатов, оставляем первый
))
.values()
.stream()
.collect(Collectors.toList());
Такой подход более читабелен и использует возможности Java 8 для работы со стримами и коллекциями. Надеюсь, это поможет!
Java 8: Преобразование List<V> в Map<K, V>
Инициализация ArrayList в одну строчку
Как установить Java 8 на Mac
Преобразование 'ArrayList<String>' в 'String[]' в Java
Сортировка Map<Key, Value> по значениям