Для вызова товарищ по гольфунаписал следующий код:
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?
Дидье Л обнаружил, что проблему можно воспроизвести с помощью гораздо меньшего и более понятного кода:
class Main {
public static void main(String[] args) {
String[] array = { "" };
array[test()] += "a";
}
static int test() {
System.out.println("evaluated");
return 0;
}
}
Результат при компиляции на Java 8:
evaluated
Результат при компиляции на Java 9 и 10:
evaluated
evaluated
Проблема, похоже, ограничивается оператором конкатенации и присваивания строк (+=) с выражением с побочными эффектами в качестве левого операнда, как в array[test()]+ = "a", array[ix++]+ = "a", test()[index]+ = "a" или test().field+ = "a". Чтобы включить конкатенацию строк, по крайней мере одна из сторон должна иметь тип String. Попытка воспроизвести это на других типах или конструкциях не удалась.
Похоже, что это легко может быть проблемой в «реальном» коде. Если я правильно понял, чего-то вроде array[i++] += n достаточно, чтобы дважды запустить i++?
@JollyJoker Ограничено +=, применяемым к косвенным ссылкам на String. Итак, сначала ваш массив должен быть String[]. Проблема не возникает с int[], long[] и другими. Но да, вы в принципе правы!
@ OlivierGrégoire массив не обязательно должен быть String[]. Если это Object[], а вы - array[expression] += "foo";, то это то же самое. Но да, это не относится к примитивным массивам, так как он должен иметь возможность хранить ссылки типа String (Object[], CharSequence[], Comparable[],…), чтобы сохранить результат конкатенации строк.
Почему я не могу воспроизвести это в jdoodle? jdoodle.com/a/wSU
@danadam Поскольку код скомпилирован с виртуальной машиной Java 8 или чем-то подобным: ключевое слово Java-10 var не распознается (например, в var x = "";.
Этому был присвоен идентификатор ошибки JDK-8204322.
@StuartMarks, спасибо! Это было интегрировано в ответ: я действительно хотел, чтобы вопрос оставался вопросом о том, нормально это или ошибка. Тем не менее, мы могли бы более четко указать идентификатор ошибки в ответе. Я его щас адаптирую.
Что вы имеете в виду под «нестандартным» кодом?
Операторы @xehpuk с несколькими эффектами / побочными эффектами.
Подобные вещи заставляют меня поверить в то, что «Oracle не может должным образом обрабатывать Java-разработку»
Очень интересный баг, спасибо. Мы уже исправили это в JDK11
Спасибо @VicenteRomero за то, что так быстро исправили эту ошибку! Когда будет публично доступен бэкпорт для Java 10?
@ OlivierGrégoire, извините за поздний ответ, просто чтобы завершить обсуждение, проблема была перенесена в JDK 10 06.06.2018 bugs.openjdk.java.net/browse/JDK-8204873




Это ошибка в javac, начиная с JDK 9 (который внес некоторые изменения в отношении конкатенации строк, что, как я подозреваю, является частью проблемы), как подтверждено командой javac под идентификатором ошибки JDK-8204322. Если вы посмотрите на соответствующий байт-код для строки:
array[i++%size] += i + " ";
Это:
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:
A compound assignment expression of the form
E1 op= E2is equivalent toE1 = (T) ((E1) op (E2)), whereTis the type ofE1, except thatE1is evaluated only once.
Итак, для выражения array[i++%size] += i + " "; часть array[i++%size] должна оцениваться только один раз. Но он оценивается дважды (один раз для загрузки и один раз для магазина).
Так что да, это ошибка.
Ошибка исправлена в JDK 11 и была перенесена на JDK 10 (здесь и здесь), но не на JDK 9, поскольку он больше не получает общедоступные обновления.
Алексей Шипилев упоминает о Страница JBS (и @DidierL в комментариях здесь):
Workaround: compile with
-XDstringConcat=inline
Это вернется к использованию StringBuilder для выполнения конкатенации и не содержит ошибки.
Между прочим, это относится ко всему выражению с левой стороны, а не только к подвыражению, обеспечивающему индекс. Это выражение может быть сколь угодно сложным. См. Например IntStream.range(0, 10) .peek(System.out::println).boxed().toArray()[0] += "";…
@Holger Левая часть даже не требует использования массивов, проблема также возникает с простым test().field += "sth".
@DidierL Я знаю. Я это уже проверял. Я просто не нашел простого примера, подходящего для комментария, так как не существует класса JRE с подходящим общедоступным полем. Я уже писал комментарий, относящийся к += в целом (если это конкатенация строк), но, видимо, он был удален из-за саркастического дополнительного предложения. Кстати, это поле может быть даже static, что делает избыточную оценку еще менее ненужной.
Не то, чтобы это важно, поведение ужасно нарушено, но первая оценка предназначена для магазина, а вторая - для загрузки, поэтому array[index++] += "x"; будет читать с array[index+1] и записывать в array[index]…
Согласно отчету об ошибке, версия прямого исправления - 11, и она будет перенесена на 10. Значит, для 9 не будет никаких резервных копий?
@TheCoder Да, я так думаю. JDK 9 не является выпуском с долгосрочной поддержкой (LTS). Был JDK 8, а следующим выпуском LTS будет JDK 11. См. Здесь: oracle.com/technetwork/java/javase/eol-135779.html Обратите внимание, что публичные обновления JDK 9 закончились в марте.
@Holger вау, это действительно плохо, я удивлен, что никто не видел этого раньше и не было тестов.
Не то, чтобы жаловаться. Если исправление не будет перенесено в JDK 9, то практически никто не должен запускать свои приложения JDK 9 :(
@TheCoder, если вы обновились до Java 9, вам нужно было обновиться до Java 10, как только она вышла, так как она подписала конец общедоступных обновлений для Java 9. То же самое будет с 10 и 11. 11 будет LTS, поэтому это должно быть довольно типично прыгать с 8 на 11.
На JDK-8204322 Алексей Шипилев предложил скомпилировать с -XDstringConcat=inline в качестве обходного пути для тех, кому это нужно.
@DidierL Действительно может подтвердить, что флаг компилятора работает для предоставленного кода @ OlivierGrégoire в TIO Java 10.
Комментарии не подлежат расширенному обсуждению; этот разговор был переехал в чат.