web-dev-qa-db-ja.com

Java 8のプロパティに基づいてオブジェクトのリストから重複を削除します

いくつかのプロパティに基づいてオブジェクトのリストから重複を削除しようとしています。

Java 8を使用して簡単な方法でそれを行うことができます

List<Employee> employee

従業員のidプロパティに基づいて重複を削除できますか。文字列の配列リストから重複文字列を削除する投稿を見てきました。

54
Patan

Listからストリームを取得し、TreeSetに入れると、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));

例を挙げます:

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

別のアイデアは、従業員をラップするラッパーを使用し、その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());
    }
}

次に、各インスタンスをラップし、distinct()を呼び出し、それらをアンラップして、結果をリストに収集します。

List<Employee> unique = employee.stream()
                                .map(WrapperEmployee::new)
                                .distinct()
                                .map(WrapperEmployee::unwrap)
                                .collect(Collectors.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))
86
Alexis C.

リストで直接行う最も簡単な方法は

HashSet<Object> seen=new HashSet<>();
employee.removeIf(e->!seen.add(e.getID()));
  • removeIfは、指定された基準を満たす場合に要素を削除します
  • Set.addは、falseを変更しなかった場合、つまり既に値が含まれている場合はSetを返します
  • これら2つを組み合わせると、以前にidに遭遇したすべての要素(従業員)が削除されます。

もちろん、リストが要素の削除をサポートしている場合にのみ機能します。

46
Holger

このコードを試してください:

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

equalsを使用できる場合は、ストリーム内でdistinctを使用してリストをフィルターします(上記の回答を参照)。 equalsメソッドをオーバーライドできない場合、またはオーバーライドしたくない場合は、任意のプロパティに対して次の方法でストリームをfilterできます。プロパティ名の場合(プロパティIDなども同じ):

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

順序が重要でなく、並行して実行するほうがパフォーマンスが良い場合は、マップに収集してから値を取得します。

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

これは私のために働いた:

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

もちろん、equalsを実装する必要があります

3
Seba D'Agostino

別の解決策は、述語を使用することです。その後、任意のフィルターでこれを使用できます。

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(..)を使用してコレクションを構築すると例外がスローされます。

2
navins

ここには多くの良い答えがありますが、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;
      });
0
Alex

シンプルな別のバージョン

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