Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javac missing optimization for effective final

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 ?

like image 818
jmj Avatar asked Apr 17 '18 02:04

jmj


1 Answers

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 new String 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 type String 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 a try-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 declared final, 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

  • Rethrowing caught exceptions with more freedom (improved type checking) (since Java 7)
  • They may be referenced (captured) by lambda expressions and inner classes (since Java 8)
  • Refer to them with the try-with-resource (try(existingVariable) { … } (since Java 9)

but otherwise, they are not treated like final variables.

like image 191
Holger Avatar answered Oct 16 '22 01:10

Holger