Modificando objetos dentro do fluxo em Java8 durante a iteração

87

Em fluxos Java8, tenho permissão para modificar / atualizar objetos dentro? Por exemplo. List<User> users:

users.stream().forEach(u -> u.setProperty("value"))
Tejas Gokhale
fonte

Respostas:

101

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 Spliteratorrelatórios a CONCURRENTcaracterí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 ConcurrentModificationExceptionou 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
Pshemo
fonte
4
Quais são as soluções se você deseja modificar os usuários?
Augustas
2
@Augustas Tudo depende de como você deseja modificar esta lista. Mas geralmente você deve evitar o Iteratorque 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.Collectionsclasse tem poucos métodos que podem ser úteis como addAll(CollectionOfNewElements,list).
Pshemo
@Pshemo, uma solução para isso é criar uma nova instância de uma coleção como ArrayList com os itens dentro de sua lista primária; itere sobre a nova lista e faça a operação na lista primária: new ArrayList <> (usuários) .stream.forEach (u -> users.remove (u));
MNZ de
2
@Blauhirn Solve what? Não temos permissão para modificar a fonte do fluxo ao usar o fluxo, mas podemos modificar o estado de seus elementos. Não importa se usamos map, foreach ou outros métodos de fluxo.
Pshemo
1
Em vez de modificar a coleção, sugiro usarfilter()
jontro
5

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

Andreas M. Oberheim
fonte
3

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 Collectionsimilar ArrayListcom 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));
MNZ
fonte
1

Para se livrar de ConcurrentModificationException, use CopyOnWriteArrayList

Krupali_wadekar
fonte
2
Isso está realmente correto, mas: isso depende da classe específica que é usada aqui. Mas quando você apenas sabe que tem um List<Something>- você não tem ideia se é um CopyOnWriteArrayList. Portanto, este é mais um comentário medíocre, mas não uma resposta real.
GhostCat
Se ele tivesse postado como um comentário, provavelmente alguém teria dito que ele não deveria postar respostas como comentários.
Bill K de
1

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.

Nicolas Zozol
fonte
1

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.

Gaurav Khanzode
fonte
0

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]
    }
}
Vadim
fonte
0

Você pode usar removeIfpara 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);
JJ
fonte
-1

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 + "]";
        }



    }
Joey587
fonte