¿Por qué la matriz[idx++]+=”a” aumentar idx una vez en Java 8 pero dos veces en Java 9 y 10?

5 minutos de lectura

avatar de usuario
Olivier Grégoire

Para un desafío, un compañero golfista de código escribió el siguiente código:

import java.util.*;
public class Main {
  public static void main(String[] args) {
    int size = 3;
    String[] array = new String[size];
    Arrays.fill(array, "");
    for (int i = 0; i <= 100;) {
      array[i++ % size] += i + " ";
    }
    for (String element: array) {
      System.out.println(element);
    }
  }
}

Al ejecutar este código en Java 8, obtenemos el siguiente resultado:

1 4 7 10 13 16 19 22 25 28 31 34 37 40 43 46 49 52 55 58 61 64 67 70 73 76 79 82 85 88 91 94 97 100 
2 5 8 11 14 17 20 23 26 29 32 35 38 41 44 47 50 53 56 59 62 65 68 71 74 77 80 83 86 89 92 95 98 101 
3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 66 69 72 75 78 81 84 87 90 93 96 99 

Al ejecutar este código en Java 10, obtenemos el siguiente resultado:

2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100 102 
2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100 

La numeración está completamente desactivada con Java 10. Entonces, ¿qué está pasando aquí? ¿Es un error en Java 10?

Seguimiento de los comentarios:

  • El problema aparece cuando se compila con Java 9 o posterior (lo encontramos en Java 10). Compilar este código en Java 8 y luego ejecutarlo en Java 9 o cualquier versión posterior, incluido el acceso anticipado de Java 11, da el resultado esperado.

  • Este tipo de código no es estándar, pero es válido según la especificación. Fue encontrado por Kevin Cruijssen en un discusión en un reto de golfde ahí el extraño caso de uso encontrado.

  • Didier L simplificó el problema con este código mucho más pequeño y comprensible:

      class Main {
        public static void main(String[] args) {
          String[] array = { "" };
          array[test()] += "a";
        }
        static int test() {
          System.out.println("evaluated");
          return 0;
        }
      }
    

    Resultado cuando se compila en Java 8:

      evaluated
    

    Resultado al compilar en Java 9 y 10:

      evaluated
      evaluated
    
  • El problema parece estar limitado al operador de asignación y concatenación de cadenas (+=) con una expresión con efecto(s) secundario(s) como el operando izquierdo, como en array[test()]+="a", array[ix++]+="a", test()[index]+="a"o test().field+="a". Para habilitar la concatenación de cadenas, al menos uno de los lados debe tener el tipo String. Intentar reproducir esto en otros tipos o construcciones falló.

  • Los comentarios no son para una discusión extensa; esta conversación se ha movido al chat.

    – Samuel Liev

    4 de junio de 2018 a las 23:22

  • @JollyJoker Está limitado a += aplicado a indirecto String referencias Entonces, primero, su matriz debe ser una String[]. El problema no ocurre con int[], long[] y amigos. Pero sí, ¡básicamente tienes razón!

    – Olivier Grégoire

    5 de junio de 2018 a las 8:11


  • @OlivierGrégoire la matriz no necesita ser String[]. Si esto es Object[] y lo hace array[expression] += "foo";, es lo mismo. Pero sí, no se aplica a matrices primitivas, ya que debe poder contener referencias de tipo String (Object[], CharSequence[], Comparable[]…), para almacenar el resultado de la concatenación de cadenas.

    – Holger

    5 de junio de 2018 a las 9:29

  • A esto se le ha asignado una identificación de error JDK-8204322.

    – Marcas de Stuart

    5 de junio de 2018 a las 15:19


  • @StuartMarks gracias! Eso se integró en la respuesta: realmente quería que la pregunta siguiera siendo una pregunta, sobre si es normal o un error. Sin embargo, podríamos ser más explícitos sobre la identificación del error en la respuesta. Lo adaptaré ahora mismo.

    – Olivier Grégoire

    5 de junio de 2018 a las 15:55


avatar de usuario
Jorn Vernée

Este es un error en javac a partir de JDK 9 (que realizó algunos cambios con respecto a la concatenación de cadenas, que sospecho que es parte del problema), como lo confirma el javac equipo con el ID de error JDK-8204322. Si observa el código de bytes correspondiente para la línea:

array[i++%size] += i + " ";

Está:

  21: aload_2
  22: iload_3
  23: iinc          3, 1
  26: iload_1
  27: irem
  28: aload_2
  29: iload_3
  30: iinc          3, 1
  33: iload_1
  34: irem
  35: aaload
  36: iload_3
  37: invokedynamic #5,  0 // makeConcatWithConstants:(Ljava/lang/String;I)Ljava/lang/String;
  42: aastore

donde el ultimo aaload es la carga real de la matriz. Sin embargo, la parte

  21: aload_2             // load the array reference
  22: iload_3             // load 'i'
  23: iinc          3, 1  // increment 'i' (doesn't affect the loaded value)
  26: iload_1             // load 'size'
  27: irem                // compute the remainder

Lo que corresponde aproximadamente a la expresión array[i++%size] (menos la carga y el almacenamiento reales), está allí dos veces. Esto es incorrecto, como dice la especificación en jls-15.26.2:

Una expresión de asignación compuesta de la forma E1 op= E2 es equivalente a E1 = (T) ((E1) op (E2))dónde T es el tipo de E1, excepto eso E1 se evalúa una sola vez.

Entonces, para la expresión array[i++%size] += i + " ";la parte array[i++%size] sólo debe evaluarse una vez. Pero se evalúa dos veces (una para la carga y otra para la tienda).

Así que sí, esto es un error.


Algunas actualizaciones:

El error se solucionó en JDK 11 y se retroportó a JDK 10 (aquí y aquí), pero no a JDK 9, ya que ya no recibe actualizaciones públicas.

Aleksey Shipilev menciona en el página JBS (y @DidierL en los comentarios aquí):

Solución alternativa: compilar con -XDstringConcat=inline

Eso volverá a usar StringBuilder para hacer la concatenación, y no tiene el error.

  • Por cierto, esto se aplica a toda la expresión del lado izquierdo, no solo a la subexpresión que proporciona el índice. Esta expresión puede ser arbitrariamente compleja. Ver por ejemplo IntStream.range(0, 10) .peek(System.out::println).boxed().toArray()[0] += "";

    – Holger

    5 de junio de 2018 a las 9:45


  • @Holger El lado izquierdo ni siquiera necesita involucrar matrices, el problema también ocurre con un simple test().field += "sth".

    – Didier L.

    5 de junio de 2018 a las 11:40

  • No es que importe, el comportamiento está terriblemente roto de todos modos, pero la primera evaluación es para la tienda y la segunda para la carga, por lo que array[index++] += "x"; leerá de array[index+1] y escribe a array[index]

    – Holger

    5 de junio de 2018 a las 12:08

  • @TheCoder Sí, creo que sí. JDK 9 no es una versión de soporte a largo plazo (LTS). JDK 8 fue, y el próximo lanzamiento de LTS es JDK 11. Vea aquí: oracle.com/technetwork/java/javase/eol-135779.html Tenga en cuenta que las actualizaciones públicas de JDK 9 finalizaron en marzo.

    – Jorn Vernée

    6 de junio de 2018 a las 8:37

  • En JDK-8204322, Aleksey Shipilev sugirió compilar con -XDstringConcat=inline como una solución, para aquellos que lo necesitan.

    – Didier L.

    6 jun 2018 a las 19:50

¿Ha sido útil esta solución?