根据Java 8中的属性从对象列表中删除重复项

Pat*_*tan 59 java list java-8

我试图基于某些属性从对象列表中删除重复项.

我们可以使用java 8以简单的方式完成它

List<Employee> employee
Run Code Online (Sandbox Code Playgroud)

我们可以根据id员工的财产从中删除重复项.我已经看到帖子从字符串的arraylist中删除重复的字符串.

Ale*_* C. 96

您可以从中获取流ListTreeSet从中提供自定义比较器,该比较器唯一地比较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));
Run Code Online (Sandbox Code Playgroud)

举个例子:

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

它将输出:

[Employee{id=1, name='John'}, Employee{id=2, name='Alice'}]
Run Code Online (Sandbox Code Playgroud)

另一个想法可能是使用包装员工的包装器,并使用基于其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());
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你包装每个实例,调用distinct(),打开它们并在列表中收集结果.

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

事实上,我认为你可以通过提供一个可以进行比较的函数来使这个包装器变得通用:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

并且映射将是:

.map(e -> new Wrapper<>(e, Employee::getId))
Run Code Online (Sandbox Code Playgroud)

  • 你的第一个建议是比包装器:)更好的答案.包装很明显,但第一个要好得多.我不知道收集和然后 (3认同)
  • @AvijitBarua 您可以根据需要比较任意数量的字段。`TreeSet` 构造函数将接受任何 `Comparator`。在 Java 8 及以后的版本中,`comparingInt` 方法只是一种创建比较 `int` 字段的 Comparator 的快速方法。如果你想在比较中添加另一个字段,你可以使用链接到原始比较的 `thenComparing`,这样它看起来就像 `comparingInt(Employee::getId).thenComparing(Employee::getName)`。这似乎是一篇解释比较器的好文章 - https://www.baeldung.com/java-8-comparator-comparing。 (3认同)

Hol*_*ger 51

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

HashSet<Object> seen=new HashSet<>();
employee.removeIf(e->!seen.add(e.getID()));
Run Code Online (Sandbox Code Playgroud)
  • removeIf 将删除符合指定条件的元素
  • Set.addfalse如果它没有修改Set,它将返回,即已经包含该值
  • 结合这两个,它将删除之前遇到过id的所有元素(雇员)

当然,它仅在列表支持删除元素时才有效.

  • 很好的答案!这对我来说比接受的答案更好,即使两者都很好! (3认同)
  • @ user3871754:你需要一个持有复合键并具有适当的`equals`和`hashCode`实现的对象,例如`yourList.removeIf(e - >!seen.add(Arrays.asList(e.getFirstKeyPart(),e.​​getSecondKeyPart ()));`.通过`Arrays.asList`组合键可以使用任意数量的组件,而对于少量组件,专用键类型可能更有效. (2认同)

nav*_*ins 15

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

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

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

employees.stream().filter(distinctBy(e -> e.getId));
Run Code Online (Sandbox Code Playgroud)

注意:在过滤器的 JavaDoc 中,它说它需要一个无状态的预测。实际上,即使流是并行的,这也能正常工作。


关于其他解决方案:

1)使用.collect(Collectors.toConcurrentMap(..)).values()是一个很好的解决方案,但如果你想排序和保持顺序就很烦人。

2)stream.removeIf(e->!seen.add(e.getID()));也是另一个非常好的解决方案。但是我们需要确保集合实现了removeIf,例如如果我们构造集合 use 就会抛出异常Arrays.asList(..)

  • 好的!如果你没有 ConcurrentHashSet,你可以将 `new ConcurrentHashSet` 更改为 `ConcurrentHashMap.newKeySet()` (3认同)
  • 我想知道为什么这没有添加到 java 8 库中。使用像“stream().distinctBy(Employee::Id)”这样的东西会非常方便 (2认同)

Tho*_*Tho 14

试试这段代码:

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


Rol*_*015 13

如果您可以使用equals,则distinct在流中使用过滤列表(请参阅上面的答案).如果您不能或不想覆盖该equals方法,您可以 filter通过以下方式为任何属性流,例如属性名称(属性Id等相同):

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


Xia*_*Liu 8

如果顺序无关紧要并且并行运行性能更高,则收集到地图然后获取值:

employee.stream().collect(Collectors.toConcurrentMap(Employee::getId, Function.identity(), (p, q) -> p)).values()
Run Code Online (Sandbox Code Playgroud)

  • 所以,如果你想要一个列表,我想是这样的: `employee.stream().collect(Collectors.toConcurrentMap(Employee::getId, Function.identity(), (p, q) -&gt; p)).values ().stream().collect(Collectors.toList())`。而且,关于并行,你可以在这里使用它,也可以不使用它 - 我的意思是parallelStream API? (2认同)

Seb*_*ino 8

这对我有用:

list.stream().distinct().collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

你当然需要实现equals