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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29670116/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 08:24:59  来源:igfitidea点击:

Remove duplicates from a list of objects based on property in Java 8

javalistjava-8

提问by Patan

I am trying to remove duplicates from a List of objects based on some property.

我正在尝试从基于某些属性的对象列表中删除重复项。

can we do it in a simple way using java 8

我们可以使用 java 8 以简单的方式做到这一点吗

List<Employee> employee

Can we remove duplicates from it based on idproperty of employee. I have seen posts removing duplicate strings form arraylist of string.

我们可以根据id员工的财产从中删除重复项吗?我见过从字符串数组列表中删除重复字符串的帖子。

采纳答案by Alexis C.

You can get a stream from the Listand put in in the TreeSetfrom which you provide a custom comparator that compares id uniquely.

您可以从 中获取流List并将其放入TreeSet从中提供唯一比较 id 的自定义比较器。

Then if you really need a list you can put then back this collection into an ArrayList.

然后如果你真的需要一个列表,你可以把这个集合放回一个 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));

Given the example:

举个例子:

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

It will output:

它会输出:

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


Another idea could be to use a wrapper that wraps an employee and have the equals and hashcode method based with its id:

另一个想法可能是使用一个包装器来包装员工,并根据其 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());
    }
}

Then you wrap each instance, call distinct(), unwrap them and collect the result in a list.

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

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


In fact, I think you can make this wrapper generic by providing a function that will do the comparison:

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

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

and the mapping will be:

映射将是:

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

回答by Tho

Try this code:

试试这个代码:

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

回答by Holger

The easiest way to do it directly in the list is

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

HashSet<Object> seen=new HashSet<>();
employee.removeIf(e->!seen.add(e.getID()));
  • removeIfwill remove an element if it meets the specified criteria
  • Set.addwill return falseif it did not modify the Set, i.e. already contains the value
  • combining these two, it will remove all elements (employees) whose id has been encountered before
  • removeIf如果一个元素满足指定的条件,将删除它
  • Set.addfalse如果它没有修改Set,即已经包含该值,则将返回
  • 结合这两者,它将删除之前遇到过 id 的所有元素(员工)

Of course, it only works if the list supports removal of elements.

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

回答by zawhtut

Another version which is simple

另一个简单的版本

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

回答by Xiao Liu

If order does not matter and when it's more performant to run in parallel, Collect to a Map and then get values:

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

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

回答by Alex

There are a lot of good answers here but I didn't find the one about using reducemethod. So for your case, you can apply it in following way:

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

回答by Sebastian D'Agostino

This worked for me:

这对我有用:

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

You need to implement equals, of course

你当然需要实现equals

回答by Rolch2015

If you can make use of equals, then filter the list by using distinctwithin a stream (see answers above). If you can not or don't want to override the equalsmethod, you can filterthe stream in the following way for any property, e.g. for the property Name (the same for the property Id etc.):

如果您可以使用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());

回答by navins

Another solution is to use a Predicate, then you can use this in any filter:

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

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

Then simply reuse the predicate anywhere:

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

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

Note: in the JavaDoc of filter, which says it takes a stateless Predicte. Actually, this works fine even if the stream is parallel.

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


About other solutions:


关于其他解决方案:

1) Using .collect(Collectors.toConcurrentMap(..)).values()is a good solution, but it's annoying if you want to sort and keep the order.

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

2) stream.removeIf(e->!seen.add(e.getID()));is also another very good solution. But we need to make sure the collection implemented removeIf, for example it will throw exception if we construct the collection use Arrays.asList(..).

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