Удалить дубликаты из списка объектов на основе свойства в Java 8
Я пытаюсь удалить дубликаты из списка объектов на основе некоторого свойства.
мы можем сделать это простым способом, используя java 8
List<Employee> employee
Можно ли удалить дубликаты из него на основе свойства id
сотрудника. Я видел сообщения, удаляющие повторяющиеся строки, формы arraylist строки.
Ответы
Ответ 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;
});