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.
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))
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ülltSet.add
gibt false
zurück, wenn die Set
nicht geändert wurde, d. h. bereits den Wert enthältNatürlich funktioniert es nur, wenn die Liste das Entfernen von Elementen unterstützt.
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();
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());
Das hat für mich funktioniert:
list.stream().distinct().collect(Collectors.toList());
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.
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()
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;
});
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);