Почему array [idx ++] + = увеличивает idx один раз в Java 8, но дважды в Java 9 и 10?

Если хотите, другой игрок в гольф по кодам написал следующий код:

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

При выполнении этого кода в Java 8, получаем следующий результат:

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 

При выполнении этого кода в Java 10, мы получаем следующий результат:

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 

Нумерация полностью отключена при использовании Java 10. Итак, что здесь происходит? Это ошибка Java 10?

Продолжение комментариев:


person Olivier Grégoire    schedule 04.06.2018    source источник
comment
Комментарии не подлежат расширенному обсуждению; этот разговор был перемещен в чат < / а>.   -  person Samuel Liew♦    schedule 05.06.2018
comment
Похоже, что это легко может быть проблемой в реальном коде. Если я правильно понял, что-то вроде array[i++] += n достаточно, чтобы i++ сработал дважды?   -  person JollyJoker    schedule 05.06.2018
comment
@JollyJoker Ограничено +=, применяется к косвенным String ссылкам. Итак, сначала ваш массив должен быть String[]. Проблема не возникает с int[], long[] и друзьями. Но да, вы в принципе правы!   -  person Olivier Grégoire    schedule 05.06.2018
comment
@ OlivierGrégoire массив не обязательно должен быть String[]. Если это Object[], а вы делаете array[expression] += "foo";, то это то же самое. Но да, это не применимо к примитивным массивам, так как оно должно содержать ссылки типа String (Object[], CharSequence[], Comparable[],…), чтобы сохранить результат конкатенации строк.   -  person Holger    schedule 05.06.2018
comment
@danadam Поскольку код скомпилирован с виртуальной машиной Java 8 или чем-то подобным: ключевое слово Java-10 var не распознается (например, в var x = "";.   -  person Olivier Grégoire    schedule 05.06.2018
comment
Этому был присвоен идентификатор ошибки JDK-8204322.   -  person Stuart Marks    schedule 05.06.2018
comment
@StuartMarks, спасибо! Это было интегрировано в ответ: я действительно хотел, чтобы вопрос оставался вопросом о том, нормально это или ошибка. Тем не менее, мы могли бы более четко указать идентификатор ошибки в ответе. Я его щас адаптирую.   -  person Olivier Grégoire    schedule 05.06.2018
comment
Очень интересный баг, спасибо. Мы уже исправили это в JDK11   -  person Vicente Romero    schedule 06.06.2018
comment
Спасибо @VicenteRomero за то, что так быстро исправили эту ошибку! Когда будет публично доступен бэкпорт для Java 10?   -  person Olivier Grégoire    schedule 07.06.2018
comment
Что вы имеете в виду под нестандартным кодом?   -  person xehpuk    schedule 08.06.2018
comment
Операторы @xehpuk с несколькими эффектами / побочными эффектами.   -  person Olivier Grégoire    schedule 08.06.2018
comment
Подобные вещи заставляют меня поверить, что Oracle не может должным образом обрабатывать Java-разработку.   -  person nomadSK25    schedule 04.06.2019
comment
@ OlivierGrégoire, извините за поздний ответ, просто чтобы завершить обсуждение, проблема была перенесена в JDK 10 06.06.2018 bugs.openjdk.java.net/browse/JDK-8204873   -  person Vicente Romero    schedule 27.02.2021


Ответы (1)


Это ошибка javac, начиная с JDK 9 (который внес некоторые изменения в отношении конкатенации строк, что, как я подозреваю, является частью проблемы), , что подтверждено командой javac под идентификатором ошибки JDK-8204322. Если вы посмотрите на соответствующий байт-код для строки:

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

It is:

  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

Где последний aaload - это фактическая нагрузка из массива. Однако часть

  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

Что примерно соответствует выражению array[i++%size] (за вычетом фактической загрузки и хранения), присутствует там дважды. Это неверно, как указано в спецификации в jls-15.26.2:

Составное выражение присваивания в форме E1 op= E2 эквивалентно E1 = (T) ((E1) op (E2)), где T - это тип E1, за исключением того, что E1 оценивается только один раз.

Итак, для выражения array[i++%size] += i + " "; часть array[i++%size] должна оцениваться только один раз. Но он оценивается дважды (один раз для загрузки и один раз для магазина).

Так что да, это ошибка.


Некоторые обновления:

Ошибка исправлена ​​в JDK 11 и была перенесена на JDK 10 (здесь и здесь), но не в JDK 9, поскольку он больше не получает общедоступные обновления.

Алексей Шипилев упоминает страница JBS (и @DidierL в комментариях здесь):

Обходной путь: скомпилировать с -XDstringConcat=inline

Это вернется к использованию StringBuilder для конкатенации и не содержит ошибки.

person Jorn Vernee    schedule 04.06.2018
comment
Между прочим, это относится ко всему выражению с левой стороны, а не только к подвыражению, обеспечивающему индекс. Это выражение может быть сколь угодно сложным. См., Например, IntStream.range(0, 10) .peek(System.out::println).boxed().toArray()[0] += ""; - person Holger; 05.06.2018
comment
@Holger Левая часть даже не требует использования массивов, проблема также возникает с простым test().field += "sth". - person Didier L; 05.06.2018
comment
@DidierL Я знаю. Я это уже проверял. Я просто не нашел простого примера, подходящего для комментария, так как не существует класса JRE с подходящим общедоступным полем. Я уже писал комментарий, относящийся к += в целом (если это конкатенация строк), но, видимо, он был удален из-за саркастического дополнительного предложения. Между прочим, поле может быть даже static, что делает избыточную оценку еще менее ненужной. - person Holger; 05.06.2018
comment
Не то чтобы это важно, поведение в любом случае ужасно нарушено, но первая оценка предназначена для хранилища, а вторая - для загрузки, поэтому array[index++] += "x"; будет читать из array[index+1] и писать в array[index] ... - person Holger; 05.06.2018
comment
Согласно отчету об ошибке, версия прямого исправления - 11, и она будет перенесена на 10. Значит, для 9 не будет никаких резервных копий? - person The Coder; 06.06.2018
comment
@TheCoder Да, я так думаю. JDK 9 не является выпуском с долгосрочной поддержкой (LTS). Был JDK 8, а следующим выпуском LTS будет JDK 11. См. Здесь: oracle.com/technetwork/java/javase/eol-135779.html Обратите внимание, что общедоступные обновления JDK 9 закончились в марте. - person Jorn Vernee; 06.06.2018
comment
@Holger вау, это действительно плохо, я удивлен, что никто не видел этого раньше и не было тестов. - person agilob; 06.06.2018
comment
Не то, чтобы жаловаться. Если исправление не будет перенесено в JDK 9, то практически никто не должен запускать свои приложения JDK 9 :( - person The Coder; 06.06.2018
comment
@TheCoder, если вы обновились до Java 9, вам нужно было обновиться до Java 10, как только она вышла, так как она подписала конец общедоступных обновлений для Java 9. То же самое будет с 10 и 11. 11 будет LTS, поэтому это должно быть довольно типично прыгать с 8 на 11. - person Didier L; 06.06.2018
comment
На JDK-8204322 Алексей Шипилев предложил компилировать с -XDstringConcat=inline в качестве обходного пути для тех, кому это нужно. - person Didier L; 06.06.2018