2016-03-10 12 views
7

ho la seguente classe:Java 8 Stream per trovare elemento della lista

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

Ho una lista di elementi. Voglio scorrere l'elenco e trovare l'istanza che ha un ID particolare. Sto provando a farlo attraverso i flussi.

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--; 
    } 
} 

È questo il modo migliore per iterare la lista e ottenere l'elemento di cui ho bisogno? Inoltre, ottengo un errore sulla riga del filtro per id che dice che le variabili usate nelle espressioni lambda devono essere definitive o effettivamente definitive. Forse posso definire id all'interno del ciclo while, che dovrebbe sbarazzarsi dell'eccezione. Grazie.

+1

Si sta utilizzando la stessa variabile i per l'indice nell'elenco e per l'elemento corrente in lambda. Scegli un nome diverso. Quel codice va bene, ma piuttosto inefficiente. Se hai più ID e hai bisogno di trovare l'elemento corrispondente per tutti, inizia creando un HashMap , e poi usa HashMap. –

+0

Sto usando una variabile diversa nel mio codice, stavo cercando di semplificare il codice qui. Lo cambierò. –

+1

Dichiara la variabile 'id' ** all'interno ** del ciclo, e sarà effettivamente definitiva. Essendo all'esterno, lo si reinizializza ad ogni iterazione e non è quindi definitivo. Dichiarare le variabili nel più piccolo ambito possibile è una best practice in generale. –

risposta

5

Se hai un sacco di ID per la ricerca, si consiglia di utilizzare una soluzione che lo fa in un unico passaggio, piuttosto che fare una ricerca lineare per ogni ID:

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

La prima affermazione è sufficiente creare una mappa dall'elenco degli ID, mappando ciascun ID di ricerca su un numero vuoto Optional.

La seconda itera economico sulla base della elementi utilizzando forEach e per ciascuna voce, controlla se c'è una mappatura dalla sua id a un vuoto Optional e lo sostituirà con un Optional incapsulare l'articolo, se v'è una tale mappatura, tutto in un'unica operazione, computeIfPresent.

L'ultimo ciclo for esegue l'iterazione all'indietro sopra l'elenco ids, poiché si desidera elaborarli in tale ordine ed eseguire l'azione se è presente un numero non vuoto Optional. Poiché la mappa è stata inizializzata con tutti gli identificativi trovati nell'elenco, get non restituirà mai null, restituirà uno Optional vuoto, se l'ID non è stato trovato nell'elenco items.

In questo modo, partendo dal presupposto che ricerca s' il Map ha O(1) complessità temporale, che è il caso di implementazioni tipiche, la complessità tempo netto modificata da O(m×n) a O(m+n) ...

+0

Mi piace l'idea di fare un singolo passaggio e ottenere tutte le voci richieste. Ma ho bisogno di elaborare le voci nell'ordine in cui le ho ricevute nella lista degli identificativi. Non penso che il codice di cui sopra si preoccupi di ordinare, giusto? Inoltre, puoi dare qualche spiegazione su cosa stai cercando di fare nel codice sopra? Sarà utile. Grazie. –

+0

@Gengis Khan: fa esattamente quello che vuoi. Il ciclo 'for' sta iterando all'indietro sulla lista' ids' come desiderato. – Holger

8

Si può provare a utilizzare qualcosa di simile:

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

opzionale qui dimostra che il metodo del filtro può restituire un flusso di vuoto, quindi se si chiama FindFirst che riesce a trovare uno o zero elementi.

+0

sostituendo 'findFirst()' con 'findAny()' migliorerebbe le prestazioni qui. https://stackoverflow.com/questions/35359112/difference-between-findany-and-findfirst-in-java-8 – stuart

2

Se si vuole attaccare con corsi d'acqua e iterare all'indietro, si potrebbe fare in questo modo:

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

Questo codice fa uguali ai suoi.

It ita all'indietro, a partire da un seme: ids.size() - 1. Il flusso iniziale di int s è limitato nelle sue dimensioni con limit(), in modo che non vi siano valori negativi int e lo streaming abbia le stesse dimensioni dell'elenco di ids. Quindi, un'operazione map() converte l'indice nell'effettivo id che si trova nella posizione iodell'elenco ids (ciò viene fatto invocando ids.get(i)). Infine, l'elemento viene ricercato nell'elenco items allo stesso modo del tuo codice.

+2

L'operazione di streaming implica comunque una complessità 'O (n)' ... – Holger

+0

@Holger Intendo per 'id 'lista –

+1

@Holger Ora capisco cosa intendi, si modificherà per rimuovere quella nota. Grazie! –

0

si vuole trovare al massimo un oggetto per ogni dato id e fare qualcosa con l'elemento trovato, giusto? Un po 'più di miglioramento delle prestazioni:

Set<Integer> idsToLookup = new HashSet<>(getIdsToLookup()); // replace list with Set 
items.stream().filter(e -> idsToLookup.remove(e.getId())).forEach(/* doing something */);