Summary: Your initial assumption was (kind of) wrong. Which led to some interesting findings. Read on.
Consider the following code:
class DoNothing { Integer value; }
Integer getInt() {
return 5 / 0;
}
Object o;
((DoNothing)o).value = getInt();
We have a pending NullPointerException and a pending MathException. Which one happens first? In actuality, the MathException happens first. More specifically, your assumption about the order of operations was incorrect.
Let's get to a basic example of where that could lead to a surprise:
class A() {
String value;
A() {
this('Hello').value = value + value;
}
A(String v) {
value = v;
}
}
What should value be? "HelloHello"? Nope, it would be "nullnull" (assuming this code were allowed to compile). That's because the right-hand was evaluated first, cached, then the left-side was evaluated, and then the concatenation/assignment operator was applied using the null value. We were depending on value being evaluated later, but it was not.
(Edit: I noticed that += actually does work correctly. Adding demos.)
Here's an example of the caching effect in play:
public class exampleValueSet {
public String value;
public exampleValueSet setValue(String v) {
value = v;
return this;
}
}
exampleValueSet v = new exampleValueSet();
v.setValue('world').value = v.value + v.value;
system.debug(v.value);
While using += results in the intended result:
public class exampleValueSet {
public String value;
public exampleValueSet setValue(String v) {
value = v;
return this;
}
}
exampleValueSet v = new exampleValueSet();
v.setValue('world').value += v.value;
system.debug(v.value);
Note that the Order of Operations manual specifically says that the assignment operators evaluate last. This can cause the timing for when a value is evaluated to change. For example += v.value + v.value
yields a different result than += v.value
. Unless you're 100% certain of how side effects will occur, it's best to avoid trying to introduce them to your code. It gets pretty complicated quickly.
Worse, if they allowed code like this, and later decided to change the order of operations, then code that depended on the current order might break. To avoid any ambiguity, they just blocked it altogether.
It's a very powerful block, too. It supersedes all other errors I tested it against:
Unary increment/decrement can only be applied to numeric expressions
this(5)++;
Arithmetic expressions must use numeric arguments
this() / 5;
Comparison arguments must be compatible types
this() == 5;
Expression cannot be a statement
this() == this;
Object has no superclass for super invocation
super() == this;
All of those errors, in any other context, are replaced by the same Call to another constructor must be the first statement in constructor block error when applied to this() or super(). It even beats out super()'s "no superclass" error; this error is literally thrown even before types are checked or anything else.
Also, interestingly, they've specifically blocked using parameters that lead to instance methods or variables. So, you can't do this either:
public class A {
public Integer b;
public A() {
this(b=5);
}
public A(Integer v) {
b = v * 2;
}
}
Which would normally be valid code if called on a normal method (5 would be assigned to b, and then 5 would be multiplied by 2, leaving b as 10).
In that case, we get a different error:
cannot reference instance variables or instance methods inside a constructor invocation
Unless, of course, you cause the "Call to another constructor..." error, as before.
this.someProperty = property;
in the second constructor?