it-swarm.com.de

Warum erhöht array [idx ++] + = "a" idx einmal in Java 8, aber zweimal in Java 9 und 10?

Für eine Herausforderung ein Mit-Code-Golferschrieb den folgenden Code :

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);
    }
  }
}

Wenn dieser Code in Java 8 ausgeführt wird, erhalten wir das folgende Ergebnis:

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 

Wenn dieser Code in Java 10 ausgeführt wird, erhalten wir das folgende Ergebnis:

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 

Die Nummerierung ist mit Java 10 gänzlich deaktiviert. Was passiert also hier? Ist es ein Fehler in Java 10?

Follow-ups aus den Kommentaren:

  • Das Problem tritt auf, wenn es mit Java 9 oder höher kompiliert wurde (wir haben es in Java 10 gefunden). Wenn Sie diesen Code unter Java 8 kompilieren und dann unter Java 9 oder einer späteren Version, einschließlich Java 11, ausführen, erhalten Sie das erwartete Ergebnis.
  • Diese Art von Code entspricht nicht dem Standard, ist jedoch gemäß der Spezifikation gültig. Es wurde von Kevin Cruijssen in einer Diskussion in einer Golf Herausforderung gefunden, daher der seltsame Gebrauch Fall aufgetreten.
  • Didier L hat herausgefunden, dass das Problem mit dem viel kleineren und verständlicheren Code reproduziert werden kann:

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

    Ergebnis beim Kompilieren in Java 8:

    evaluated
    

    Ergebnis beim Kompilieren in Java 9 und 10:

    evaluated
    evaluated
    
  • Das Problem scheint auf den Operator für die Verkettung und Zuweisung von Zeichenfolgen (+=) mit einem Ausdruck mit Nebeneffekten als linkem Operanden wie in array[test()]+="a", array[ix++]+="a", test()[index]+="a" oder test().field+="a". Zum Aktivieren der Zeichenfolgenverkettung muss mindestens eine der Seiten den Typ String haben. Der Versuch, dies auf anderen Typen oder Konstrukten zu reproduzieren, ist fehlgeschlagen.

722

Dies ist ein Fehler in javac ab JDK 9 (der einige Änderungen in Bezug auf die Verkettung von Zeichenfolgen vorgenommen hat, von denen ich vermute, dass sie Teil des Problems sind), wie vom javac - Team unter bestätigt) die Bug-ID JDK-8204322 . Wenn Sie sich den entsprechenden Bytecode für die Zeile ansehen:

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

Es ist:

  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

Wobei das letzte aaload die tatsächliche Last aus dem Array ist. Allerdings ist das Teil

  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

Was ungefähr dem Ausdruck array[i++%size] (abzüglich des tatsächlichen Ladens und Speicherns) entspricht, ist dort zweimal. Dies ist falsch, wie die Spezifikation in jls-15.26.2 sagt:

Ein zusammengesetzter Zuweisungsausdruck der Form E1 op= E2 entspricht E1 = (T) ((E1) op (E2)), wobei T der Typ von E1 ist mit der Ausnahme, dass E1 wird nur einmal ausgewertet.

Für den Ausdruck array[i++%size] += i + " "; sollte der Teil array[i++%size] nur einmal ausgewertet werden. Es wird jedoch zweimal ausgewertet (einmal für das Laden und einmal für das Speichern).

Also ja, das ist ein Fehler.


Einige Updates:

Der Fehler ist in JDK 11 behoben und es wird ein Back-Port zu JDK 1 (aber nicht JDK 9, da es werden keine öffentlichen Updates mehr empfangen ) geben.

Aleksey Shipilev erwähnt auf der JBS-Seite (und @DidierL in den Kommentaren hier):

Problemumgehung: Kompilieren Sie mit -XDstringConcat=inline

Dadurch wird wieder StringBuilder für die Verkettung verwendet, und der Fehler tritt nicht auf.

611
Jorn Vernee