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?

8voto

Apar Punkte 111

Ich würde gerne Stuart Marks Antwort verbessern. Was passiert, wenn der Schlüssel null ist, wird eine NullPointerException auslösen. Hier ignoriere ich den null Schlüssel, indem ich eine weitere Überprüfung hinzufüge keyExtractor.apply(t)!=null.

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

}

8voto

Tomasz Linkowski Punkte 4186

Eine weitere Bibliothek, die dies unterstützt, ist jOO, sowie seine Seq.distinct(Function) Methode:

Seq.seq(persons).distinct(Person::getName).toList();

Unter der Haube macht es praktisch dasselbe wie die akzeptierte Antwort.

8voto

Abhinav Ganguly Punkte 276

Während die am meisten hochgevotete Antwort absolut die beste Antwort bzgl. Java 8 ist, ist sie gleichzeitig absolut die schlechteste in Bezug auf die Leistung. Wenn Sie wirklich eine schlechte, wenig leistungsfähige Anwendung wünschen, dann verwenden Sie sie ruhig. Die einfache Anforderung, einen eindeutigen Satz von Personennamen zu extrahieren, kann allein durch "For-Each" und ein "Set" erreicht werden. Die Dinge werden noch schlimmer, wenn die Liste größer als 10 ist.

Angenommen, Sie haben eine Sammlung von 20 Objekten, wie folgt:

public static final List testList = Arrays.asList(
            new SimpleEvent("Tom"), new SimpleEvent("Dick"),new SimpleEvent("Harry"),new SimpleEvent("Tom"),
            new SimpleEvent("Dick"),new SimpleEvent("Huckle"),new SimpleEvent("Berry"),new SimpleEvent("Tom"),
            new SimpleEvent("Dick"),new SimpleEvent("Moses"),new SimpleEvent("Chiku"),new SimpleEvent("Cherry"),
            new SimpleEvent("Roses"),new SimpleEvent("Moses"),new SimpleEvent("Chiku"),new SimpleEvent("gotya"),
            new SimpleEvent("Gotye"),new SimpleEvent("Nibble"),new SimpleEvent("Berry"),new SimpleEvent("Jibble"));

Wo Ihr Objekt SimpleEvent so aussieht:

public class SimpleEvent {

private String name;
private String type;

public SimpleEvent(String name) {
    this.name = name;
    this.type = "type_"+name;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public String getType() {
    return type;
}

public void setType(String type) {
    this.type = type;
}
}

Und zum Testen haben Sie einen JMH-Code wie diesen (Bitte beachten Sie, dass ich dieselbe distinctByKey-Predicate aus der akzeptierten Antwort verwende):

@Benchmark
@OutputTimeUnit(TimeUnit.SECONDS)
public void aStreamBasedUniqueSet(Blackhole blackhole) throws Exception{

    Set uniqueNames = testList
            .stream()
            .filter(distinctByKey(SimpleEvent::getName))
            .map(SimpleEvent::getName)
            .collect(Collectors.toSet());
    blackhole.consume(uniqueNames);
}

@Benchmark
@OutputTimeUnit(TimeUnit.SECONDS)
public void aForEachBasedUniqueSet(Blackhole blackhole) throws Exception{
    Set uniqueNames = new HashSet<>();

    for (SimpleEvent event : testList) {
        uniqueNames.add(event.getName());
    }
    blackhole.consume(uniqueNames);
}

public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder()
            .include(MyBenchmark.class.getSimpleName())
            .forks(1)
            .mode(Mode.Throughput)
            .warmupBatchSize(3)
            .warmupIterations(3)
            .measurementIterations(3)
            .build();

    new Runner(opt).run();
}

Dann haben Sie Benchmark-Ergebnisse wie diese:

Benchmark                                  Mode  Samples        Score  Score error  Units
c.s.MyBenchmark.aForEachBasedUniqueSet    thrpt        3  2635199.952  1663320.718  ops/s
c.s.MyBenchmark.aStreamBasedUniqueSet     thrpt        3   729134.695   895825.697  ops/s

Und wie Sie sehen können, ist ein einfacher For-Each in Bezug auf Durchsatz 3-mal besser und weist einen geringeren Fehlerwert auf im Vergleich zu Java 8 Stream.

Je höher der Durchsatz, desto besser die Leistung

7voto

saran3h Punkte 10035

Dies funktioniert wie ein Zauber:

  1. Gruppieren der Daten nach eindeutigem Schlüssel, um eine Map zu erstellen.
  2. Rückgabe des ersten Objekts von jedem Wert der Map (Es könnten mehrere Personen den gleichen Namen haben).

    persons.stream() .collect(groupingBy(Person::getName)) .values() .stream() .flatMap(values -> values.stream().limit(1)) .collect(toList());

7voto

Set set = new HashSet<>();
list
        .stream()
        .filter(it -> set.add(it.getYourProperty()))
        .forEach(it -> ...);

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