Modificar una variable local en forEach
da un error de compilación:
Normal
int ordinal = 0;
for (Example s : list) {
s.setOrdinal(ordinal);
ordinal++;
}
Con Lambda
int ordinal = 0;
list.forEach(s -> {
s.setOrdinal(ordinal);
ordinal++;
});
¿Alguna idea de cómo resolver esto?
Respuestas:
Usa una envoltura
Cualquier tipo de envoltorio es bueno.
Con Java 8+ , use un
AtomicInteger
:... o una matriz:
Con Java 10+ :
Nota: tenga mucho cuidado si utiliza una transmisión en paralelo. Es posible que no obtenga el resultado esperado. Otras soluciones como la de Stuart podrían adaptarse mejor a esos casos.
Para tipos distintos a
int
Por supuesto, esto sigue siendo válido para tipos distintos de
int
. Solo necesita cambiar el tipo de envoltura aAtomicReference
una matriz de ese tipo. Por ejemplo, si usa aString
, simplemente haga lo siguiente:Usa una matriz:
O con Java 10+:
fuente
int[] ordinal = { 0 };
? Puedes explicarlo. Graciasclass MyClass$1 { int value; }
En una clase habitual, esto significa que crea una clase con una variable privada de paquete denominadavalue
. Esto es lo mismo, pero la clase es anónima para nosotros, no para el compilador o la JVM. Java compilará el código como si lo fueraMyClass$1 wrapper = new MyClass$1();
. Y la clase anónima se convierte en una clase más. Al final, solo agregamos azúcar sintáctico encima para que sea legible. Además, la clase es una clase interna con un campo privado de paquete. Ese campo es utilizable.Esto está bastante cerca de un problema XY . Es decir, la pregunta que se hace es esencialmente cómo mutar una variable local capturada de una lambda. Pero la tarea real que tenemos entre manos es cómo numerar los elementos de una lista.
En mi experiencia, más del 80% de las veces hay una cuestión de cómo mutar un local capturado desde dentro de una lambda, hay una mejor manera de proceder. Por lo general, esto implica una reducción, pero en este caso, la técnica de ejecutar una secuencia sobre los índices de la lista se aplica bien:
fuente
list
es unaRandomAccess
listaSi solo necesita pasar el valor desde el exterior a la lambda y no sacarlo, puede hacerlo con una clase anónima regular en lugar de una lambda:
fuente
Como las variables utilizadas desde fuera de lamda deben ser (implícitamente) finales, debe usar algo como
AtomicInteger
o escribir su propia estructura de datos.Consulte https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#accessing-local-variables .
fuente
Si está en Java 10, puede usar
var
para eso:fuente
Una alternativa a
AtomicInteger
(o cualquier otro objeto capaz de almacenar un valor) es usar una matriz:Pero vea la respuesta de Stuart : podría haber una mejor manera de lidiar con su caso.
fuente
Sé que es una vieja pregunta, pero si se siente cómodo con una solución alternativa, considerando el hecho de que la variable externa debe ser definitiva, simplemente puede hacer esto:
Quizás no sea la más elegante, ni siquiera la más correcta, pero servirá.
fuente
Puede resumirlo para solucionar el problema del compilador, pero recuerde que no se recomiendan los efectos secundarios en lambdas.
Para citar el javadoc
fuente
Tuve un problema ligeramente diferente. En lugar de incrementar una variable local en forEach, necesitaba asignar un objeto a la variable local.
Resolví esto definiendo una clase de dominio interno privado que envuelve tanto la lista sobre la que quiero iterar (countryList) como la salida que espero obtener de esa lista (foundCountry). Luego, usando Java 8 "forEach", itero sobre el campo de lista, y cuando se encuentra el objeto que quiero, asigno ese objeto al campo de salida. Entonces esto asigna un valor a un campo de la variable local, sin cambiar la variable local en sí. Creo que dado que la variable local en sí no se cambia, el compilador no se queja. Luego puedo usar el valor que capturé en el campo de salida, fuera de la lista.
Objeto de dominio:
Objeto envoltorio:
Operación iterativa:
Puede eliminar el método de la clase contenedora "setCountryList ()" y hacer que el campo "countryList" sea final, pero no obtuve errores de compilación dejando estos detalles como están.
fuente
Para tener una solución más general, puede escribir una clase Wrapper genérica:
(esta es una variante de la solución dada por Almir Campos).
En el caso específico, esta no es una buena solución, ya que
Integer
es peor queint
para su propósito, de todos modos esta solución es más general, creo.fuente
Sí, puede modificar las variables locales desde dentro de las lambdas (de la manera que se muestra en las otras respuestas), pero no debe hacerlo. Las lambdas han sido para un estilo funcional de programación y esto significa: Sin efectos secundarios. Lo que quieres hacer se considera de mal estilo. También es peligroso en caso de corrientes paralelas.
Debería encontrar una solución sin efectos secundarios o utilizar un bucle for tradicional.
fuente