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
final
fields 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
final
andeffectively final
for 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
final
variables:Since
int
is primitive, the variablea
is 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,
a
is 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 final
is 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
int
asa
is anint
already. That explains the output of97
.final
The version with the
final
variable is matched by this rule:The final variable
a
is of typeint
and 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,
final
variables are treated as constant expression/variable, andeffectively final
is not.In Java, string interning is based on constant expressions, hence
is
true
as 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.