Em fluxos Java8, tenho permissão para modificar / atualizar objetos dentro? Por exemplo. List<User> users
:
users.stream().forEach(u -> u.setProperty("value"))
Em fluxos Java8, tenho permissão para modificar / atualizar objetos dentro? Por exemplo. List<User> users
:
users.stream().forEach(u -> u.setProperty("value"))
Respostas:
Sim, você pode modificar o estado dos objetos dentro do seu fluxo, mas na maioria das vezes você deve evitar modificar o estado da fonte do fluxo. Na seção de não interferência da documentação do pacote de fluxo, podemos ler que:
Para a maioria das fontes de dados, evitar interferência significa garantir que a fonte de dados não seja modificada durante a execução do pipeline de fluxo. A exceção notável a isso são os fluxos cujas origens são coleções simultâneas, que são projetadas especificamente para lidar com a modificação simultânea. Fontes de fluxo simultâneo são aquelas cujos
Spliterator
relatórios aCONCURRENT
característica.
Então está tudo bem
List<User> users = getUsers();
users.stream().forEach(u -> u.setProperty(value));
// ^ ^^^^^^^^^^^^^
mas isso na maioria dos casos não é
users.stream().forEach(u -> users.remove(u));
//^^^^^ ^^^^^^^^^^^^
e pode lançar ConcurrentModificationException
ou até mesmo outras exceções inesperadas como 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
que impede a modificação da lista (remover / adicionar novos elementos) durante a iteração e ambos os fluxos e os loops for-each estão usando-a. Você pode tentar usar um loop simples como o for(int i=0; ..; ..)
qual não tem esse problema (mas não irá impedi-lo quando outro thread modificar sua lista). Você também pode usar métodos como list.removeAll(Collection)
, list.removeIf(Predicate)
. Além disso, a java.util.Collections
classe tem poucos métodos que podem ser úteis como addAll(CollectionOfNewElements,list)
.
filter()
A forma funcional seria imho:
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]
}
}
Nesta solução, a lista original não é modificada, mas deve conter seu resultado esperado em uma nova lista que é acessível sob a mesma variável da antiga
Para fazer modificações estruturais na origem do fluxo, como Pshemo mencionou em sua resposta, uma solução é criar uma nova instância de um Collection
similar ArrayList
com os itens dentro de sua lista primária; itere sobre a nova lista e faça as operações na lista primária.
new ArrayList<>(users).stream().forEach(u -> users.remove(u));
Para se livrar de ConcurrentModificationException, use CopyOnWriteArrayList
List<Something>
- você não tem ideia se é um CopyOnWriteArrayList. Portanto, este é mais um comentário medíocre, mas não uma resposta real.
Em vez de criar coisas estranhas, você pode apenas filter()
e então map()
seu resultado.
Isso é muito mais legível e seguro. Streams fará isso em apenas um loop.
Sim, você pode modificar ou atualizar os valores dos objetos na lista no seu caso da mesma forma:
users.stream().forEach(u -> u.setProperty("some_value"))
No entanto, a instrução acima fará atualizações nos objetos de origem. O que pode não ser aceitável na maioria dos casos.
Felizmente, temos outra maneira como:
List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());
O que retorna uma lista atualizada, sem prejudicar a antiga.
Como foi mencionado antes - você não pode modificar a lista original, mas pode transmitir, modificar e coletar itens em uma nova lista. Aqui está um exemplo simples de como modificar o elemento string.
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]
}
}
Você pode usar removeIf
para remover dados de uma lista condicionalmente.
Ex: - Se você deseja remover todos os números pares de uma lista, pode fazer da seguinte maneira.
final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());
list.removeIf(number -> number % 2 == 0);
Isso pode ser um pouco tarde. Mas aqui está um dos usos. Isso para obter a contagem do número de arquivos.
Crie um ponteiro para a memória (um novo obj neste caso) e modifique a propriedade do objeto. O fluxo Java 8 não permite modificar o próprio ponteiro e, portanto, se você declarar apenas contar como uma variável e tentar incrementar dentro do fluxo, ele nunca funcionará e lançará uma exceção do compilador em primeiro lugar
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 + "]";
}
}