2016-03-10 10 views
7

ich die folgende Klasse haben:Java 8 Stream-Element in der Liste finden

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

Ich habe eine Liste von Elementen. Ich möchte die Liste durchlaufen und die Instanz finden, die eine bestimmte ID hat. Ich versuche es durch Streams zu machen.

Ist dies der beste Weg, um über die Liste zu iterieren und das Element zu bekommen, das ich brauche? Außerdem erhalte ich 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 ID innerhalb der While-Schleife definieren, das sollte die Ausnahme loswerden. Vielen Dank.

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

Optional hier zeigt, dass Ihre Filtermethode einen leeren Stream zurückkehren können, wenn Sie also findfirst nennen kann es ein oder Null-Elemente finden:

+1

Sie verwenden die gleiche Variable i für den Index in der Liste und für das aktuelle Element im Lambda. Wählen Sie einen anderen Namen. Dieser Code ist in Ordnung, aber ziemlich ineffizient. Wenn Sie mehrere IDs haben und das entsprechende Element für alle suchen müssen, erstellen Sie zuerst eine HashMap und verwenden Sie dann die HashMap. –

+0

Ich verwende eine andere Variable in meinem Code, ich habe versucht, den Code hier zu vereinfachen. Ich werde es ändern. –

+1

Deklarieren Sie die Variable "ID" ** innerhalb der Schleife, und es wird effektiv endgültig. Wenn du draußen bist, initialisierst du es bei jeder Wiederholung neu und es ist somit nicht endgültig. Das Deklarieren von Variablen im kleinstmöglichen Umfang ist im Allgemeinen eine bewährte Methode. –

Antwort

5

Wenn Sie viele IDs zu suchen haben, ist es empfehlenswert, eine Lösung zu verwenden, die es in einem einzigen Durchlauf funktioniert nicht für jede ID eine lineare Suche tun:

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 einfach eine Karte erstellen Aus der IDs-Liste wird jede Such-ID einer leeren Optional zugeordnet.

Die zweite Anweisung iteriert über die Elemente forEach und für jedes Element verwendet wird, wird geprüft, ob eine Abbildung von seiner ID auf einen leeren Optional gibt es und ersetzen sie durch eine Optional das Element einkapseln, wenn es eine solche Zuordnung ist, alle in einem Vorgang, computeIfPresent.

Die letzte for Schleife iteriert rückwärts über die ids Liste, wie Sie sie in dieser Reihenfolge verarbeiten und die Aktion ausführen möchten, wenn es eine nicht leere Optional gibt. Da die Karte mit allen in der Liste gefundenen IDs initialisiert wurde, gibt get niemals null zurück, und es wird eine leere Optional zurückgegeben, wenn die ID nicht in der Liste items gefunden wurde.

Auf diese Weise unter der Annahme, dass die Map ‚s-Lookup O(1) Zeitkomplexität hat, was der Fall in typischen Implementierungen ist, änderte sich die Netto-Zeitkomplexität von O(m×n) zu O(m+n) ...

+0

Ich mag die Idee, einen einzigen Pass zu machen und alle erforderlichen Einträge zu bekommen. Aber ich muss die Einträge in der Reihenfolge bearbeiten, in der ich sie in der IDs-Liste erhalten habe. Ich denke nicht, dass der obige Code sich um die Bestellung kümmert, oder? Können Sie auch erläutern, was Sie im obigen Code zu tun versuchen? Das wird hilfreich sein. Vielen Dank. –

+0

@Gengis Khan: es tut genau das, was Sie wollen. Die 'for'-Schleife läuft rückwärts über die' ids'-Liste, wie Sie es wünschen. – Holger

8

Sie können Gebrauch machen so etwas wie dies versuchen.

+0

Ersetzen 'findFirst()' mit 'findAny()' würde hier die Leistung verbessern. https://stackoverflow.com/questions/35359112/difference-between-findany-and-findfirst-in-java-8 – stuart

2

Wenn Sie mit Strömen halten wollen und iterieren rückwärts, könnten Sie es auf diese Weise 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 macht das gleiche wie Sie.

Es iteriert rückwärts, beginnend mit einem Seed: ids.size() - 1. Der Anfangsstrom von int ist in seiner Größe mit limit() begrenzt, so dass es keine negativen int s gibt und der Strom die gleiche Größe wie die Liste von ids hat. Dann wandelt eine Operation map() den Index in den tatsächlichen id um, der sich an der i-ten Position in der Liste ids befindet (dies geschieht durch Aufruf von ids.get(i)). Schließlich wird das Element in der items Liste auf die gleiche Weise wie in Ihrem Code gesucht.

+2

Die Stream-Operation impliziert eine 'O (n) 'Komplexität sowieso ... – Holger

+0

@Holger Ich meine über' IDs 'Liste –

+1

@Holger Jetzt sehe ich, was du meinst, wird bearbeiten, um diese Notiz zu entfernen. Vielen Dank! –

0

Sie wollen für höchstens einen Artikel finden jeweils id gegeben und etwas mit dem gefundenen Gegenstand machen, 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 */);