So far I thought that effectively final and final are more or less equivalent and that the JLS would treat them similar if not identical in the actual behavior. Then I found this contrived scenario:
final int a = 97;
System.out.println(true ? a : 'c'); // outputs a
// versus
int a = 97;
System.out.println(true ? a : 'c'); // outputs 97
Apparently, the JLS makes an important difference between the two here and I am not sure why.
I read other threads like
- Difference between final and effectively final
- Effectively final variable vs final variable
- What does a variable being “effectively final” mean?
but they do not go into such detail. After all, on a broader level they appear to be pretty much equivalent. But digging deeper, they apparently differ.
What is causing this behavior, can anyone provide some JLS definitions that explain this?
Edit: I found another related scenario:
final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true
// versus
String a = "a";
System.out.println(a + "b" == "ab"); // outputs false
So the string interning also behaves differently here (I dont want to use this snippet in real code, just curious about the different behavior).
First of all, we are talking about local variables only. Effectively final does not apply to fields. This is important, since the semantics for
finalfields are very distinct and are subject to heavy compiler optimizations and memory model promises, see $17.5.1 on the semantics of final fields.On a surface level
finalandeffectively finalfor local variables are indeed identical. However, the JLS makes a clear distinction between the two which actually has a wide range of effects in special situations like this.Premise
From JLS§4.12.4 about
finalvariables:Since
intis primitive, the variableais such a constant variable.Further, from the same chapter about
effectively final:So from the way this is worded, it is clear that in the other example,
ais not considered a constant variable, as it is not final, but only effectively final.Behavior
Now that we have the distinction, lets lookup what is going on and why the output is different.
You are using the conditional operator
? :here, so we have to check its definition. From JLS§15.25:In this case, we are talking about a numeric conditional expressions, from JLS§15.25.2:
And that is the part where the two cases get classified differently.
effectively final
The version that is
effectively finalis matched by this rule:Which is the same behavior as if you would do
5 + 'd', i.e.int + char, which results inint. See JLS§5.6So everything is promoted to
intasais anintalready. That explains the output of97.final
The version with the
finalvariable is matched by this rule:The final variable
ais of typeintand a constant expression (because it isfinal). It is representable aschar, hence the outcome is of typechar. That concludes the outputa.String example
The example with the string equality is based on the same core difference,
finalvariables are treated as constant expression/variable, andeffectively finalis not.In Java, string interning is based on constant expressions, hence
is
trueas well (dont use this construct in real code).See JLS§3.10.5:
Easy to overlook as it is primarily talking about literals, but it actually applies to constant expressions as well.