Java 8 Nach Eigenschaft unterscheiden

455

Wie kann ich in Java 8 eine Sammlung mithilfe der StreamAPI filtern, indem ich die Unterscheidbarkeit einer Eigenschaft jedes Objekts überprüfe?

Zum Beispiel habe ich eine Liste von PersonObjekten und möchte Personen mit demselben Namen entfernen.

persons.stream().distinct();

Verwendet die Standard-Gleichheitsprüfung für ein PersonObjekt, also brauche ich so etwas wie:

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

Leider hat die distinct()Methode keine solche Überlastung. Ist Persones möglich, dies kurz und bündig zu tun, ohne die Gleichheitsprüfung innerhalb der Klasse zu ändern?

RichK
quelle

Antworten:

556

Betrachten Sie distinctals Stateful Filter . Hier ist eine Funktion, die ein Prädikat zurückgibt, das den Status über das zuvor Gesehene beibehält und zurückgibt, ob das angegebene Element zum ersten Mal gesehen wurde:

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    Set<Object> 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 und parallel ausgeführt wird, ein beliebiges Element aus den Duplikaten anstelle des ersten beibehalten wird , wie dies auch der distinct()Fall ist.

(Dies entspricht im Wesentlichen meiner Antwort auf diese Frage: Java Lambda Stream Distinct () auf einem beliebigen Schlüssel? )

Stuart Marks
quelle
27
Ich denke, für eine bessere Kompatibilität sollte das Argument Function<? super T, ?>nicht sein Function<? super T, Object>. Es sollte auch beachtet werden, dass diese Lösung für geordnete parallele Streams nicht garantiert, welches Objekt extrahiert wird (im Gegensatz zu normal distinct()). Auch für sequentielle Streams entsteht ein zusätzlicher Aufwand für die Verwendung von CHM (der in der @ nosid-Lösung nicht vorhanden ist). Schließlich verstößt diese Lösung gegen den filterMethodenvertrag, dessen Prädikat zustandslos sein muss, wie in JavaDoc angegeben. Trotzdem positiv bewertet.
Tagir Valeev
3
@java_newbie Die von zurückgegebene Predicate-Instanz distinctByKeyhat keine Ahnung, ob sie in einem parallelen Stream verwendet wird. Es verwendet CHM für den Fall, dass es parallel verwendet wird, obwohl dies im sequentiellen Fall, wie Tagir Valeev oben erwähnt, zusätzlichen Aufwand verursacht.
Stuart Marks
5
@holandaGo Es schlägt fehl, wenn Sie die von zurückgegebene Predicate-Instanz speichern und wiederverwenden distinctByKey. Es funktioniert jedoch, wenn Sie distinctByKeyjedes Mal aufrufen , sodass jedes Mal eine neue Prädikatinstanz erstellt wird.
Stuart Marks
3
@Chinmay nein, sollte es nicht. Wenn Sie verwenden .filter(distinctByKey(...)). Die Methode wird einmal ausgeführt und das Prädikat zurückgegeben. Grundsätzlich wird die Karte bereits wiederverwendet, wenn Sie sie in einem Stream ordnungsgemäß verwenden. Wenn Sie die Karte statisch machen würden, würde die Karte für alle Verwendungen freigegeben. Wenn Sie also zwei Streams verwenden distinctByKey(), verwenden beide dieselbe Map, was nicht das ist, was Sie wollen.
g00glen00b
3
Das ist sooo klug und völlig nicht offensichtlich. Im Allgemeinen handelt es sich um ein zustandsbehaftetes Lambda, und der zugrunde liegende Wert CallSitewird mit der get$LambdaMethode verknüpft. Dadurch wird immer eine neue Instanz zurückgegeben Predicate, aber diese Instanzen haben nach meinem Verständnis dieselbe mapund dieselbe function. Sehr schön!
Eugene
152

Eine Alternative wäre, die Personen mit dem Namen als Schlüssel auf einer Karte zu platzieren:

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

Beachten Sie, dass die Person, die im Falle eines doppelten Namens aufbewahrt wird, die erste ist, die in Kontakt kommt.

was hast du?
quelle
23
@skiwi: Glaubst du, es gibt eine Möglichkeit, distinct()ohne diesen Aufwand zu implementieren ? Wie würde eine Implementierung wissen, ob sie ein Objekt zuvor gesehen hat, ohne sich tatsächlich an alle unterschiedlichen Werte zu erinnern, die sie gesehen hat? Der Overhead von toMapund distinctist also sehr wahrscheinlich der gleiche.
Holger
1
@Holger Ich habe mich dort vielleicht geirrt, da ich nicht daran gedacht hatte, dass der Overhead distinct()selbst entsteht.
Skiwi
2
Und offensichtlich bringt es die ursprüngliche Reihenfolge der Liste
Philipp
10
@Philipp: könnte durch Wechsel zupersons.collect(toMap(Person::getName, p -> p, (p, q) -> p, LinkedHashMap::new)).values();
Holger
1
@ DanielEarwicker Bei dieser Frage geht es um "Unterscheiden nach Eigentum". Es würde erfordern, dass der Stream nach derselben Eigenschaft sortiert wird , um ihn nutzen zu können. Erstens hat das OP nie angegeben, dass der Stream überhaupt sortiert ist. Zweitens können Streams nicht erkennen, ob sie nach einer bestimmten Eigenschaft sortiert sind . Drittens gibt es keine echte Stream-Operation "nach Eigenschaften unterscheiden", um das zu tun, was Sie vorschlagen. In der Praxis gibt es nur zwei Möglichkeiten, um einen solchen sortierten Stream zu erhalten. Eine sortierte Quelle ( TreeSet), die ohnehin schon verschieden ist oder sortedim Stream, die auch alle Elemente puffert.
Holger
101

Sie können die Personenobjekte in eine andere Klasse einbinden, die nur die Namen der Personen vergleicht. Anschließend packen Sie die verpackten Objekte aus, um einen Personen-Stream erneut zu erhalten. Die Stream-Operationen könnten wie folgt aussehen:

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

Die Klasse Wrapperkö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();
    }
}
nosid
quelle
13
Dies nennt man die Schwartzsche Transformation
Stuart Caie
5
@StuartCaie Nicht wirklich ... es gibt keine Memoisierung, und der Punkt ist nicht die Leistung, sondern die Anpassung an die vorhandene API.
Marko Topolnik
6
com.google.common.base.Equivalence.wrap (S) und com.google.common.base.Equivalence.Wrapper.get () könnten ebenfalls helfen.
bjmi
Sie können die Wrapper-Klasse durch eine Schlüsselextraktionsfunktion generisch und parametrisiert machen.
Lii
Die equalsMethode kann vereinfacht werdenreturn other instanceof Wrapper && ((Wrapper) other).person.getName().equals(person.getName());
Holger
55

Eine andere Lösung mit Set. Vielleicht nicht die ideale Lösung, aber es funktioniert

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

Wenn Sie die ursprüngliche Liste ändern können, können Sie die Methode removeIf verwenden

persons.removeIf(p -> !set.add(p.getName()));
Santhosh
quelle
2
Dies ist die beste Antwort, wenn Sie keine Bibliotheken von Drittanbietern verwenden!
Manoj Shrestha
5
unter Verwendung der genialen Idee, dass Set.add true zurückgibt, wenn dieses Set das angegebene Element nicht bereits enthielt. +1
Luvie
Ich glaube, diese Methode funktioniert nicht für die parallele Stream-Verarbeitung, da sie nicht threadsicher ist.
LoBo
@ LoBo Wahrscheinlich nicht. Dies ist nur eine Idee, die für einfache Fälle funktioniert. Benutzer können es aus Gründen der Thread-Sicherheit / Parallelität erweitern.
Santhosh
Interessanter Ansatz, sieht aber wie ein Anti-Pattern aus, um eine externe Sammlung (Set) zu ändern, während ein Stream nach einer anderen Sammlung (Personen)
gefiltert wird
31

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

persons.stream()
    .collect(Collectors.toCollection(
      () -> new TreeSet<Person>((p1, p2) -> p1.getName().compareTo(p2.getName())) 
));
Josketres
quelle
4
Ich denke, Ihre Antwort hilft bei der Bestellung und nicht bei der Einzigartigkeit. Es half mir jedoch, meine Gedanken darüber zu machen, wie es geht. Überprüfen Sie hier: stackoverflow.com/questions/1019854/…
Janagn
Denken Sie daran, dass Sie hier den Preis für das Sortieren der Elemente zahlen und wir keine Sortierung benötigen, um Duplikate zu finden oder sogar zu entfernen.
Pisaruk
12
Comparator.comparing (Person :: getName)
Jean-François Savard
24

Wir können auch RxJava (sehr leistungsfähige reaktive Erweiterungsbibliothek ) verwenden.

Observable.from(persons).distinct(Person::getName)

oder

Observable.from(persons).distinct(p -> p.getName())
frhack
quelle
Rx ist großartig, aber das ist eine schlechte Antwort. Observableist Push-basiert, während StreamPull-basiert ist. stackoverflow.com/questions/30216979/…
sdgfsdh
4
Die Frage lautet nach einer Java8-Lösung, die nicht unbedingt Stream verwendet. Meine Antwort zeigt, dass Java8 Stream API weniger leistungsfähig ist als RX API
Frhack
1
Mit Reaktor wird es seinFlux.fromIterable(persons).distinct(p -> p.getName())
Ritesh
Die Frage lautet wörtlich "Verwenden der StreamAPI", nicht "Nicht unbedingt Verwenden des Streams". Dies ist jedoch eine großartige Lösung für das XY-Problem, den Stream nach bestimmten Werten zu filtern.
M. Justin
12

Sie können groupingByCollector verwenden:

persons.collect(Collectors.groupingBy(p -> p.getName())).values().forEach(t -> System.out.println(t.get(0).getId()));

Wenn Sie einen anderen Stream haben möchten, können Sie diesen verwenden:

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream().map(l -> (l.get(0)));
Saeed Zarinfam
quelle
11

Sie können die distinct(HashingStrategy)Methode in Eclipse-Sammlungen verwenden .

List<Person> persons = ...;
MutableList<Person> distinct =
    ListIterate.distinct(persons, HashingStrategies.fromFunction(Person::getName));

Wenn Sie die personsImplementierung einer Eclipse Collections-Schnittstelle umgestalten können, können Sie die Methode direkt in der Liste aufrufen.

MutableList<Person> persons = ...;
MutableList<Person> distinct =
    persons.distinct(HashingStrategies.fromFunction(Person::getName));

HashingStrategy ist einfach eine Strategie-Schnittstelle, mit der Sie benutzerdefinierte Implementierungen von Equals und Hashcode definieren können.

public interface HashingStrategy<E>
{
    int computeHashCode(E object);
    boolean equals(E object1, E object2);
}

Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.

Craig P. Motlin
quelle
Die Methode differentBy wurde in Eclipse Collections 9.0 hinzugefügt, wodurch diese Lösung weiter vereinfacht werden kann. medium.com/@donraab/…
Donald Raab
10

Ich empfehle die Verwendung von Vavr , wenn Sie können. Mit dieser Bibliothek können Sie Folgendes tun:

io.vavr.collection.List.ofAll(persons)
                       .distinctBy(Person::getName)
                       .toJavaSet() // or any another Java 8 Collection
Mateusz Rasiński
quelle
Früher als "Javaslang" -Bibliothek bekannt.
user11153
9

Sie können die StreamEx- Bibliothek verwenden:

StreamEx.of(persons)
        .distinct(Person::getName)
        .toList()
Sllouyssgort
quelle
Leider ist diese Methode der ansonsten fantastischen StreamEx-Bibliothek schlecht konzipiert - sie vergleicht die Objektgleichheit, anstatt Gleichheit zu verwenden. Dies kann Stringdank String-Internierung für s funktionieren , aber möglicherweise auch nicht.
Drehmoment
7

Wenn Sie die Antwort von Stuart Marks erweitern, können Sie dies auf kürzere Weise und ohne gleichzeitige Zuordnung tun (wenn Sie keine parallelen Streams benötigen):

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
    final Set<Object> seen = new HashSet<>();
    return t -> seen.add(keyExtractor.apply(t));
}

Dann ruf an:

persons.stream().filter(distinctByKey(p -> p.getName());
Wojciech Górski
quelle
2
Dieser berücksichtigt nicht, dass der Stream parallel sein könnte.
Brunnsbe
Danke für den Kommentar, ich habe meine Antwort aktualisiert. Wenn Sie keinen parallelen Stream benötigen, erhalten Sie eine viel bessere Leistung, wenn Sie keine gleichzeitigen Karten verwenden.
Wojciech Górski
Ihr Code würde wahrscheinlich für parallele Sammlungen funktionieren, wenn Sie Collections.synchronizedSet(new HashSet<>())stattdessen eine erstellen würden . Aber es wäre wahrscheinlich langsamer als mit einem ConcurrentHashMap.
Lii
7

Ähnlicher Ansatz, den Saeed Zarinfam verwendet hat, aber mehr Java 8-Stil :)

persons.collect(Collectors.groupingBy(p -> p.getName())).values().stream()
 .map(plans -> plans.stream().findFirst().get())
 .collect(toList());
Alex
quelle
1
Ich würde die Kartenlinie durch flatMap(plans -> plans.stream().findFirst().stream())diese ersetzen und die Verwendung von get on Optional vermeiden
Andrew Sneck
Vielleicht ist das auch in Ordnung: flatMap (Pläne -> Plans.stream (). Limit (1))
Rrr
6

Ich habe eine generische Version gemacht:

private <T, R> Collector<T, ?, Stream<T>> distinctByKey(Function<T, R> keyExtractor) {
    return Collectors.collectingAndThen(
            toMap(
                    keyExtractor,
                    t -> t,
                    (t1, t2) -> t1
            ),
            (Map<R, T> map) -> map.values().stream()
    );
}

Ein Beispiel:

Stream.of(new Person("Jean"), 
          new Person("Jean"),
          new Person("Paul")
)
    .filter(...)
    .collect(distinctByKey(Person::getName)) // return a stream of Person with 2 elements, jean and Paul
    .map(...)
    .collect(toList())
Guillaume Kornett
quelle
6
Set<YourPropertyType> set = new HashSet<>();
list
        .stream()
        .filter(it -> set.add(it.getYourProperty()))
        .forEach(it -> ...);
Andrew Novitskyi
quelle
2
Eine gute Antwort hat eine bessere Erklärung Wie schreibe ich eine gute Antwort?
Narendra Jadhav
5

Mein Ansatz dabei ist, alle Objekte mit derselben Eigenschaft zu gruppieren, dann die Gruppen auf die Größe 1 zu kürzen und sie schließlich als zu sammeln List.

  List<YourPersonClass> listWithDistinctPersons =   persons.stream()
            //operators to remove duplicates based on person name
            .collect(Collectors.groupingBy(p -> p.getName()))
            .values()
            .stream()
            //cut short the groups to size of 1
            .flatMap(group -> group.stream().limit(1))
            //collect distinct users as list
            .collect(Collectors.toList());
uneq95
quelle
3

Eine Liste eindeutiger Objekte finden Sie unter:

 List distinctPersons = persons.stream()
                    .collect(Collectors.collectingAndThen(
                            Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person:: getName))),
                            ArrayList::new));
Naveen Dhalaria
quelle
2

Der einfachste Weg, dies zu implementieren, besteht darin, auf die Sortierfunktion zu springen, da sie bereits eine Option bereitstellt Comparator, die mithilfe der Eigenschaft eines Elements erstellt werden kann. Dann müssen Sie Duplikate herausfiltern, was mit einem statefull möglich ist, Predicateder die Tatsache nutzt, dass für einen sortierten Stream alle gleichen Elemente benachbart sind:

Comparator<Person> c=Comparator.comparing(Person::getName);
stream.sorted(c).filter(new Predicate<Person>() {
    Person previous;
    public boolean test(Person p) {
      if(previous!=null && c.compare(previous, p)==0)
        return false;
      previous=p;
      return true;
    }
})./* more stream operations here */;

Natürlich ist ein Statefull Predicatenicht threadsicher, aber wenn Sie dies benötigen, können Sie diese Logik in eine verschieben Collectorund den Stream für die Thread-Sicherheit sorgen lassen, wenn Sie Ihre verwenden Collector. Dies hängt davon ab, was Sie mit dem Strom unterschiedlicher Elemente tun möchten, die Sie uns in Ihrer Frage nicht mitgeteilt haben.

Holger
quelle
1

Aufbauend auf der Antwort von @ josketres habe ich eine generische Dienstprogrammmethode erstellt:

Sie können dies Java 8-freundlicher machen, indem Sie einen Collector erstellen .

public static <T> Set<T> removeDuplicates(Collection<T> input, Comparator<T> comparer) {
    return input.stream()
            .collect(toCollection(() -> new TreeSet<>(comparer)));
}


@Test
public void removeDuplicatesWithDuplicates() {
    ArrayList<C> input = new ArrayList<>();
    Collections.addAll(input, new C(7), new C(42), new C(42));
    Collection<C> result = removeDuplicates(input, (c1, c2) -> Integer.compare(c1.value, c2.value));
    assertEquals(2, result.size());
    assertTrue(result.stream().anyMatch(c -> c.value == 7));
    assertTrue(result.stream().anyMatch(c -> c.value == 42));
}

@Test
public void removeDuplicatesWithoutDuplicates() {
    ArrayList<C> input = new ArrayList<>();
    Collections.addAll(input, new C(1), new C(2), new C(3));
    Collection<C> result = removeDuplicates(input, (t1, t2) -> Integer.compare(t1.value, t2.value));
    assertEquals(3, result.size());
    assertTrue(result.stream().anyMatch(c -> c.value == 1));
    assertTrue(result.stream().anyMatch(c -> c.value == 2));
    assertTrue(result.stream().anyMatch(c -> c.value == 3));
}

private class C {
    public final int value;

    private C(int value) {
        this.value = value;
    }
}
Garrett Smith
quelle
1

Vielleicht wird es für jemanden nützlich sein. Ich hatte ein bisschen eine andere Anforderung. Wenn Sie eine Liste von Objekten Avon Drittanbietern haben, entfernen Sie alle, die dasselbe A.bFeld für dasselbe haben A.id(mehrere AObjekte mit demselben A.idin der Liste). Die Antwort auf die Stream-Partition von Tagir Valeev hat mich dazu inspiriert, einen benutzerdefinierten Code zu verwenden, Collectorder zurückgibt Map<A.id, List<A>>. Einfach flatMapwird den Rest erledigen.

 public static <T, K, K2> Collector<T, ?, Map<K, List<T>>> groupingDistinctBy(Function<T, K> keyFunction, Function<T, K2> distinctFunction) {
    return groupingBy(keyFunction, Collector.of((Supplier<Map<K2, T>>) HashMap::new,
            (map, error) -> map.putIfAbsent(distinctFunction.apply(error), error),
            (left, right) -> {
                left.putAll(right);
                return left;
            }, map -> new ArrayList<>(map.values()),
            Collector.Characteristics.UNORDERED)); }
Aliaksei Yatsau
quelle
1

Ich hatte eine Situation, in der ich verschiedene Elemente aus der Liste basierend auf 2 Schlüsseln erhalten sollte. Versuchen Sie dies, wenn Sie anhand von zwei Schlüsseln oder einem zusammengesetzten Schlüssel unterscheiden möchten

class Person{
    int rollno;
    String name;
}
List<Person> personList;


Function<Person, List<Object>> compositeKey = personList->
        Arrays.<Object>asList(personList.getName(), personList.getRollno());

Map<Object, List<Person>> map = personList.stream().collect(Collectors.groupingBy(compositeKey, Collectors.toList()));

List<Object> duplicateEntrys = map.entrySet().stream()`enter code here`
        .filter(settingMap ->
                settingMap.getValue().size() > 1)
        .collect(Collectors.toList());
Akanksha Gore
quelle
0

In meinem Fall musste ich kontrollieren, was das vorherige Element war. Ich habe dann ein statusbehaftetes Prädikat erstellt, in dem ich gesteuert habe, ob sich das vorherige Element vom aktuellen Element unterscheidet. In diesem Fall habe ich es beibehalten.

public List<Log> fetchLogById(Long id) {
    return this.findLogById(id).stream()
        .filter(new LogPredicate())
        .collect(Collectors.toList());
}

public class LogPredicate implements Predicate<Log> {

    private Log previous;

    public boolean test(Log atual) {
        boolean isDifferent = previouws == null || verifyIfDifferentLog(current, previous);

        if (isDifferent) {
            previous = current;
        }
        return isDifferent;
    }

    private boolean verifyIfDifferentLog(Log current, Log previous) {
        return !current.getId().equals(previous.getId());
    }

}
Flavio Oliva
quelle
0

Meine Lösung in dieser Auflistung:

List<HolderEntry> result ....

List<HolderEntry> dto3s = new ArrayList<>(result.stream().collect(toMap(
            HolderEntry::getId,
            holder -> holder,  //or Function.identity() if you want
            (holder1, holder2) -> holder1 
    )).values());

In meiner Situation möchte ich unterschiedliche Werte finden und in die Liste aufnehmen.

Евгений Трахимович
quelle
0

Während die am höchsten bewertete Antwort die absolut beste Antwort für Java 8 ist, ist sie gleichzeitig absolut schlecht in Bezug auf die Leistung. Wenn Sie wirklich eine schlechte Anwendung mit geringer Leistung wünschen, verwenden Sie sie. Das einfache Erfordernis, einen eindeutigen Satz von Personennamen zu extrahieren, wird durch bloßes "Für jeden" und einen "Satz" erreicht. Es wird noch schlimmer, wenn die Liste über 10 liegt.

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

public static final List<SimpleEvent> 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 so SimpleEventaussieht:

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 dasselbe eindeutige ByKey-Prädikat verwende, das in der akzeptierten Antwort erwähnt wird):

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

    Set<String> 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<String> 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 folgende Benchmark- Ergebnisse:

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 dreimal besser im Durchsatz und weniger fehlerhaft als Java 8 Stream.

Je höher der Durchsatz, desto besser die Leistung

Abhinav Ganguly
quelle
1
Vielen Dank, aber die Frage war sehr spezifisch im Zusammenhang mit der Stream-API
RichK
Ja, ich stimme zu, ich habe bereits erwähnt "Während die am höchsten bewertete Antwort die absolut beste Antwort für Java 8 ist". Ein Problem kann auf n verschiedene Arten gelöst werden, und ich versuche hier hervorzuheben, dass das vorliegende Problem einfach und nicht gefährlich mit Java 8 Streams gelöst werden kann, bei denen die Gefahr einer Leistungsverschlechterung besteht. :)
Abhinav Ganguly
0
Here is the example
public class PayRoll {

    private int payRollId;
    private int id;
    private String name;
    private String dept;
    private int salary;


    public PayRoll(int payRollId, int id, String name, String dept, int salary) {
        super();
        this.payRollId = payRollId;
        this.id = id;
        this.name = name;
        this.dept = dept;
        this.salary = salary;
    }
} 

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collector;
import java.util.stream.Collectors;

public class Prac {
    public static void main(String[] args) {

        int salary=70000;
        PayRoll payRoll=new PayRoll(1311, 1, "A", "HR", salary);
        PayRoll payRoll2=new PayRoll(1411, 2    , "B", "Technical", salary);
        PayRoll payRoll3=new PayRoll(1511, 1, "C", "HR", salary);
        PayRoll payRoll4=new PayRoll(1611, 1, "D", "Technical", salary);
        PayRoll payRoll5=new PayRoll(711, 3,"E", "Technical", salary);
        PayRoll payRoll6=new PayRoll(1811, 3, "F", "Technical", salary);
        List<PayRoll>list=new ArrayList<PayRoll>();
        list.add(payRoll);
        list.add(payRoll2);
        list.add(payRoll3);
        list.add(payRoll4);
        list.add(payRoll5);
        list.add(payRoll6);


        Map<Object, Optional<PayRoll>> k = list.stream().collect(Collectors.groupingBy(p->p.getId()+"|"+p.getDept(),Collectors.maxBy(Comparator.comparingInt(PayRoll::getPayRollId))));


        k.entrySet().forEach(p->
        {
            if(p.getValue().isPresent())
            {
                System.out.println(p.getValue().get());
            }
        });



    }
}

Output:

PayRoll [payRollId=1611, id=1, name=D, dept=Technical, salary=70000]
PayRoll [payRollId=1811, id=3, name=F, dept=Technical, salary=70000]
PayRoll [payRollId=1411, id=2, name=B, dept=Technical, salary=70000]
PayRoll [payRollId=1511, id=1, name=C, dept=HR, salary=70000]
Sourav Sharma
quelle
-2

Wenn Sie eine Liste der Personen erstellen möchten, ist dies der einfache Weg

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

Wenn Sie eine eindeutige oder eindeutige Liste von Namen und nicht von Personen suchen möchten , können Sie außerdem die folgenden zwei Methoden verwenden.

Methode 1: Verwenden distinct

persons.stream().map(x->x.getName()).distinct.collect(Collectors.toList());

Methode 2: Verwenden HashSet

Set<E> set = new HashSet<>();
set.addAll(person.stream().map(x->x.getName()).collect(Collectors.toList()));
Abdur Rahman
quelle
2
Dies erzeugt eine Liste von Namen, nicht Persons.
Hulk
1
Genau das habe ich gesucht. Ich brauchte eine einzeilige Methode, um Duplikate zu entfernen, während ich eine Sammlung in eine andere umwandelte. Vielen Dank.
Raj
-3

Der einfachste Code, den Sie schreiben können:

    persons.stream().map(x-> x.getName()).distinct().collect(Collectors.toList());
2Big2BeSmall
quelle
12
Das wird jedoch eine eindeutige Liste von Namen erhalten, nicht Personen mit Namen
RichK