Как сделать E1 += E2 незаконным, а E1 = E1 + E2 допустимым?

Я читал Java Puzzlers Блоха и Гафтера и добрался до головоломки 10 (Tweedledee). Суть этой головоломки в том, чтобы

предоставьте объявления для переменных x и i таким образом, чтобы это было допустимым утверждением:

x = x + i;

но это не так:

x += i;

Решение этого выглядит, согласно книге, так:

Object x = "Buy ";
String i = "Effective Java!";

В книге утверждается, что в операторе += правое выражение может быть любого типа, только если левое выражение имеет тип String. Однако я попытался запустить этот код, и он скомпилировался и запустился без проблем.

Затем я углубился в спецификацию языка Java. В разделе 15.26.2 говорится о двух случаях: когда левое выражение является выражением доступа к массиву, а когда нет. Если выражение левого операнда не является выражением доступа к массиву, то JLS ничего не говорит о том, что левое выражение является строкой. Когда это так, эта часть применяется:

Если T является ссылочным типом, то он должен быть String. Поскольку класс String является конечным классом, S также должен быть String. Поэтому проверка во время выполнения, которая иногда требуется для простого оператора присваивания, никогда не требуется для составного оператора присваивания.

❖ Сохраненное значение компонента массива и значение правого операнда используются для выполнения бинарной операции (конкатенации строк), указанной составным оператором присваивания (обязательно +=). Если эта операция завершается внезапно, выражение присваивания завершается внезапно по той же причине, и присваивание не происходит.

T — это тип левого операнда, определенный во время компиляции, а S — выбранный компонент массива. Поэтому я подумал, что изменю свой код на это:

Object[] x = {new Object()};
String i = "Effective Java!";
x[0] += i;

Но даже этот код компилируется и работает без проблем, хотя new Object() даже отдаленно не является String.

Почему это происходит? Означает ли это, что компилятор Java отличается от JLS? И можно ли еще как-то решить изначальную головоломку?


person Malcolm    schedule 08.01.2013    source источник
comment
Вы пытались создать объект, который переопределяет toString для печати в stdout/stderr?   -  person    schedule 08.01.2013
comment
Вы уверены в том, что должно компилироваться? Или, может быть, в книге опечатка. Если x и i, например, являются байтами, то x += i; допустимо, а x = x + i; — нет.   -  person Patricia Shanahan    schedule 08.01.2013
comment
Вы уверены, что действительно тестировали x += i, а не i += x? Я имею в виду, что (String) += (Object) будет легко компилироваться, когда (Object) += (String) не будет компилироваться.   -  person Dmitry Zaytsev    schedule 08.01.2013
comment
@Tinctorius Вам не нужно создавать объект для печати на стандартный вывод, результатом будет строка, которая выглядит примерно так: java.lang.Object@5a4b4b50Effective Java!. Таким образом, конкатенация происходит красиво.   -  person Malcolm    schedule 08.01.2013
comment
@Malcolm: Я просто предназначал это для (ленивой) отладки, чтобы увидеть, что на самом деле делает Java.   -  person    schedule 08.01.2013
comment
@PatriciaShanahan Абсолютно уверен, потому что это тема предыдущей головоломки, головоломки 9: Траляля.   -  person Malcolm    schedule 08.01.2013
comment
@DmitryZaitsev Я тестировал оба. С JDK7 оба работают.   -  person Malcolm    schedule 08.01.2013


Ответы (3)


Попробуйте с javac ‹ 1.4.2, там тоже будет работать.

Это было изменение между различными выпусками. Изменение для версии 1.4.2 (x += i; разрешено до, а не после):
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4642850

Это правильно, потому что версия JLS 2. определила:

Все составные операторы присваивания требуют, чтобы оба операнда были примитивного типа, за исключением +=, который позволяет правому операнду быть любого типа, если левый операнд имеет тип String.

Изменение для 7 (x += i; раньше не разрешалось, разрешено с тех пор):
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4741726

Что верно, начиная с версии JLS 3. (см. http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.26 предыдущее условие удалено)


Просто небольшое редактирование: я не вижу способа исправить/решить головоломку в Java 7.0_10.

person tb-    schedule 08.01.2013
comment
Хорошо, я вижу изменения в JLS, но как насчет кода, который выполняет доступ к массиву? Это не должно работать в соответствии с последней версией JLS, не так ли? - person Malcolm; 08.01.2013
comment
@Malcom Я не вижу никакого противоречия. Можно было бы иметь аргумент, если если T является ссылочным типом, то он должен быть String означает, что T должен быть String или что T обрабатывается как String. Я бы сказал, что это второй вариант, см. JLS 5.1.11. Преобразование строк. и 15.18.1. (If only one operand expression is of type String, then string conversion (§5.1.11) is performed on the other operand to produce a string at run-time.) - person tb-; 08.01.2013
comment
Да, но если преобразование строк применяется автоматически, то почему вообще написано предложение о String? Кроме того, преобразование происходит при выполнении бинарной операции, а происходит это после предполагаемой проверки, является ли операнд строкой. Лично я считаю, что это баг JLS: убрали строчку про требование String в начале абзаца и забыли об этой части. - person Malcolm; 08.01.2013
comment
Вы можете попробовать опубликовать это как ошибку и посмотреть, что произойдет. - person tb-; 09.01.2013
comment
Может быть, я сделаю это. Хорошо, я думаю, что теперь на вопрос дан ответ, за исключением того, что ответа на загадку нет, но теперь она, по-видимому, неразрешима. - person Malcolm; 09.01.2013
comment
Вот копия Спецификация языка Java, второе издание. - person Daniel Le; 21.07.2021

У меня есть следующее, и оно показывает данное решение как правильный ответ:

public class testIt
{
  public static void main(String args[])
  {
    new testIt();
  }

  public testIt()
  {
    Object x = "Buy";
    String i = "Effective Java!"

    x += i;

    x = x + i;
  }
}

когда я компилирую это, я получаю

testIt.java:  incompatible types
found:     java.lang.Object
required:  java.lang.String;

  x += i;
  ^
1 error
person jco.owens    schedule 08.01.2013
comment
Какую версию Java вы используете? Это компилируется и работает на Java 7. - person Peter Lawrey; 08.01.2013
comment
Затем Java 7 игнорирует JLS. - person ; 08.01.2013
comment
Но говорит ли JLS, что x += i не должно работать? Насколько я понимаю, JLS нужен String только в массивах, во всех остальных случаях без разницы. JLS просто говорит, что first, the left-hand operand is evaluated to produce a variable, а затем the saved value of the left-hand variable and the value of the right-hand operand are used to perform the binary operation indicated by the compound assignment operator, что ничем не отличается от x + i. Ничего не сказано о том, что левое выражение является строкой. - person Malcolm; 08.01.2013

В Java 6 вы можете сказать

Object x = 1;
String i = "i";
x = x + i; // compiles
x += i; // doesn't compile in Java 6, but does in Java 7.
System.out.println(x);

Почему это происходит?

Такой же как

x[0] = x[0] + i;

Скомпилировано с Java 7

Object[] x = {new Object()};
String i = "Effective Java!";
x[0] +=  i;
System.out.println(x[0]);

отпечатки

java.lang.Object@a62b39fEffective Java!

но не с обновлением Java 6 37

    Error:Error:line (25)java: src\Main.java:25: incompatible types
found   : java.lang.Object
required: java.lang.String

Означает ли это, что компилятор Java отличается от JLS?

Я подозреваю, что это означает, что Java 6 не соответствует текущему JLS. Может быть более старая версия, которой он соответствовал.

И можно ли еще как-то решить изначальную головоломку?

Есть подсказка. Это компилирует

char ch = '0';
ch *= 1.1;

Это не

char ch = '0';
ch = ch * 1.1;
person Peter Lawrey    schedule 08.01.2013
comment
В его примере есть (Объект) += (Строка). Ваш совет работает только для реверса: (String) += (Object) (когда Object приводится к String). - person Dmitry Zaytsev; 08.01.2013
comment
@DmitryZaitsev Это работает в Java 7. Какую версию Java вы пробовали? - person Peter Lawrey; 08.01.2013
comment
@DmitryZaitsev Что предполагает, что это могла быть ошибка JLS в Java 6. - person Peter Lawrey; 08.01.2013
comment
Напротив, JDK7, похоже, не придерживается JLS, потому что JLS требует, чтобы значение слева было String, если оно находится в массиве, и все равно компилируется и запускается без проблем. Хотя как программист я думаю, что такое поведение на самом деле лучше, чем в JLS, поскольку оно согласуется с x + i. Что касается оригинальной головоломки, я ищу случай, когда += недопустимо, а + разрешено. Я знаю об обратном случае, который происходит из-за скрытого приведения, меня это не интересует. - person Malcolm; 08.01.2013