Страницы

Поиск по вопросам

четверг, 16 мая 2019 г.

Удаление дубликатов по полю объекта Stream API

У нас есть список объектов доменной области (Person). У объекта Person есть 3 поля: id, имя, фамилия. Задача: найти дубликаты и создать из них список (или множество, не важно), остальные объекты отбросить. Дубликатами являются объекты, у которых совпадает поле имя. Реализовать механизм необходимо через стримы. Решение:
private static Predicate distinctByKey(Function keyExtractor) { Set seen = new HashSet<>(); return t -> seen.add(keyExtractor.apply(t)); }
persons.stream().filter(distinctByKey(Person::getName))
Реализация работает, но есть вопросы по работе такого подхода:
Судя по всему HashSet создается всего лишь единожды. Но почему? Изначально все же ожидается будто множество будет создаваться при каждой итерации. Хотелось бы увидеть развернутый ответ на этот счет. Вызывает вопрос строка return. Откуда у нас берется реализация метода apply()? Явно ведь я нигде не реализую Function и метод apply() соответственно.


Ответ

Java для каждой лямбды и ссылки на метод в момент выполнения создаёт прокси-класс, реализующий функциональный интерфейс. Метод distinctByKey объявлен как принимающий функциональный интерфейс Function, поэтому при его вызове виртуальная машина создаст прокси-класс
class Example$$Lambda$1 implements Function { public String apply(Person person) { return person.getName(); } }
и передаст его вместо ссылки на метод Person::getName. Если убрать весь сахар, может стать понятнее, почему множество seen создаётся один раз. Ваш код в рантайме преобразовывается в приблизительно эквивалентный этому:
class Example$$Lambda$2 implements Predicate { private final java.util.Set arg$1;
public boolean test(String name) { return arg$1.add(name); } }
private static Example$$Lambda$2 distinctByKey(Example$$Lambda$1 keyExtractor) { Set seen = new HashSet<>(); // Здесь seen "магическим" образом присваивается // полю arg$1 возвращаемого объекта return new Example$$Lambda$2(); }
Example$$Lambda$1 keyExtractor = new Example$$Lambda$1(); Example$$Lambda$2 predicate = distinctByKey(keyExtractor); Iterator stream = persons.iterator(); List result = new ArrayList<>(); while (stream.hasNext()) { Person person = stream.next(); String key = keyExtractor.apply(person); boolean duplicate = predicate.test(key); if (!duplicate) { result.add(key); } }
И кстати, операция filter оставляет в потоке элементы соответствующие предикату, а метод множества add возвращает true для тех элементов, которых в множестве не было. То есть вы наоборот убираете дубликаты из потока. Вам надо инвертировать предикат:
persons.stream() .filter(distinctByKey(Person::getName).negate());

Комментариев нет:

Отправить комментарий