benf.org :  other :  cfr :  Java 1.4 class constants.

(Thanks to Jarrko for pointing out this oddity)

As of 0_65, CFR correctly resugars Java 1.4 class constants - the approach for class constants seems to have changed considerably between 1.4 and 1.5 - here's some example code

public class Test {
    private final Class c;

    public Test(Class c) {
        this.c = c;
    }

    public static Test instance = new Test(String.class);
}

Nice and simple, yes? Let's compile this up in javac 1.8, and look at the generated bytecode for the static initialiser..

  static {};
    Code:
       0: new           #3                  // class cfrtest/Test
       3: dup           
       4: ldc_w         #4                  // class java/lang/String
       7: invokespecial #5                  // Method "<init>":(Ljava/lang/Class;)V
      10: putstatic     #6                  // Field instance:Lcfrtest/Test;
      13: return   

Yes, simple! But what do we get if we compile it up in javac 1.4?...

  static {};
    Code:
       0: new           #8                  // class cfrtest/Test
       3: dup           
       4: getstatic     #9                  // Field class$java$lang$String:Ljava/lang/Class;
       7: ifnonnull     22
      10: ldc           #10                 // String java.lang.String
      12: invokestatic  #11                 // Method class$:(Ljava/lang/String;)Ljava/lang/Class;
      15: dup           
      16: putstatic     #9                  // Field class$java$lang$String:Ljava/lang/Class;
      19: goto          25
      22: getstatic     #9                  // Field class$java$lang$String:Ljava/lang/Class;
      25: invokespecial #12                 // Method "<init>":(Ljava/lang/Class;)V
      28: putstatic     #13                 // Field instance:Lcfrtest/Test;
      31: return    

That's a bit more complex! At this point, it might be easier to look at CFR's decompilation (in 0_65+ you'll need to specify --j14classobj false to turn resugaring OFF.

Decompilation of 1.4 code, with --j14classobj false

public class Test {
    private final Class c;
    public static Test instance = new Test(Test.class$java$lang$String == null ? (Test.class$java$lang$String = Test.class$("java.lang.String")) : Test.class$java$lang$String);
    static /* synthetic */ Class class$java$lang$String;

    public Test(Class class_) {
        this.c = class_;
    }

    static /* synthetic */ Class class$(String string) {
        try {
            return Class.forName(string);
        }
        catch (ClassNotFoundException var1_1) {
            throw new NoClassDefFoundError().initCause((Throwable)var1_1);
        }
    }
}

That's interesting! Rather than a simple ldc_w of a ConstantClass, a synthetic method "class$" is used to explicitly call Class.forName - a static Class variable is used to cache the value of this class, and it's initialised with a ternary check everywhere it's used! It's also annoying that this won't quite compile, as, while NoClassDefFoundError is not checked, its 'initCause' method returns a checked Throwable.

It appears that this was done because earlier jvms didn't support ldc_w against class constants - the draft of the old 'java virtual machine book' says "the following java types can be pushed using ldc_w [ int float string ]", as compared to the current spec, which is much more flexible.

This isn't mentioned very much on the web - the only useful link apparent is on A. Sundararajan's oracle blog from 2006


For what it's worth .... CFR 0_65

public class Test {
    private final Class c;
    public static Test instance = new Test(String.class);

    public Test(Class class_) {
        this.c = class_;
    }
}

03/2014 - Thanks to Michael Schierl for pointing out mistakes and filling in the blanks in my original.


Last updated 03/2014