Negli stream Java8, posso modificare / aggiornare gli oggetti all'interno? Per es. List<User> users
:
users.stream().forEach(u -> u.setProperty("value"))
Negli stream Java8, posso modificare / aggiornare gli oggetti all'interno? Per es. List<User> users
:
users.stream().forEach(u -> u.setProperty("value"))
Risposte:
Sì, puoi modificare lo stato degli oggetti all'interno del tuo flusso, ma molto spesso dovresti evitare di modificare lo stato dell'origine del flusso. Dalla sezione sulla non interferenza della documentazione del pacchetto stream possiamo leggere che:
Per la maggior parte delle origini dati, prevenire le interferenze significa garantire che l'origine dati non venga modificata durante l'esecuzione della pipeline di flusso. La notevole eccezione a ciò sono i flussi le cui origini sono raccolte simultanee, progettate specificamente per gestire la modifica simultanea. Le sorgenti di flusso simultanee sono quelle il cui
Spliterator
rapporto sullaCONCURRENT
caratteristica.
Quindi va bene
List<User> users = getUsers();
users.stream().forEach(u -> u.setProperty(value));
// ^ ^^^^^^^^^^^^^
ma questo nella maggior parte dei casi non lo è
users.stream().forEach(u -> users.remove(u));
//^^^^^ ^^^^^^^^^^^^
e può generare ConcurrentModificationException
o anche altre eccezioni impreviste come NPE:
List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList());
list.stream()
.filter(i -> i > 5)
.forEach(i -> list.remove(i)); //throws NullPointerException
Iterator
che impedisce la modifica dell'elenco (rimozione / aggiunta di nuovi elementi) durante l'iterazione e sia i flussi che i cicli for-each lo stanno usando. Potresti provare a usare un ciclo semplice come il for(int i=0; ..; ..)
quale non ha questo problema (ma non ti fermerà quando altri thread modificheranno la tua lista). Si potrebbe anche usare metodi come list.removeAll(Collection)
, list.removeIf(Predicate)
. Inoltre la java.util.Collections
classe ha pochi metodi che potrebbero essere utili come addAll(CollectionOfNewElements,list)
.
filter()
Il modo funzionale sarebbe:
import static java.util.stream.Collectors.toList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class PredicateTestRun {
public static void main(String[] args) {
List<String> lines = Arrays.asList("a", "b", "c");
System.out.println(lines); // [a, b, c]
Predicate<? super String> predicate = value -> "b".equals(value);
lines = lines.stream().filter(predicate.negate()).collect(toList());
System.out.println(lines); // [a, c]
}
}
In questa soluzione la lista originale non viene modificata, ma dovrebbe contenere il risultato atteso in una nuova lista accessibile sotto la stessa variabile di quella vecchia
Per apportare modifiche strutturali alla sorgente del flusso, come ha menzionato Pshemo nella sua risposta, una soluzione è creare una nuova istanza di un Collection
simile ArrayList
con gli elementi all'interno della tua lista principale; iterare sul nuovo elenco ed eseguire le operazioni sull'elenco principale.
new ArrayList<>(users).stream().forEach(u -> users.remove(u));
Per sbarazzarsi di ConcurrentModificationException utilizzare CopyOnWriteArrayList
List<Something>
- non hai idea se si tratta di un CopyOnWriteArrayList. Quindi questo più simile a un commento mediocre, ma non una vera risposta.
Invece di creare cose strane, puoi solo filter()
e poi il map()
tuo risultato.
Questo è molto più leggibile e sicuro. Gli stream lo faranno in un solo loop.
Puoi utilizzare il file removeIf
per rimuovere i dati da un elenco in modo condizionale.
Ad esempio: - Se vuoi rimuovere tutti i numeri pari da un elenco, puoi farlo come segue.
final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());
list.removeIf(number -> number % 2 == 0);
Sì, puoi modificare o aggiornare i valori degli oggetti nell'elenco nel tuo caso allo stesso modo:
users.stream().forEach(u -> u.setProperty("some_value"))
Tuttavia, la dichiarazione precedente apporterà aggiornamenti sugli oggetti di origine. Che potrebbe non essere accettabile nella maggior parte dei casi.
Fortunatamente, abbiamo un altro modo come:
List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());
Che restituisce un elenco aggiornato, senza ostacolare quello vecchio.
Come accennato in precedenza, non è possibile modificare l'elenco originale, ma è possibile eseguire lo streaming, modificare e raccogliere elementi in un nuovo elenco. Ecco un semplice esempio di come modificare l'elemento stringa.
public class StreamTest {
@Test
public void replaceInsideStream() {
List<String> list = Arrays.asList("test1", "test2_attr", "test3");
List<String> output = list.stream().map(value -> value.replace("_attr", "")).collect(Collectors.toList());
System.out.println("Output: " + output); // Output: [test1, test2, test3]
}
}
Potrebbe essere un po 'tardi. Ma qui è uno degli usi. Questo per ottenere il conteggio del numero di file.
Crea un puntatore alla memoria (un nuovo oggetto in questo caso) e modifica la proprietà dell'oggetto. Il flusso Java 8 non consente di modificare il puntatore stesso e quindi se dichiari conta solo come variabile e provi ad aumentare all'interno del flusso, non funzionerà mai e genererà un'eccezione del compilatore in primo luogo
Path path = Paths.get("/Users/XXXX/static/test.txt");
Count c = new Count();
c.setCount(0);
Files.lines(path).forEach(item -> {
c.setCount(c.getCount()+1);
System.out.println(item);});
System.out.println("line count,"+c);
public static class Count{
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
@Override
public String toString() {
return "Count [count=" + count + "]";
}
}