El paso a paso o código que verás en este artículo es la resolución más eficiente y válida que hallamos a esta duda o dilema.
Solución:
Las expresiones lambda funcionan como devoluciones de llamada. En el momento en que se pasan en el código, “almacenan” los valores externos (o referencias) que necesitan para operar (como si estos valores se pasaran como argumentos en una llamada de función. Esto simplemente está oculto para el desarrollador). En su primer ejemplo, podría solucionar el problema almacenando k
a una variable separada, como d:
for (int k = 0; k < 10; k++)
final int d = k
new Thread(() -> System.out.println(d)).start();
Efectivamente final
significa que, en el ejemplo anterior, puede omitir la palabra clave “final”, porque d
es efectivamente final, ya que nunca se modifica dentro de su alcance.
for
los bucles funcionan de manera diferente. Son código iterativo (a diferencia de una devolución de llamada). Trabajan dentro de su alcance respectivo y pueden usar todas las variables en su propia pila. Esto significa que el for
El bloque de código del bucle es parte del bloque de código externo.
En cuanto a su pregunta destacada:
Un mejorado for
loop no opera con un índice-contador regular, al menos no directamente. Mejorado for
los bucles (sobre no matrices) crean un iterador oculto. Puede probar esto de la siguiente manera:
Collection mySet = new HashSet<>();
mySet.addAll(Arrays.asList("A", "B", "C"));
for (String myString : mySet)
if (myString.equals("B"))
mySet.remove(myString);
El ejemplo anterior provocará una ConcurrentModificationException. Esto se debe a que el iterador se da cuenta de que la colección subyacente ha cambiado durante la ejecución. Sin embargo, en su ejemplo, el bucle externo crea una variable ‘efectivamente final’ arg
al que se puede hacer referencia dentro de la expresión lambda, porque el valor se captura en el momento de la ejecución.
La prevención de la captura de valores ‘no efectivamente finales’ es más o menos una precaución en Java, porque en otros lenguajes (como JavaScript, por ejemplo) esto funciona de manera diferente.
Entonces, el compilador podría traducir teóricamente su código, capturar el valor y continuar, pero tendría que almacenar ese valor de manera diferente y probablemente obtendría resultados inesperados. Por lo tanto, el equipo que desarrolló lambdas para Java 8 excluyó correctamente este escenario, previniéndolo con una excepción.
Si alguna vez necesita cambiar los valores de las variables externas dentro de las expresiones lambda, puede declarar un elemento de un solo elemento array:
String[] myStringRef = "before" ;
someCallingMethod(() -> myStringRef[0] = "after" );
System.out.println(myStringRef[0]);
O usar AtomicReference
para que sea seguro para subprocesos. Sin embargo, con su ejemplo, esto probablemente devolvería “antes” ya que la devolución de llamada probablemente se ejecute después de la ejecución de println.
En un bucle for mejorado, la variable se inicializa en cada iteración. De §14.14.2 del Especificación del lenguaje Java (JLS):
…
Cuando un mejorado
for
se ejecuta la instrucción, la variable local se inicializa, en cada iteración del bucle, a los elementos sucesivos del array oIterable
producido por la expresión. El significado preciso de lo mejoradofor
declaración se da por traducción a un básicofor
declaración, como sigue:
Si el tipo de Expresión es un subtipo de
Iterable
, entonces la traducción es la siguiente.Si el tipo de Expresión es un subtipo de
Iterable
para algún tipo de argumentoX
, luego dejaI
ser el tipojava.util.Iterator
; de lo contrario, dejaI
ser el tipo crudojava.util.Iterator
.El mejorado
for
declaración es equivalente a un básicofor
declaración de la forma:for (I #i = Expression.iterator(); #i.hasNext(); ) VariableModifier TargetType Identifier = (TargetType) #i.next(); Statement
…
De lo contrario, el Expresión necesariamente tiene un array escribe,
T[]
.Dejar
L1 ... Lm
ser la secuencia (posiblemente vacía) de etiquetas inmediatamente anterior a la mejoradafor
declaración.El mejorado
for
declaración es equivalente a un básicofor
declaración de la forma:T[] #a = Expression; L1: L2: ... Lm: for (int #i = 0; #i < #a.length; #i++) VariableModifier TargetType Identifier = #a[#i]; Statement
...
En otras palabras, su bucle for mejorado es equivalente a:
ArrayList listOfInt = new ArrayList<>();
// add elements...
for (Iterator itr = listOfInt.iterator(); itr.hasNext(); )
Integer arg = itr.next();
new Thread(() -> System.out.println(arg)).start();
Dado que la variable se inicializa en cada iteración, es efectivamente final (a menos que modifique la variable dentro del ciclo).
Por el contrario, la variable en el bucle for básico (k
en su caso) se inicializa una vez y actualizado cada iteración (si un "ForUpdate"está presente, p. ej. k++
). Consulte §14.14.1 de JLS para obtener más información. Dado que la variable se actualiza, cada iteración es no final ni efectivamente final.
La necesidad de una variable final o efectivamente final es obligatoria y explicada por §15.27.2 de la JLS:
...
Cualquier variable local, parámetro formal o parámetro de excepción utilizado pero no declarado en una expresión lambda debe declararse
final
o ser efectivamente final§4.12.4), o se produce un error en tiempo de compilación cuando se intenta el uso.Cualquier variable local utilizada pero no declarada en un cuerpo lambda debe asignarse definitivamente (§16 (asignación definitiva)) antes del cuerpo lambda, o se produce un error en tiempo de compilación.
Se aplican reglas similares sobre el uso de variables en el cuerpo de una clase interna (§8.1.3). La restricción a las variables finales efectivamente prohíbe el acceso a variables locales que cambian dinámicamente, cuya captura probablemente introduciría problemas de concurrencia. En comparación con el
final
restricción, reduce la carga administrativa de los programadores.La restricción a las variables efectivamente finales incluye variables de bucle estándar, pero no mejoradas.
for
variables de bucle, que se tratan como distintas para cada iteración del bucle (§14.14.2)....
Esa última oración incluso menciona explícitamente la diferencia entre las variables básicas del ciclo for y las mejoradas para las variables del ciclo.
Las otras respuestas son útiles, pero no parecen abordar la pregunta directamente y responderla en términos claros.
En su primer ejemplo, está intentando acceder k
de la expresión lambda. El problema aquí es que k
cambia su valor con el tiempo (k++
se llama después de cada iteración del ciclo). Las expresiones lambda capturan referencias externas, pero deben marcarse como final
o ser "efectivamente definitivos" (es decir, marcarlos como final
todavía produciría un código válido). Esto es para evitar problemas de simultaneidad; para cuando se ejecute el hilo que creaste, k
ya podría tener un nuevo valor.
En su segundo ejemplo, por otro lado, la variable a la que accede es arg
, cual es reinicializado con cada iteración del bucle for mejorado (compárese con el ejemplo anterior, donde k
simplemente se actualizó), por lo que está creando una variable completamente nueva con cada iteración. Como acotación al margen, también puede declarar explícitamente la variable de iteración de un bucle for mejorado como final
:
for (final Integer arg : listOfInt)
new Thread(() -> System.out.println(arg)).start();
Esto asegura que el valor arg
las referencias no habrán cambiado para cuando se ejecute el hilo que creó.