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 enarray[test()]+="a"
,array[ix++]+="a"
,test()[index]+="a"
otest().field+="a"
. Para habilitar la concatenación de cadenas, al menos uno de los lados debe tener el tipoString
. Intentar reproducir esto en otros tipos o construcciones falló.
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 aE1 = (T) ((E1) op (E2))
dóndeT
es el tipo deE1
, excepto esoE1
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á dearray[index+1]
y escribe aarray[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
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 indirectoString
referencias Entonces, primero, su matriz debe ser unaString[]
. El problema no ocurre conint[]
,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 esObject[]
y lo hacearray[expression] += "foo";
, es lo mismo. Pero sí, no se aplica a matrices primitivas, ya que debe poder contener referencias de tipoString
(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