How do I make E1 += E2 illegal while E1 = E1 + E2 is legal?
Asked Answered
H

3

13

I've been reading Java Puzzlers by Bloch and Gafter and got to the puzzle 10 (Tweedledee). The essence of this puzzle is to

provide declarations for the variables x and i such that this is a legal statement:

x = x + i;

but this is not:

x += i;

The solution to this looks, according to the book, like this:

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

The book claims that in the += operator the right-hand expression can be of any type only if the left-hand expression has type String. However, I tried to run this code, and it compiled and ran without any problems.

Then I dug into the Java Language Specification. Section 15.26.2 talks about two cases: when the left-hand expression is an array access expression and when it's not. If the left-hand operand expression is not an array access expression, then JLS doesn't say anything about the left-hand expression being a String. When it is, this part appllies:

If T is a reference type, then it must be String. Because class String is a final class, S must also be String. Therefore the run-time check that is sometimes required for the simple assignment operator is never required for a compound assignment operator.

❖ The saved value of the array component and the value of the right-hand operand are used to perform the binary operation (string concatenation) indicated by the compound assignment operator (which is necessarily +=). If this operation completes abruptly, then the assignment expression completes abruptly for the same reason and no assignment occurs.

T here is the type of the left-hand operand as determined at compile-time, and S is the selected array component. So I thought that I would modify my code into this:

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

But even this code compiles and runs without any problems even though new Object() is not even remotely a String.

Why is this happening? Does this mean that Java compiler deviates from the JLS? And is it still possible to somehow solve the original puzzle?

Harwill answered 8/1, 2013 at 15:29 Comment(7)
Have you tried creating an object that overrides toString to print to stdout/stderr?Aperiodic
Are you sure about which is supposed to compile? Or maybe the book has a typo. If x and i are, for example, byte then x += i; is valid, but x = x + i; is not.Spiro
Are you sure that you actually tested x += i and not i += x? I mean (String) += (Object) will compile easily, when (Object) += (String) will not compile.Acrobatic
@Tinctorius You don't have to create an object to print to stdout, the result is a String which looks something like this: java.lang.Object@5a4b4b50Effective Java!. So concatenation happens nicely.Harwill
@Malcolm: I just intended that for (lazy) debugging, to see what's Java is actually doing.Aperiodic
@PatriciaShanahan Absolutely sure, because that's the subject of the previous puzzle, puzzle 9: Tweedledum.Harwill
@DmitryZaitsev I tested both. With JDK7 both work.Harwill
P
4

Try with javac < 1.4.2, it will work there too.

This was a change between different release. The change for 1.4.2 (x += i; allowed before, not since):
https://bugs.java.com/bugdatabase/view_bug?bug_id=4642850

Which is correct, because the JLS 2. edition defined:

All compound assignment operators require both operands to be of primitive type, except for +=, which allows the right-hand operand to be of any type if the lefthand operand is of type String.

The change for 7 (x += i; not allowed before, allowed since):
https://bugs.java.com/bugdatabase/view_bug?bug_id=4741726

Which is correct since JLS 3. edition (see http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.26 the previous prerequisite is removed)


Just a small edit: I do not see any way to fix/solve the puzzle in Java 7.0_10

Peroxidize answered 8/1, 2013 at 17:6 Comment(6)
OK, I see about the changes in JLS, but what about the code that does array access? It shouldn't work accroding to the latest JLS, should it?Harwill
@Malcom I do not see any contradiction. One could have an argument if "If T is a reference type, then it must be String" means that it T must be String or that T is handled as as String. I would say, it is the second option, see JLS 5.1.11. String Conversion. and 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.)Peroxidize
Yes, but if the string conversion is automatically applied, why is the sentence about the String written at all? Besides, the conversion happens when the binary operation is carried out, and this happens after the supposed check if the operand is String. Personally, I think that this is a JLS bug: they removed the line about the String requirement at the start of the paragraph and forgot about this part.Harwill
You could try yo post it as a bug and see what happens.Peroxidize
Maybe I'll do that. OK, I think the question is now answered, except there is no answer to the puzzle, but now it is apparently unsolvable.Harwill
Here's a copy of The Java Language Specification, Second Edition.Surgical
A
2

I have the following and it show the given solution as the correct answer:

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

when I compile this I get

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

  x += i;
  ^
1 error
Amyamyas answered 8/1, 2013 at 15:40 Comment(3)
Which version of Java are you using? This compiles and runs on Java 7.Scorpaenoid
Then Java 7 ignores the JLS.Aperiodic
But does the JLS say that x += i shouldn't work? As I take it, JLS needs String only in arrays, in all other cases it doesn't matter. JLS just says that first, the left-hand operand is evaluated to produce a variable and then 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, which is nothing different from x + i. Nothing is said about left-hand expression being a String.Harwill
S
0

In Java 6 you can say

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

Why is this happening?

The same as

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

Compiled with Java 7

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

prints

java.lang.Object@a62b39fEffective Java!

but not with Java 6 update 37

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

Does this mean that Java compiler deviates from the JLS?

I suspect it means Java 6 doesn't follow the current JLS. There may be an older version it did comply with.

And is it still possible to somehow solve the original puzzle?

There is a hint. This compiles

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

This does not

char ch = '0';
ch = ch * 1.1;
Scorpaenoid answered 8/1, 2013 at 15:39 Comment(4)
In his example there is (Object) += (String). Your advice works only for reverse: (String) += (Object) (when Object casted to String).Acrobatic
@DmitryZaitsev It works in Java 7. Which version of Java have you tried?Scorpaenoid
@DmitryZaitsev Which suggests it could have been a JLS bug in Java 6.Scorpaenoid
On the contrary, JDK7 doesn't seem to adhere to JLS, because JLS requires the value on the left to be String if it is in the array, and it still compiles and runs without problem. Though as a programmer I think that this behavior is actually better that what is in JLS since it is consistent with x + i. About the original puzzle, I'm looking for the the case when += is illegal, and + is legal. I know about the reverse case which happens because of a hidden cast, I'm not interested in that.Harwill

© 2022 - 2024 — McMap. All rights reserved.