| 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.
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
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 |