Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does optimization of final references work in java?

I'd been trying to figure out all about Java optimizations and found something interesting.

First case: primitive type compile-time optimization

public class Clazz {
    public static void main(String args[]) {
        final int i = 300;
        new Clazz() {
            void foo() {
                System.out.println(i);
            }
        }.foo();
    }
}

After compilation (I'm using jd-gui-0.3.5.windows to decompile binary files) it is looks like:

public class Clazz {
    public static void main(String[] args) {
        int i = 300;
        new Clazz() {
            void foo() {
                System.out.println(300);
            }
        }.foo();
    }
}

As expected, isn't it? i was replaced with it's value (inlining optimization) after compilation. So, I expected to see something similar after replacing primitive type with it's wrapper, but...

Second case: non-primitive type compile-time optimization

public class Clazz {
    public static void main(String args[]) {
        final Integer i = 300; // replaced int with Integer
        new Clazz() {
            void foo() {
                System.out.println(i);
            }
        }.foo();
    }
}

After compilation:

public class Clazz {
    public static void main(String[] args) {
        Integer i = Integer.valueOf(300);
        new Clazz() {
            void foo() {
                System.out.println(Clazz.this);
            }
        }.foo();
    }
}

Questions:

What is Clazz.this in this context? I know, that it is reference to enclosing instance of Clazz, but it should not work in that case! I need to print i, but compiler suggests me to print Clazz.this instead of it and it works! What is the problem? Does jd-gui decompiles incorrectly or do I missing something about Java compilation and optimization?

UPD:

Content of Class$1:

class Clazz$1 extends Clazz {
    Clazz$1(Integer paramInteger) {}

    void foo() {
        System.out.println(this.val$i);
    }
}
like image 249
bsiamionau Avatar asked Mar 25 '13 11:03

bsiamionau


People also ask

Does final in Java improve performance?

The final keyword has a different purpose when applied to classes and methods. When we apply the final keyword to a class, then that class cannot be subclassed. When we apply it to a method, then that method cannot be overridden. There are no reported performance benefits of applying final to classes and methods.

What is final reference in Java?

final means that you can't change the object's reference to point to another reference or another object, but you can still mutate its state (using setter methods e.g). Whereas immutable means that the object's actual value can't be changed, but you can change its reference to another one.

What happens if a method has the final keyword as modifier?

The final modifier for finalizing the implementations of classes, methods, and variables. We can declare a method as final, once you declare a method final it cannot be overridden. So, you cannot modify a final method from a sub class.

What happens if I write final keyword just prefix of any method?

If you prefix a class definition with final , you prevent anyone from subclassing that class. Value types don't support inheritance so it doesn't make sense to mark a struct or enum as final . The final keyword communicates to the compiler that the class cannot and should not be subclassed.


2 Answers

Does jd-gui decompiles incorrectly or do I missing something about Java compilation and optimization?

jd-gui is decompiling the code incorrectly.

On my JVM, the disassembled code for the anonymous class looks as follows:

class Clazz$1 extends Clazz {
  Clazz$1(java.lang.Integer);
    Code:
       0: aload_0       
       1: aload_1       
       2: putfield      #10                 // Field val$i:Ljava/lang/Integer;
       5: aload_0       
       6: invokespecial #12                 // Method Clazz."<init>":()V
       9: return        

  void foo();
    Code:
       0: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0       
       4: getfield      #10                 // Field val$i:Ljava/lang/Integer;
       7: invokevirtual #26                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      10: return        
}

As you can see, a copy of i stored in the anonymous class, in the field called val$i (the name is implementation-specific).

It is this field that your decompiler appears to incorrectly render as Clazz.this.

like image 183
NPE Avatar answered Sep 28 '22 18:09

NPE


You could simply have a look at the bytecode (javap -c Clazz$1.class).

With int i = 300:

void foo();
   0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
   3: sipush        300
   6: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
   9: return

With Integer i = 300:

void foo();
   0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
   3: aload_0
   4: getfield      #1                  // Field val$i:Ljava/lang/Integer;
   7: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  10: return

So the int is inlined but the Integer is not.


Also for the record, this is what I get from jd-gui (3.0.5):

public static void main(String[] args) {
  Integer i = Integer.valueOf(300);
  new Clazz() {
    void foo() {
      System.out.println(this.val$i);
    }
  }
  .foo();
}
like image 41
assylias Avatar answered Sep 28 '22 18:09

assylias