Tuesday, April 19, 2005

The mysterious .class variable

Those who code in java will know that each class has a field called class which gets the class Object.
Say we have a class called Test,

Class cls = Test.class; gives the class object for Test.
This is equivalent of

Class cls = Class.forName("Test");

Lets write a class, which does reflection to find out what are all the fields and methods for that class.

========================================
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public Class cls = Test.class;
public static void main(String[] args) {
Field[] fields = Test.class.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
System.out.println(fields[i]);
}
Method[] methods = Test.class.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i]);
}
}
}
========================================
interestingly, if you run this program, it prints
----------------------------------------
public java.lang.Class Test.cls
static java.lang.Class Test.class$Test
public static void Test.main(java.lang.String[])
static java.lang.Class Test.class$(java.lang.String)
----------------------------------------
*Note, this class is compiled using Sun's JDK 1.4.2 compiler. If you are using eclipse, you might get a different result.

So as you see, there is no "class" field as such, but we got a field called class$Test, and a method called Class class$(String).
Lets disassemble the class to find what is happening inside!'

For simplicity, im disassembling this class

========================================
public class Test {
public Class cls = Test.class;
}
========================================
.source Test.java
.class public super Test
.super java/lang/Object

.field public cls Ljava/lang/Class;
.field static class$Test Ljava/lang/Class;

.method public ()V
.limit stack 3
.limit locals 1
.line 1
l0: aload_0
l1: invokespecial java/lang/Object/ ()V
.line 2
l4: aload_0
l5: getstatic Test/class$Test Ljava/lang/Class;
l8: ifnonnull l23
l11: ldc "Test"
l13: invokestatic Test/class$ (Ljava/lang/String;)Ljava/lang/Class;
l16: dup
l17: putstatic Test/class$Test Ljava/lang/Class;
l20: goto l26
l23: getstatic Test/class$Test Ljava/lang/Class;
l26: putfield Test/cls Ljava/lang/Class;
l29: return

.end method

.method static class$ (Ljava/lang/String;)Ljava/lang/Class;
.limit stack 3
.limit locals 2
.catch java/lang/ClassNotFoundException from l0 to l4 using l5
.line 2
l0: aload_0
l1: invokestatic java/lang/Class/forName (Ljava/lang/String;)Ljava/lang/Class;
l4: areturn
l5: astore_1
l6: new java/lang/NoClassDefFoundError
l9: dup
l10: aload_1
l11: invokevirtual java/lang/ClassNotFoundException/getMessage ()Ljava/lang/String;
l14: invokespecial java/lang/NoClassDefFoundError/ (Ljava/lang/String;)V
l17: athrow

.end method
========================================

Look at the constructor. What it does is
if (class$Test == null) {
class$Test = class$("Test")
}
cls = class$Test;

So it is pretty much evident that, the class object is once stored in class$Test.

And what happens in class$ method?
Well, this is what it does

return Class.forName(String) !! (Not counting the ClassNotFoundException)

So from this we can understand that the .class Field is just a syntactic sugar which the compiler provides, and not a real Field.

No comments: