it-swarm.com.ru

Удалить дубликаты из списка объектов на основе свойства в Java 8

Я пытаюсь удалить дубликаты из списка объектов на основе какого-либо свойства.

мы можем сделать это простым способом, используя Java 8

List<Employee> employee

Можем ли мы удалить из него дубликаты, основываясь на свойстве id сотрудника. Я видел сообщения, удаляющие повторяющиеся строки из массива строк.

46
Patan

Вы можете получить поток из List и вставить в TreeSet, из которого вы предоставляете пользовательский компаратор, который уникальным образом сравнивает id. 

Затем, если вам действительно нужен список, вы можете поместить эту коллекцию обратно в ArrayList.

import static Java.util.Comparator.comparingInt;
import static Java.util.stream.Collectors.collectingAndThen;
import static Java.util.stream.Collectors.toCollection;

...
List<Employee> unique = employee.stream()
                                .collect(collectingAndThen(toCollection(() -> new TreeSet<>(comparingInt(Employee::getId))),
                                                           ArrayList::new));

Приведенный пример:

List<Employee> employee = Arrays.asList(new Employee(1, "John"), new Employee(1, "Bob"), new Employee(2, "Alice"));

Это выведет:

[Employee{id=1, name='John'}, Employee{id=2, name='Alice'}]

Другая идея может состоять в том, чтобы использовать оболочку, которая обертывает сотрудника и имеет метод equals и hashcode, основанный на его id:

class WrapperEmployee {
    private Employee e;

    public WrapperEmployee(Employee e) {
        this.e = e;
    }

    public Employee unwrap() {
        return this.e;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        WrapperEmployee that = (WrapperEmployee) o;
        return Objects.equals(e.getId(), that.e.getId());
    }

    @Override
    public int hashCode() {
        return Objects.hash(e.getId());
    }
}

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

List<Employee> unique = employee.stream()
                                .map(WrapperEmployee::new)
                                .distinct()
                                .map(WrapperEmployee::unwrap)
                                .collect(Collectors.toList());

На самом деле, я думаю, что вы можете сделать эту обертку универсальной, предоставив функцию, которая будет выполнять сравнение:

class Wrapper<T, U> {
    private T t;
    private Function<T, U> equalityFunction;

    public Wrapper(T t, Function<T, U> equalityFunction) {
        this.t = t;
        this.equalityFunction = equalityFunction;
    }

    public T unwrap() {
        return this.t;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        @SuppressWarnings("unchecked")
        Wrapper<T, U> that = (Wrapper<T, U>) o;
        return Objects.equals(equalityFunction.apply(this.t), that.equalityFunction.apply(that.t));
    }

    @Override
    public int hashCode() {
        return Objects.hash(equalityFunction.apply(this.t));
    }
}

и отображение будет:

.map(e -> new Wrapper<>(e, Employee::getId))
79
Alexis C.

Самый простой способ сделать это прямо в списке

HashSet<Object> seen=new HashSet<>();
employee.removeIf(e->!seen.add(e.getID()));
  • removeIf удалит элемент, если он соответствует указанным критериям
  • Set.add вернет false, если он не изменил Set, т.е. уже содержит значение
  • комбинируя эти два, он удалит все элементы (сотрудников), чьи идентификаторы встречались ранее

Конечно, это работает только в том случае, если список поддерживает удаление элементов.

40
Holger

Попробуйте этот код:

Collection<Employee> nonDuplicatedEmployees = employees.stream()
   .<Map<Integer, Employee>> collect(HashMap::new,(m,e)->m.put(e.getId(), e), Map::putAll)
   .values();
12
Tho

Если вы можете использовать equals, то отфильтруйте список с помощью distinct в потоке (см. Ответы выше). Если вы не можете или не хотите переопределять метод equals, вы можете filter поток следующим образом для любого свойства, например, для имени свойства (то же самое для идентификатора свойства и т. д.):

Set<String> nameSet = new HashSet<>();
List<Employee> employeesDistinctByName = employees.stream()
            .filter(e -> nameSet.add(e.getName()))
            .collect(Collectors.toList());
8
Rolch2015

Это сработало для меня:

list.stream().distinct().collect(Collectors.toList());
2
Seba D'Agostino

Другое решение состоит в том, чтобы использовать Предикат, тогда вы можете использовать это в любом фильтре:

public static <T> Predicate<T> distinctBy(Function<? super T, ?> f) {
  Set<Object> objects = new ConcurrentHashSet<>();
  return t -> objects.add(f.apply(t));
}

Тогда просто используйте предикат где угодно:

employees.stream().filter(distinctBy(e -> e.getId));

Примечание: в JavaDoc фильтра, который говорит, что он принимает Predicte без состояния. На самом деле, это работает нормально, даже если поток параллельный.


О других решениях:

1) Использование .collect(Collectors.toConcurrentMap(..)).values() - хорошее решение, но оно раздражает, если вы хотите отсортировать и сохранить порядок.

2) stream.removeIf(e->!seen.add(e.getID())); - это еще одно очень хорошее решение. Но нам нужно убедиться, что в коллекции реализован метод removeIf, например, он выдаст исключение, если мы создадим коллекцию с использованием Arrays.asList(..).

2
navins

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

employee.stream().collect(Collectors.toConcurrentMap(Employee::getId, Function.identity(), (p, q) -> p)).values()
2
Allen Liu

Здесь есть много хороших ответов, но я не нашел ответа об использовании метода reduce. Таким образом, для вашего случая вы можете применить его следующим образом:

 List<Employee> employeeList = employees.stream()
      .reduce(new ArrayList<>(), (List<Employee> accumulator, Employee employee) ->
      {
        if (accumulator.stream().noneMatch(emp -> emp.getId().equals(employee.getId())))
        {
          accumulator.add(employee);
        }
        return accumulator;
      }, (acc1, acc2) ->
      {
        acc1.addAll(acc2);
        return acc1;
      });
0
asdasdsdf

Другая версия, которая проста

BiFunction<TreeSet<Employee>,List<Employee> ,TreeSet<Employee>> appendTree = (y,x) -> (y.addAll(x))? y:y;

TreeSet<Employee> outputList = appendTree.apply(new TreeSet<Employee>(Comparator.comparing(p->p.getId())),personList);
0
zawhtut