Modificar uma variável local em forEach
dá um erro de compilação:
Normal
int ordinal = 0;
for (Example s : list) {
s.setOrdinal(ordinal);
ordinal++;
}
Com lambda
int ordinal = 0;
list.forEach(s -> {
s.setOrdinal(ordinal);
ordinal++;
});
Alguma ideia de como resolver isso?
Respostas:
Use um invólucro
Qualquer tipo de embalagem é bom.
Com o Java 8+ , use
AtomicInteger
:... ou uma matriz:
Com Java 10+ :
Nota: tenha muito cuidado se você usar um fluxo paralelo. Você pode não acabar com o resultado esperado. Outras soluções como a de Stuart podem ser mais adaptadas para esses casos.
Para tipos diferentes de
int
Claro, isso ainda é válido para tipos diferentes de
int
. Você só precisa alterar o tipo de embalagem para umAtomicReference
ou uma matriz desse tipo. Por exemplo, se você usar umString
, faça o seguinte:Use uma matriz:
Ou com Java 10+:
fonte
int[] ordinal = { 0 };
? Você pode explicar isso. Obrigadoclass MyClass$1 { int value; }
Em uma classe normal, isso significa que você cria uma classe com uma variável privada de pacote chamadavalue
. É o mesmo, mas a classe é realmente anônima para nós, não para o compilador ou a JVM. Java irá compilar o código como se fosseMyClass$1 wrapper = new MyClass$1();
. E a classe anônima se torna apenas outra classe. No final, apenas adicionamos açúcar sintático em cima para torná-lo legível. Além disso, a classe é uma classe interna com campo de pacote privado. Esse campo é utilizável.Isso é quase um problema XY . Ou seja, a questão que está sendo feita é essencialmente como transformar uma variável local capturada de um lambda. Mas a tarefa real em questão é como numerar os elementos de uma lista.
Na minha experiência, em mais de 80% das vezes, há uma questão de como transformar um local capturado de dentro de um lambda, há uma maneira melhor de proceder. Normalmente, isso envolve redução, mas, neste caso, a técnica de executar um fluxo sobre os índices da lista se aplica bem:
fonte
list
for umaRandomAccess
listaSe você só precisa passar o valor de fora para o lambda, e não retirá-lo, pode fazer isso com uma classe anônima regular em vez de um lambda:
fonte
Como as variáveis usadas de fora do lamda devem ser (implicitamente) finais, você deve usar algo como
AtomicInteger
ou escrever sua própria estrutura de dados.Consulte https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables .
fonte
Se você estiver no Java 10, pode usar
var
para isso:fonte
Uma alternativa para
AtomicInteger
(ou qualquer outro objeto capaz de armazenar um valor) é usar uma matriz:Mas veja a resposta do Stuart : pode haver uma maneira melhor de lidar com o seu caso.
fonte
Eu sei que é uma pergunta antiga, mas se você se sentir confortável com uma solução alternativa, considerando o fato de que a variável externa deve ser final, você pode simplesmente fazer isto:
Talvez não seja o mais elegante, ou mesmo o mais correto, mas servirá.
fonte
Você pode embrulhar para contornar o compilador, mas lembre-se de que os efeitos colaterais em lambdas são desencorajados.
Para citar o javadoc
fonte
Eu tive um problema um pouco diferente. Em vez de incrementar uma variável local em forEach, precisei atribuir um objeto à variável local.
Resolvi isso definindo uma classe de domínio interno privado que envolve a lista que desejo iterar (countryList) e a saída que espero obter dessa lista (foundCountry). Em seguida, usando Java 8 "forEach", itero sobre o campo de lista e, quando o objeto que desejo é encontrado, atribuo esse objeto ao campo de saída. Portanto, isso atribui um valor a um campo da variável local, não alterando a própria variável local. Acredito que, como a variável local em si não é alterada, o compilador não reclama. Posso então usar o valor que capturei no campo de saída, fora da lista.
Objeto de domínio:
Objeto Wrapper:
Operação de iteração:
Você poderia remover o método da classe wrapper "setCountryList ()" e tornar o campo "countryList" final, mas não recebi erros de compilação deixando esses detalhes como estão.
fonte
Para ter uma solução mais geral, você pode escrever uma classe Wrapper genérica:
(esta é uma variante da solução dada por Almir Campos).
No caso específico esta não é uma boa solução, pois
Integer
é pior do queint
para o seu propósito, de qualquer forma esta solução é mais geral eu acho.fonte
Sim, você pode modificar variáveis locais de dentro dos lambdas (da maneira mostrada pelas outras respostas), mas você não deve fazer isso. Lambdas são para um estilo funcional de programação e isso significa: Sem efeitos colaterais. O que você quer fazer é considerado um estilo ruim. Também é perigoso no caso de fluxos paralelos.
Você deve encontrar uma solução sem efeitos colaterais ou usar um loop for tradicional.
fonte