701 Stimmen

Java 8 Distinct nach Eigenschaft

In Java 8, wie kann ich eine Sammlung mithilfe der Stream-API filtern, indem ich die Eindeutigkeit einer Eigenschaft jedes Objekts überprüfe?

Zum Beispiel habe ich eine Liste von Person-Objekten und möchte Personen mit dem gleichen Namen entfernen,

persons.stream().distinct();

Verwendet den Standard-Gleichheitscheck für ein Person-Objekt, daher benötige ich etwas wie,

persons.stream().distinct(p -> p.getName());

Leider hat die distinct()-Methode keine solche Überladung. Ist es möglich, dies ohne Änderung des Gleichheitschecks innerhalb der Person-Klasse knapp zu tun?

941voto

Stuart Marks Punkte 119555

Betrachten Sie distinct als einen zustandsbehafteten Filter. Hier ist eine Funktion, die ein Prädikat zurückgibt, das den Zustand über das zuvor gesehene Element aufrechterhält und zurückgibt, ob das gegebene Element zum ersten Mal gesehen wurde:

public static  Predicate distinctByKey(Function keyExtractor) {
    Set seen = ConcurrentHashMap.newKeySet();
    return t -> seen.add(keyExtractor.apply(t));
}

Dann können Sie schreiben:

persons.stream().filter(distinctByKey(Person::getName))

Beachten Sie, dass wenn der Stream geordnet ist und parallel ausgeführt wird, dies ein beliebiges Element aus den Duplikaten bewahrt, anstelle des ersten, wie es distinct() tut.

(Dies ist im Wesentlichen dasselbe wie meine Antwort auf diese Frage: Java Lambda Stream Distinct() on arbitrary key?)

228voto

wha'eve' Punkte 3580

Ein alternativer Ansatz wäre, die Personen in einer Map zu platzieren und den Namen als Schlüssel zu verwenden:

persons.collect(Collectors.toMap(Person::getName, p -> p, (p, q) -> p)).values();

Beachten Sie, dass die Person, die im Falle eines doppelten Namens behalten wird, die zuerst gefundene sein wird.

144voto

nosid Punkte 47014

Sie können die Personobjekte in eine andere Klasse einwickeln, die nur die Namen der Personen vergleicht. Anschließend entpacken Sie die eingewickelten Objekte, um wieder einen Personen-Stream zu erhalten. Die Stream-Operationen könnten wie folgt aussehen:

persons.stream()
    .map(Wrapper::new)
    .distinct()
    .map(Wrapper::unwrap)
    ...;

Die Klasse Wrapper könnte wie folgt aussehen:

class Wrapper {
    private final Person person;
    public Wrapper(Person person) {
        this.person = person;
    }
    public Person unwrap() {
        return person;
    }
    public boolean equals(Object other) {
        if (other instanceof Wrapper) {
            return ((Wrapper) other).person.getName().equals(person.getName());
        } else {
            return false;
        }
    }
    public int hashCode() {
        return person.getName().hashCode();
    }
}

97voto

Santhosh Punkte 26455

Another solution, using Set. Möglicherweise nicht die ideale Lösung, aber sie funktioniert

Set set = new HashSet<>(persons.size());
persons.stream().filter(p -> set.add(p.getName())).collect(Collectors.toList());

Oder wenn Sie die Original-Liste ändern können, können Sie die removeIf Methode verwenden

persons.removeIf(p -> !set.add(p.getName()));

37voto

josketres Punkte 3301

Es gibt einen einfacheren Ansatz, einen TreeSet mit einem benutzerdefinierten Vergleicher zu verwenden.

persons.stream()
    .collect(Collectors.toCollection(
      () -> new TreeSet((p1, p2) -> p1.getName().compareTo(p2.getName())) 
));

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X