Saltar al contenido

Expresiones mejoradas de bucle y lambda ‘for’

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 o Iterable producido por la expresión. El significado preciso de lo mejorado for declaración se da por traducción a un básico for 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 argumento X, luego deja I ser el tipo java.util.Iterator; de lo contrario, deja I ser el tipo crudo java.util.Iterator.

    El mejorado for declaración es equivalente a un básico for 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 mejorada for declaración.

    El mejorado for declaración es equivalente a un básico for 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ó.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *