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?

4voto

Ravikumar Punkte 473

Es gibt viele Ansätze, dieser wird auch helfen - einfach, sauber und klar

    List mitarbeiter = new ArrayList<>();

    mitarbeiter.add(new Employee(11, "Ravi"));
    mitarbeiter.add(new Employee(12, "Stalin"));
    mitarbeiter.add(new Employee(23, "Anbu"));
    mitarbeiter.add(new Employee(24, "Yuvaraj"));
    mitarbeiter.add(new Employee(35, "Sena"));
    mitarbeiter.add(new Employee(36, "Antony"));
    mitarbeiter.add(new Employee(47, "Sena"));
    mitarbeiter.add(new Employee(48, "Ravi"));

    List empList = new ArrayList<>(mitarbeiter.stream().collect(
                    Collectors.toMap(Employee::getName, obj -> obj,
                    (existingValue, newValue) -> existingValue))
                   .values());

    empList.forEach(System.out::println);

    //  Collectors.toMap(
    //  Employee::getName, - Schlüssel (der Wert, durch den doppelte eliminiert werden sollen)
    //  obj -> obj,  - Wert (gesamtes Mitarbeiterobjekt)
    //  (existingValue, newValue) -> existingValue) - um illegalstateexception: doppelten Schlüssel zu vermeiden

Ausgabe - toString() überladen

Employee{id=35, name='Sena'}
Employee{id=12, name='Stalin'}
Employee{id=11, name='Ravi'}
Employee{id=24, name='Yuvaraj'}
Employee{id=36, name='Antony'}
Employee{id=23, name='Anbu'}

2voto

M. Justin Punkte 8707

Das JEP 461: Stream Gatherers Java 22-Vorschau Sprachfunktion fügt Unterstützung für Sammleroperationen hinzu, die verwendet werden können, um den Stream nach eindeutigen Elementen gemäß einer Funktion zu filtern:

void main() {
    List persons = List.of(
            new Person("A"), new Person("B"), new Person("C"), new Person("A"),
            new Person("D"), new Person("B")
    );

    List distinct = 
            persons.stream().gather(distinctBy(Person::getName)).toList();

    // [Person[name=A], Person[name=B], Person[name=C], Person[name=D]]
    System.out.println(distinct);
}

static  Gatherer distinctBy(Function function) {
    Gatherer.Integrator.Greedy, T, T> integrator =
            (state, element, downstream) -> {
                R functionResult = function.apply(element);
                if (state.contains(functionResult)) {
                    return true;
                } else {
                    state.add(functionResult);
                    return downstream.push(element);
                }
            };
    return Gatherer.ofSequential(
            HashSet::new,
            Gatherer.Integrator.ofGreedy(integrator)
    );
}

Der benutzerdefinierte Sammler verfolgt, ob ein bestimmter Wert bereits aufgrund des Ergebnisses angewendet einer Funktion auf jedes Streamelement zuvor aufgetreten ist. Wenn ein bestimmter Funktionswert noch nicht aufgetreten ist, wird das Streamelement stromabwärts gepusht.

Zugegeben, das ist isoliert eine recht große Menge Code, aber es handelt sich um eine wiederverwendbare Zwischenoperation für jeden Stream, der nur auf eindeutigen Elementen basierend auf einer bestimmten Funktion arbeiten muss.

Hier ist dieselbe Lösung, mit einem spezifischen Einmal-Sammler für das Szenario Person:

List distinct = persons.stream().gather(Gatherer.ofSequential(
        HashSet::new,
        Gatherer.Integrator., Person, Person>ofGreedy(
                (state, element, downstream) -> {
                    String name = element.getName();
                    if (state.contains(name)) {
                        return true;
                    } else {
                        state.add(name);
                        return downstream.push(element);
                    }
                })
)).toList();

Javadocs

Gatherer:

Eine Zwischenoperation, die einen Stream von Eingabeelementen in einen Stream von Ausgabeelementen umwandelt und optional eine abschließende Aktion anwendet, wenn das Ende des Upstreams erreicht wird. [...]

[…]

Es gibt viele Beispiele für Sammeloperationen, einschließlich, aber nicht beschränkt auf: Elemente in Chargen gruppieren (Fensterfunktionen); aufeinanderfolgende ähnliche Elemente deduplizieren; inkrementelle Akkumulationsfunktionen (Präfixscan); inkrementelle Funktionen zur Neuanordnung usw. Die Klasse Gatherers bietet Implementierungen gängiger Sammeloperationen.

API-Hinweis:

Ein Gatherer wird durch vier Funktionen definiert, die zusammenarbeiten, um Eingabeelemente zu verarbeiten, optional unter Verwendung eines Zwischenzustands und optional eine Abschlussaktion am Ende der Eingabe auszuführen. Sie sind:

Stream.gather(Gatherer gatherer):

Gibt einen Stream zurück, der aus den Ergebnissen der Anwendung des angegebenen Sammlers auf die Elemente dieses Streams besteht.

Gatherer.ofSequential(initializer, integrator)

Gibt einen neuen, sequentiellen Gatherer zurück, der durch den angegebenen initializer und integrator beschrieben wird.

2voto

Sourav Sharma Punkte 401

Hier ist das Beispiel:

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);
        Listlist=new ArrayList();
        list.add(payRoll);
        list.add(payRoll2);
        list.add(payRoll3);
        list.add(payRoll4);
        list.add(payRoll5);
        list.add(payRoll6);

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

Ausgabe:

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]

2voto

Holger Punkte 264693

Der einfachste Weg, dies zu implementieren, ist die Verwendung der Sortierfunktion, da sie bereits einen optionalen Comparator bietet, der mithilfe einer Eigenschaft eines Elements erstellt werden kann. Anschließend müssen Duplikate herausgefiltert werden, was mithilfe eines zustandsbehafteten Predicate erfolgen kann, der die Tatsache nutzt, dass für einen sortierten Stream alle gleichen Elemente benachbart sind:

Comparator c=Comparator.comparing(Person::getName);
stream.sorted(c).filter(new Predicate() {
    Person previous;
    public boolean test(Person p) {
      if(previous!=null && c.compare(previous, p)==0)
        return false;
      previous=p;
      return true;
    }
})./* weitere Stream-Operationen hier */;

Ein zustandsbehaftetes Predicate ist natürlich nicht threadsicher. Wenn Sie dies jedoch benötigen, können Sie diese Logik in einen Collector verschieben und den Stream die Thread-Sicherheit übernehmen lassen, wenn Sie Ihren Collector verwenden. Dies hängt davon ab, was Sie mit dem Stream von unterschiedlichen Elementen tun möchten, was Sie in Ihrer Frage nicht angegeben haben.

1voto

Akanksha gore Punkte 438

Ich hatte eine Situation, in der ich distinct Elemente aus einer Liste basierend auf 2 Schlüsseln bekommen sollte. Wenn Sie distinct basierend auf zwei Schlüsseln oder einem zusammengesetzten Schlüssel möchten, versuchen Sie dies:

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

Function> compositeKey = personList->
        Arrays.asList(personList.getName(), personList.getRollno());

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

List duplicateEntrys = map.entrySet().stream()
        .filter(entry ->
                entry.getValue().size() > 1)
        .collect(Collectors.toList());

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