在Java 8中基于属性从对象列表中删除重复项[duplicate]

p1iqtdky  于 2023-03-11  发布在  Java
关注(0)|答案(9)|浏览(142)

此问题在此处已有答案

Java 8 Distinct by property(34个答案)
三年前关闭了。
我正尝试从基于某些属性的对象列表中删除重复项。
我们能用Java8以一种简单的方式做到吗

List<Employee> employee

我们能不能根据雇员的id属性删除重复的字符串?我看到过从字符串数组列表中删除重复字符串的帖子。

3qpi33ja

3qpi33ja1#

您可以从List中获取一个流,并将其放入TreeSet中,从中提供一个定制的比较器,用于唯一地比较id。
如果你真的需要一个列表你可以把这个集合放回数组列表中.

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'}]

另一个想法是使用一个 Package 器来 Package 一个雇员,并使用基于其id的equals和hashcode方法:

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

然后 Package 每个示例,调用distinct(),展开它们并将结果收集到一个列表中。

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

实际上,我认为可以通过提供一个执行比较的函数来使这个 Package 器通用:

public 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将是:

.map(e -> new Wrapper<>(e, Employee::getId))
4xrmg8kj

4xrmg8kj2#

直接在列表中执行此操作的最简单方法是

HashSet<Object> seen = new HashSet<>();
employee.removeIf(e -> !seen.add(e.getID()));
  • removeIf将删除满足指定条件的元素
  • 如果Set.add未修改Set,即已包含值,则Set.add将返回false
  • 将这两个元素组合在一起,它将删除以前遇到过其id的所有元素(employees

当然,只有当列表支持移除元素时,它才起作用。

g6baxovj

g6baxovj3#

如果你可以使用equals,那么就在流中使用distinct来过滤列表(见上面的答案)。如果你不能或者不想覆盖equals方法,你可以用下面的方法对任何属性filter流,例如属性Name(属性Id等也是一样):

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

vvppvyoh4#

另一个解决方案是使用 predicate ,然后您可以在任何过滤器中使用它:

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

然后只需在任何地方重用 predicate :

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

注意:在filter的JavaDoc中,它说它接受一个无状态的Predict。实际上,即使流是并行的,它也能很好地工作。

关于其他解决方案:

1)使用.collect(Collectors.toConcurrentMap(..)).values()是一个很好的解决方案,但是如果您想排序并保持顺序,那么它会很烦人。
2)stream.removeIf(e->!seen.add(e.getID()));也是一个很好的解决方案,但是我们需要确保集合实现了removeIf,例如如果我们使用Arrays.asList(..)构造集合,它会抛出异常。

iq3niunx

iq3niunx5#

请尝试以下代码:

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

6ojccjat6#

这对我很有效:

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

当然,您需要实现equals

lmvvr0a8

lmvvr0a87#

如果顺序无关紧要,并且并行运行性能更高,请Collect to a Map,然后获取值:

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

az31mfrm8#

这里有很多很好的答案,但是我没有找到关于使用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;
      });
nkhmeac6

nkhmeac69#

另一个版本很简单

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

相关问题