Reputation: 11138
We know, that if if
statement's boolean expression/condition contains compile-time constant (or a variable, holding compile-time constant), then compiler can resolve this constant expression and:
public void ctc1() {
final int x = 1;
String text;
if (x > 0) text = "some text";
System.out.println(text); //compiles fine, as compile-time constant is resolved during compilation phase;
}
would compile and work fine, without "variable might not have been initialized" compiler error. No initialisation of text
in "else" branch (or after "if") is required, as compiler "understands" the constant is always going to result in true
, while evaluating x > 0
(which ends up being 1 > 0
).
Why, however, same resolution does not work (or works differently) when we want to return from the method, as:
public int ctc2() {
final int x = 1;
if (x > 0) return 1;
//requires another explicit "return" for any other condition, than x > 0
}
or, moreover, as:
public int ctc2() {
final int x = 1;
if (1 > 0) return 1;
}
?
Why compiler cannot infer/understand/resolve absolutely identical semantics and cannot be sure, that the return
is always executed and code is OK to be compiled?
In case of initialisation in the branch containing compile-time constant, compiler can resolve the constant value(s), and as it knows they will never change, it is sure, variable is going to be initialised. So, it allows usage of the variable after if
statement.
Why resolving constant expression works differently for the return
case, though? what is the point behind this difference/limitation?
To me, this looks like "two identical logical semantics" work differently.
I'm using:
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
Upvotes: 3
Views: 254
Reputation: 140504
This is a difference (mismatch? inconsistency?) between the rules around definite assignment and the particular normal completion rules of the if statement.
Specifically, the definite assignment rules say:
In all, there are four possibilities for a variable V after a statement or expression has been executed:
V is definitely assigned and is not definitely unassigned.
(The flow analysis rules prove that an assignment to V has occurred.)
...
The "flow analysis rules" are not clearly specified with regard to branch pruning, but it doesn't seem unreasonable to assume that the flow analysis is able to take into account constant values when deciding whether to follow a branch, meaning it is able to determine there is only one of the 4 states possible (definitely assigned after the if statement).
However, the reachability rules for an if statement say that:
An if-then statement can complete normally iff it is reachable.
Nothing about the expression value or flow analysis here. It's perhaps worth pointing out that this is itself different to the reachability rules for while
, do
and basic for
loops, which do explicitly mention the case of a constant true expression. Any of these returns would be accepted in ctc2()
:
while (true) return 1;
do { return 1; } while (true);
for (;;) return 1;
So, the language is specified in such a way that it overlooks the fact that your if statement cannot complete normally because of a) the constant expression, b) the return statement, despite that being "obvious" to a human reader.
An example of this difference actually being desirable (or, at least, the reachability rules being desirable) is if you have a DEBUG
boolean (as in, a constant-valued to trigger debug-only behaviour). You can imagine a method something like:
if (!DEBUG) {
return value;
}
return otherValue;
If the "conditional" return were treated in the same way as definite assignment, at least one of the return statements unreachable.
This would be a pain for debugging-time alternate behaviour like this.
Ofc one might argue that you could instead do something that isn't compile-time constant, e.g. invoke a method. I guess you can do that, but I would argue that not allowing use of the dirt-simplest method is.... unnecessarily restrictive, for the sake of avoiding a pretty rare "head-scratcher" in code.
Upvotes: 2