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

260
10 сентября 2018, 13:40

У нас есть список объектов доменной области (Person). У объекта Person есть 3 поля: id, имя, фамилия. Задача: найти дубликаты и создать из них список (или множество, не важно), остальные объекты отбросить. Дубликатами являются объекты, у которых совпадает поле имя. Реализовать механизм необходимо через стримы. Решение:

private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
            Set<Object> seen = new HashSet<>();
            return t -> seen.add(keyExtractor.apply(t));
}
persons.stream().filter(distinctByKey(Person::getName))

Реализация работает, но есть вопросы по работе такого подхода:

  1. Судя по всему HashSet создается всего лишь единожды. Но почему? Изначально все же ожидается будто множество будет создаваться при каждой итерации. Хотелось бы увидеть развернутый ответ на этот счет.
  2. Вызывает вопрос строка return. Откуда у нас берется реализация метода apply()? Явно ведь я нигде не реализую Function и метод apply() соответственно.
Answer 1

Java для каждой лямбды и ссылки на метод в момент выполнения создаёт прокси-класс, реализующий функциональный интерфейс. Метод distinctByKey объявлен как принимающий функциональный интерфейс Function, поэтому при его вызове виртуальная машина создаст прокси-класс

class Example$$Lambda$1 implements Function<Person, String> {
    public String apply(Person person) {
        return person.getName();
    }
}

и передаст его вместо ссылки на метод Person::getName. Если убрать весь сахар, может стать понятнее, почему множество seen создаётся один раз. Ваш код в рантайме преобразовывается в приблизительно эквивалентный этому:

class Example$$Lambda$2 implements Predicate<String> {
    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<Object> 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<Person> stream = persons.iterator();
List<String> 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());
READ ALSO
Комплекс вопросов : Timber в RxJava, DataBase в RxJava, присвоение RecyclerViewAdapter DataBase объектам в RxJava

Комплекс вопросов : Timber в RxJava, DataBase в RxJava, присвоение RecyclerViewAdapter DataBase объектам в RxJava

Цель: Конвертировать список Api объектов в roomDataBase объекты и присваивать RecyclerViewAdapter уже roomDataBase объектамИ все это делать , не в основном потоке...

192
Проблемы с фокусом при нажатии на Enter в EditText

Проблемы с фокусом при нажатии на Enter в EditText

Когда тыкаю на Enter в EditText, выбирается "следующим" неправильное поле(

211
Как проверять, когда был изменен файл, перед его загрузкой с сервера?

Как проверять, когда был изменен файл, перед его загрузкой с сервера?

Загружаю в стартовой активности файл с сервера(~400КБ) Файл формата

189