Fact:
javac
is programmed to detect if a variable is final
or if it can be treated as effectively final
.
Proof:
This code illustrates this.
public static void finalCheck() {
String str1 = "hello";
Runnable r = () -> {
str1 = "hello";
};
}
This fails to compile because compiler is able to detect String
reference str1
is being re-assigned in function.
Now
Situation 1:
Javac does great optimization for final
String
instances by avoiding to create StringBuilder
and related operations.
Proof
This java method
public static void finalCheck() {
final String str1 = "hello";
final String str2 = "world";
String str3 = str1 + " " + str2;
System.out.println(str3);
}
Compiles to
public static void finalCheck();
Code:
0: ldc #3 // String hello world
2: astore_2
3: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_2
7: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
Question:
But now when we have them as effectively final
public static void finalCheck() {
String str1 = "hello";
String str2 = "world";
String str3 = str1 + " " + str2;
System.out.println(str3);
}
It doesn't optimize the similar way and ends up compiling into
public static void finalCheck();
Code:
0: ldc #3 // String hello
2: astore_0
3: ldc #4 // String world
5: astore_1
6: aload_0
7: aload_1
8: invokedynamic #5, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
13: astore_2
14: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
17: aload_2
18: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
21: return
JVM
$java -version
java version "10" 2018-03-20
Java(TM) SE Runtime Environment 18.3 (build 10+46)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+46, mixed mode)
Compiler
$javac -version
javac 10
question: why doesn't it optimize for effective final ?
The introduction of the effectively final concept did not affect the rules regarding constant expressions and string concatenation.
Refer to the Java® Language Specification, §15.18.1. String Concatenation Operator +
The
String
object is newly created (§12.5) unless the expression is a constant expression (§15.28).
The referenced section, §12.5. Creation of New Class Instances, removes any doubt:
Execution of a string concatenation operator
+
(§15.18.1) that is not part of a constant expression (§15.28) always creates a newString
object to represent the result.
So while certain constructs may have a predictable string result, even when not being a constant expression, replacing them with a constant result would violate the specification. Only constant expressions may (event must) get replaced by their constant value at compile time. Regarding referenced variables, §15.28 states that they must be constant variables according to §4.12.4 to be constant expressions:
A constant variable is a
final
variable of primitive type or typeString
that is initialized with a constant expression (§15.28).
Note the requirement of being final
for a constant variable.
There is also the concept of implicitly final variables, which is different to effectively final:
Three kinds of variable are implicitly declared
final
: a field of an interface (§9.3), a local variable declared as a resource of atry
-with-resources statement (§14.20.3), and an exception parameter of a multi-catch
clause (§14.20). An exception parameter of a uni-catch
clause is never implicitly declaredfinal
, but may be effectively final.
So, not much surprising, interface fields are implicitly final
(they are also implicitly static
), as they always were, and the other two cases of implicitly final
variables can never be strings nor of a primitive type, hence are never constants.
Effectively final variables are treated specially (like final
variables) only in certain use cases
try
-with-resource (try(existingVariable) { … }
(since Java 9)but otherwise, they are not treated like final
variables.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With