Pour un défi, n autre joueur de codea écrit le code suivant :
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);
}
}
}
Lors de l'exécution de ce code dans Java 8, nous obtenons le résultat suivant:
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
Lors de l'exécution de ce code dans Java 10, nous obtenons le résultat suivant:
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 numérotation est entièrement désactivée avec Java 10. Que se passe-t-il ici? Est-ce un bogue dans Java 10?
Didier L a découvert que le problème pouvait être reproduit avec le code beaucoup plus petit et plus compréhensible:
class Main {
public static void main(String[] args) {
String[] array = { "" };
array[test()] += "a";
}
static int test() {
System.out.println("evaluated");
return 0;
}
}
Résultat lors de la compilation dans Java 8:
evaluated
Résultat lors de la compilation dans Java 9 et 10:
evaluated
evaluated
Le problème semble être limité à l'opérateur de concaténation de chaînes et d'affectation (+=
) avec une expression avec un ou des effets secondaires comme opérande de gauche, comme dans array[test()]+="a"
, array[ix++]+="a"
, test()[index]+="a"
ou test().field+="a"
. Pour activer la concaténation de chaînes, au moins un des côtés doit avoir le type String
. Essayer de reproduire cela sur d'autres types ou constructions a échoué.
Ceci est un bogue dans javac
à partir de JDK 9 (qui a apporté quelques modifications en ce qui concerne la concaténation de chaînes, qui, je suppose, fait partie du problème), confirmé par l’équipe javac
sous l'identifiant de bogue JDK-8204322 . Si vous regardez le bytecode correspondant à la ligne:
array[i++%size] += i + " ";
Il 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
Où le dernier aaload
est la charge réelle du tableau. Cependant, la partie
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
Ce qui correspond à peu près à l'expression array[i++%size]
(moins la charge et le stockage réels), est là deux fois. Ceci est incorrect, comme le spécifie la spécification dans jls-15.26.2 :
Une expression d'affectation composée de la forme
E1 op= E2
équivaut àE1 = (T) ((E1) op (E2))
, oùT
est le type deE1
, , sauf queE1
n'est évalué qu'une fois.
Ainsi, pour l'expression array[i++%size] += i + " ";
, la partie array[i++%size]
ne doit être évaluée qu'une fois. Mais il est évalué deux fois (une fois pour la charge et une fois pour le magasin).
Alors oui, c'est un bug.
Le bogue est corrigé dans JDK 11 et il y aura un port de retour vers JDK 1 (mais pas JDK 9, car il ne reçoit plus de mises à jour publiques ).
Aleksey Shipilev mentionne sur le page JBS (et @DidierL dans les commentaires ici):
Solution: compilez avec
-XDstringConcat=inline
Cela reviendra à utiliser StringBuilder
pour faire la concaténation, et n'a pas le bogue.