wake-up-neo.net

Java 8 Stream zum Auffinden des Elements in der Liste

Ich habe folgende Klasse:

public class Item {
    int id;
    String name;
    // few other fields, contructor, getters and setters
}

Ich habe eine Liste mit Artikeln. Ich möchte die Liste durchlaufen und die Instanz finden, die eine bestimmte ID hat. Ich versuche es über Streams.

public void foobar() {

    List<Item> items = getItemList();
    List<Integer> ids = getIdsToLookup();
    int id, i = ids.size() - 1;

    while (i >= 0) {
        id = ids.get(i);
        Optional<Item> item = items
            .stream()
            .filter(a -> a.getId() == id)
            .findFirst();
        // do stuff
        i--;
    }
}

Ist dies der beste Weg, um die Liste zu durchlaufen und das Element zu erhalten, das ich brauche? Ich erhalte auch einen Fehler in der Filterzeile für id, der besagt, dass Variablen, die in Lambda-Ausdrücken verwendet werden, final oder effektiv final sein müssen. Vielleicht kann ich eine id innerhalb der while-Schleife definieren, die die Ausnahme beseitigen sollte. Vielen Dank.

8
Gengis Khan

Wenn Sie viele IDs suchen, empfiehlt es sich, eine Lösung zu verwenden, die dies in einem einzigen Durchlauf durchführt, anstatt für jede ID eine lineare Suche durchzuführen:

Map<Integer,Optional<Item>> map=ids.stream()
    .collect(Collectors.toMap(id -> id, id -> Optional.empty()));
items.forEach(item ->
    map.computeIfPresent(item.getId(), (i,o)->o.isPresent()? o: Optional.of(item)));
for(ListIterator<Integer> it=ids.listIterator(ids.size()); it.hasPrevious();) {
    map.get(it.previous()).ifPresent(item -> {
        // do stuff
    });
}

Die erste Anweisung erstellt einfach eine Map aus der IDs-Liste und ordnet jede Such-ID einem leeren Optional zu.

Die zweite Anweisung durchläuft die Elemente mit forEach, und sie überprüft für jedes Element, ob es eine Zuordnung von ihrer ID zu einem leeren Optional gibt, und ersetzt diese durch ein Optional, das das Element einkapselt. computeIfPresent.

Die letzte for-Schleife durchläuft die ids-Liste rückwärts, da Sie sie in dieser Reihenfolge bearbeiten und die Aktion ausführen möchten, wenn Optional nicht leer ist. Da die Karte mit allen in der Liste gefundenen IDs initialisiert wurde, gibt get niemals null zurück. Sie gibt ein leeres Optional zurück, wenn die ID nicht in der Liste items gefunden wurde.

Unter der Annahme, dass die Suche nach MapO(1) Zeitkomplexität hat, was bei typischen Implementierungen der Fall ist, änderte sich die Nettozeitkomplexität von O(m×n) in O(m+n)

8
Holger

Sie können so etwas versuchen:

ids.forEach(id -> 
    list.stream()
    .filter(p -> p.getId() == id)
    .findFirst()
    .ifPresent(p -> {
        // do stuff here
    });
);

Optional zeigt dies an, dass Ihre Filtermethode einen leeren Stream zurückgeben kann. Wenn Sie findFirst aufrufen, kann er ein oder null Elemente finden.

9
ByeBye

Wenn Sie bei Streams bleiben und rückwärts iterieren möchten, können Sie dies folgendermaßen tun:

IntStream.iterate(ids.size() - 1, i -> i - 1)
    .limit(ids.size())
    .map(ids::get) // or .map(i -> ids.get(i))
    .forEach(id -> items.stream()
        .filter(item -> item.getId() == id)
        .findFirst().ifPresent(item -> {
            // do stuff
        }));

Dieser Code entspricht dem von Ihnen. 

Es iteriert rückwärts und beginnt mit einem Startwert: ids.size() - 1. Der anfängliche Stream von ints ist in seiner Größe mit limit() begrenzt, so dass keine negativen ints vorhanden sind und der Stream dieselbe Größe hat wie die Liste von ids. Dann konvertiert eine map()-Operation den Index in die tatsächliche id, die sich an der i-ten Position in der ids-Liste befindet (dies geschieht durch Aufrufen von ids.get(i)). Schließlich wird das Element in der Liste items genauso wie in Ihrem Code durchsucht.

Sie möchten höchstens einen Artikel für jede ID finden und mit dem gefundenen Artikel etwas unternehmen, oder? Ein bisschen mehr Leistungsverbesserung:

Set<Integer> idsToLookup = new HashSet<>(getIdsToLookup()); // replace list with Set

items.stream()
    .filter(e -> idsToLookup.remove(e.getId()))
    .forEach(
       /* doing something */
     );
0
user_3380739