Подтвердить что ты не робот

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

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

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

List<Employee> employee

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

4b9b3361

Ответ 1

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

Тогда, если вам действительно нужен список, вы можете поместить его обратно в 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(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))

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

list.stream().distinct().collect(Collectors.toList());

Ответ 7

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

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(..).

Ответ 8

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

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);

Ответ 9

Здесь есть много хороших ответов, но я не нашел способ использования метода 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;
      });