2015-05-04 12 views
39

Come quasi tutti, sto ancora imparando le complessità (e il loro amore) della nuova API di Java 8 Streams. Ho una domanda sull'uso dei flussi. Fornirò un esempio semplificato.Java 8 Stream possono operare su un elemento di una raccolta e quindi rimuoverlo?

Java Streaming ci consente di prendere uno Collection e utilizzare il metodo stream() su di esso per ricevere un flusso di tutti i suoi elementi. Al suo interno, esistono numerosi metodi utili, come ad esempio filter(), map() e forEach(), che ci consentono di utilizzare le operazioni lambda sui contenuti.

Ho codice che assomigli a questo (semplificato):

set.stream().filter(item -> item.qualify()) 
    .map(item -> (Qualifier)item).forEach(item -> item.operate()); 
set.removeIf(item -> item.qualify()); 

L'idea è quella di ottenere una mappatura di tutti gli elementi del set, che corrispondono a una certa qualificazione, e quindi operare attraverso di loro. Dopo l'operazione, non hanno altro scopo e devono essere rimossi dal set originale. Il codice funziona bene, ma non riesco a scuotere la sensazione che ci sia un'operazione in Stream che potrebbe farlo per me, in una singola riga.

Se si tratta di Javadoc, potrei trascurarlo.

Qualcuno più familiare con l'API vede qualcosa del genere?

risposta

72

Si può fare in questo modo:

set.removeIf(item -> { 
    if (!item.qualify()) 
     return false; 
    item.operate(); 
    return true; 
}); 

Se item.operate() restituisce sempre true si può fare molto succintamente.

set.removeIf(item -> item.qualify() && item.operate()); 

Tuttavia, non mi piacciono questi approcci in quanto non è immediatamente chiaro cosa sta succedendo. Personalmente, continuerei a utilizzare un ciclo for e uno Iterator per questo.

for (Iterator<Item> i = set.iterator(); i.hasNext();) { 
    Item item = i.next(); 
    if (item.qualify()) { 
     item.operate(); 
     i.remove(); 
    } 
} 
+7

+1 per l'utilizzo di un ciclo for; non utilizzare gli stream solo per poter adattare il codice su una sola riga. I loop sono spesso più leggibili rispetto alle soluzioni di streaming, anche se sono più prolissi. – dimo414

+3

Sono un po 'titubante nell'inserire il codice che modifica lo stato in 'removeIf()', anche se credo che ciò funzionerebbe teoricamente. Inoltre, la mia situazione si presta alla programmazione funzionale, quindi il mio bisogno di flussi va oltre il confezionamento di tutto in un'unica linea; ma grazie. –

+3

L'uso di 'removeIf' con espressione lambda è perfettamente legittimo e deve essere usato in questo modo. La modifica dello stato fa parte della raccolta API e non lo streaming. – hussachai

1

No, la tua implementazione è probabilmente la più semplice. Potresti fare qualcosa di profondamente malvagio modificando lo stato nel predicato removeIf, ma per favore non farlo. D'altra parte, potrebbe essere ragionevole passare effettivamente a un'implementazione imperativa basata su iteratore, che potrebbe essere in realtà più appropriata ed efficiente per questo caso d'uso.

+0

Ci stavo pensando, ma per la mia implementazione effettiva (non semplificata), i flussi potrebbero diventare enormi. Lo sto facendo in genere su quattro o otto core machine; quindi, nonostante i giovani dell'API di Java, non sono ancora convinto che gli iteratori saranno un vantaggio. 'parallelo()' potrebbe essere un salvatore qui. Grazie per la pronta risposta, però! –

5

In una linea di no, ma forse si potrebbe fare uso del partitioningBy collezionista:

Map<Boolean, Set<Item>> map = 
    set.stream() 
     .collect(partitioningBy(Item::qualify, toSet())); 

map.get(true).forEach(i -> ((Qualifier)i).operate()); 
set = map.get(false); 

Potrebbe essere più efficace in quanto evita l'iterazione il set due volte, una per il filtraggio del flusso e poi uno per rimuovendo gli elementi corrispondenti.

In caso contrario, il tuo approccio è relativamente soddisfacente.

2

Quello che vuoi veramente è dividere il tuo set. Sfortunatamente in Java 8 il partizionamento è possibile solo tramite il metodo "collect" del terminale.Si finisce con qualcosa di simile:

// test data set 
Set<Integer> set = ImmutableSet.of(1, 2, 3, 4, 5); 
// predicate separating even and odd numbers 
Predicate<Integer> evenNumber = n -> n % 2 == 0; 

// initial set partitioned by the predicate 
Map<Boolean, List<Integer>> partitioned = set.stream().collect(Collectors.partitioningBy(evenNumber)); 

// print even numbers 
partitioned.get(true).forEach(System.out::println); 
// do something else with the rest of the set (odd numbers) 
doSomethingElse(partitioned.get(false)) 

Aggiornato:

versione Scala del codice di cui sopra

val set = Set(1, 2, 3, 4, 5) 
val partitioned = set.partition(_ % 2 == 0) 
partitioned._1.foreach(println) 
doSomethingElse(partitioned._2)` 
+0

Ah, si spera che implementino un metodo "' forEachAndRemove' "uno di questi giorni. Non penso che il mio codice sarebbe più chiaro o più veloce se lo facessi. Grazie per l'aiuto! –

+0

Sorprendentemente non hai bisogno di metodi specifici come quello. Lo stesso codice in Scala ha molto più senso (vedi sopra). –

1

se capisco la sua domanda:

set = set.stream().filter(item -> { 
    if (item.qualify()) { 
     ((Qualifier) item).operate(); 
     return false; 
    } 
    return true; 
}).collect(Collectors.toSet()); 
0

Dopo l'operazione, non hanno altro scopo, e spariscono essere rimosso dal set originale. Il codice funziona bene, ma non riesco a scuotere la sensazione che ci sia un'operazione in Stream che potrebbe farlo per me, in una singola riga.

Non è possibile rimuovere elementi dalla sorgente del flusso con il flusso. Dal Javadoc:

maggior parte delle operazioni del flusso di accettare parametri che descrivono il comportamento specificato dall'utente ..... Per conservare un comportamento corretto, questi parametri comportamentali:

  • devono essere non-interferenza (che fanno non modificare la fonte del flusso); e
  • nella maggior parte dei casi deve essere stateless (il loro risultato non dovrebbe dipendere da nessuno stato che potrebbe cambiare durante l'esecuzione della pipeline dello stream).
-3
user.getSongs() 
    .stream() 
    .filter(song -> song.getSinger().getId() != singerId) // Only those songs where singer ID doesn't match 
    .forEach(song -> user.getSongs().remove(song)); // Then remove them 
0

vedo chiarezza preoccupazione di Paolo quando si utilizzano i flussi, indicati nella risposta superiore. Forse aggiungere una spiegazione alla variabile chiarisce un po 'le intenzioni.

set.removeIf(item -> { 
    boolean removeItem=item.qualify(); 
    if (removeItem){ 
    item.operate(); 
    } 
    return removeItem; 
});