webentwicklung-frage-antwort-db.com.de

Entfernen Sie Duplikate aus einer Liste von Objekten, die auf Eigenschaften in Java 8 basieren

Ich versuche, Duplikate aus einer Liste von Objekten zu entfernen, die auf einer bestimmten Eigenschaft basieren.

können wir dies auf einfache Weise mit Java 8 tun

List<Employee> employee

Können wir Duplikate daraus entfernen, basierend auf id des Mitarbeiters. Ich habe gesehen, dass Beiträge doppelte Zeichenfolgen aus der Arrayliste von Zeichenfolgen entfernen.

46
Patan

Sie können einen Stream von der List erhalten und in die TreeSet einfügen, von der aus Sie einen benutzerdefinierten Vergleicher bereitstellen, der die ID eindeutig vergleicht. 

Wenn Sie wirklich eine Liste benötigen, können Sie diese Sammlung dann in eine ArrayList zurücklegen.

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

Gegeben das Beispiel:

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

Es wird ausgegeben:

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

Eine andere Idee könnte sein, einen Wrapper zu verwenden, der einen Mitarbeiter umschließt und die Equals- und Hashcode-Methode basierend auf seiner ID hat:

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

Dann wickeln Sie jede Instanz ein, rufen Sie distinct() auf, wickeln Sie sie aus und sammeln Sie das Ergebnis in einer Liste.

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

Ich denke, Sie können diesen Wrapper generisch machen, indem Sie eine Funktion bereitstellen, die den Vergleich durchführt:

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

und das Mapping wird sein:

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

Die einfachste Möglichkeit, dies direkt in der Liste zu tun, ist

HashSet<Object> seen=new HashSet<>();
employee.removeIf(e->!seen.add(e.getID()));
  • removeIf entfernt ein Element, wenn es die angegebenen Kriterien erfüllt
  • Set.add gibt false zurück, wenn die Set nicht geändert wurde, d. h. bereits den Wert enthält
  • durch die Kombination dieser beiden Elemente werden alle Elemente (Mitarbeiter) entfernt, deren ID zuvor angetroffen wurde

Natürlich funktioniert es nur, wenn die Liste das Entfernen von Elementen unterstützt.

40
Holger

Versuchen Sie diesen Code:

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

Wenn Sie equals verwenden können, filtern Sie die Liste mithilfe von distinct innerhalb eines Streams (siehe Antworten oben). Wenn Sie die equals-Methode nicht überschreiben können oder wollen, können Sie filter den Stream auf folgende Weise für jede Eigenschaft, z. für die Eigenschaft Name (das gleiche für die Eigenschafts-ID usw.):

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

Das hat für mich funktioniert:

list.stream().distinct().collect(Collectors.toList());
2
Seba D'Agostino

Eine andere Lösung ist, ein Prädikat zu verwenden, dann können Sie dies in jedem Filter verwenden:

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

Dann das Prädikat einfach überall wieder verwenden:

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

Hinweis: Im JavaDoc-Filter, der besagt, dass ein Predicte ohne Status ist. Eigentlich funktioniert das auch, wenn der Stream parallel ist.


Über andere Lösungen:

1) Die Verwendung von .collect(Collectors.toConcurrentMap(..)).values() ist eine gute Lösung, aber es ist ärgerlich, wenn Sie die Reihenfolge sortieren und beibehalten möchten.

2) stream.removeIf(e->!seen.add(e.getID())); ist auch eine andere sehr gute Lösung. Wir müssen jedoch sicherstellen, dass die von removeIf implementierte Auflistung zum Beispiel eine Ausnahme auslöst, wenn wir die Auflistung verwenden, die Arrays.asList(..) verwendet.

2
navins

Wenn die Reihenfolge keine Rolle spielt und wenn es performanter ist, parallel ausgeführt zu werden, sammeln Sie eine Karte und erhalten dann Werte:

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

Es gibt viele gute Antworten, aber ich fand keine zur Verwendung der reduce-Methode. Für Ihren Fall können Sie es also folgendermaßen anwenden:

 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
asdasdsdf

Eine andere Version, die einfach ist

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