java.lang.Class represents all information of a class or type. Each time you create and compile a new type, its corresponding Class object is created and stored in the identically named .class file. At run time, when you want to create a new object of that class, Java Virtual Machine (JVM) will find the .class file and load it at the point when it is needed.
There are three ways to create a Class object, two from the name of the class and one from an object of that class:
Class c1 = Class.forName("Employee");
Class c1 = Employee.class;
Employee e1 = new Employee("Frank Liu", 33); Class c1 = e1.getClass();
Some main useful methods:
forName create a new Class object
newInstance create a new object of this type by calling its default constructor. This method together with forName can create a new object only with a string class name.
isInstance check whether an unknown object is of this type
get... return the fields, constructors, methods of the class (represented by Field, Constructor and Method objects)
getSuperclass return Class objects of the interfaces implemented by this class or the super class extended by this class,
isInterface check whether the Class object represents an interface or an array
When loading a class file using forName, remember to include its package name.
Normally, if in your code you mentioned a class say "Man" in your code, e.g. you created an instance of that class, or accessed its static member, the compiler will look for the class file of that class i.e. "Man.class" at the default CLASSPATHs. If the compiler can not find it, it will complain that it "can not resolve symbol Man". If it finds a source file, it will automatically compile that source file to generate the class file. At run time, JVM will load the class file at the point when the class is first time accessed.
If we do not know the name of the class at compile time, e.g., at run time the program accepts client's request and decide which class to invoke, then we have to explicitly ask the JVM to load in the class file at the point we need it, then we can create an instance of that class. This is accomplished by
Class class = Class.forName("Man"); Man m1 = (Man)(class.newInstance());
When JVM comes to one directory to search for a class, if it finds both the .java and .class file, it will compare the date of the two files. If the .java file is more up-to-date, it will compile the .java file instead of using the existing .class file. This guarantees that the latest version is used.
Java has two levels of RTTI. The low-level RTTI is the same as C++: The assumption is that Java knows the type information (i.e. the .class file) both at compile time and run time. You only use RTTI to find out which known class the unknown object belongs to. You actually mention the specific type name and call its specific method in your program.
The high-level RTTI is called reflection and is something that C++ doesn't have. No type information is available at compile time. You don't know the exact type of the object you are using, so you can not directly quote the type name and call its methods in a normal way. You have to use class Object's method
Class getClass()
to generate a Class object from the unknown object, then check whether this object has the method or constructor you want to call by using Class's method
Method [] getMethods() Method getMethod(String, Class[]) Constructor [] getConstructors() Constructor getConstructor(String, Class[])
to generate one or an array of Method or Constructor objects, which wraps around the real method or constructor of that unknown object. You can use the methods of class Method and Constructor to get the name, return type, parameter type, etc. of the method or constructor, and then use its method invoke( ) to actually call that wrapped method or constructor.
Reflection technique is created mainly for internal use by Java, you will very rarely use it yourself. Here is an example of the use of reflection:
import java.io.*; import java.lang.reflect.*; class Employee implements Serializable{ private String name; private char gender; private int age; public Employee(String n, char g, int a) { name = n; gender = g; age = a; } public String toString1() { return "\n Employee name: " + name + "\n Gender: " + gender + "\n Age: " + age + "\n"; } } public class Test { public static void main(String [] args) { try{ ObjectOutputStream os = new ObjectOutputStream( new BufferedOutputStream( new FileOutputStream("Employee"))); Employee e1 = new Employee("Frank", 'M', 33); os. writeObject(e1); os. flush(); os. close(); // From now on no type information is used: ObjectInputStream is = new ObjectInputStream( new FileInputStream("Employee")); Object o1 = is. readObject(); Class c1 = o1.getClass(); Class [] empty1 = new Class[0]; Object [] empty2 = new Object[0]; Method m1 = c1.getMethod("toString1", empty1); System.out.println(m1.invoke(o1, empty2)); is. close(); } catch(ClassNotFoundException e) { System.out.println("ClassNotFoundException!"); } catch(ClassCastException e) { System.out.println("ClassCastException!"); } catch(NotSerializableException e) { System.out.println("NotSerializableException!"); } catch(IOException e) { System.out.println("IOException!"); } catch(NoSuchMethodException e) { System.out.println("NoSuchMethodException!"); } catch(InvocationTargetException e) { System.out.println("InvocationTargetException!"); } catch(IllegalAccessException e) { System.out.println("IllegalAccessException!"); } } }
Output will be:
Employee name: Frank Gender: M Age: 33
From this example you can see that with reflection you can use an unknown object and call its methods. The two methods used in this example are:
Class:
Method getMethod(String name, Class[] parameterTypes)
the string is the name of the method, and the Class array is the types of the parameters of the method.
Method:
Object invoke(Object obj, Object[] args)
The Object is the object whose method you want to call, and the Object array is the arguments you want to pass to the underlying method.
With low-level RTTI, you get an unknown object, get its type information, and compare it against your known types. There are three ways to do that:
Object o1 = is. readObject(); if(o1 instanceof Employee) { System.out.println("Welcome!"); } if(o1 instanceof Dog) { System.out.println("Lovely dog!"); }
Object o1 = is. readObject(); Vector v1 = new Vector(); v1.addElement("class Employee"); v1.addElement("class Dog"); Vector v2 = new Vector(); v2.addElement("Welcome!"); v2.addElement("Lovely dog!"); for(int i = 0; i < v1.length; i++) { if(o1.getClass().toString().equals(v1[i])) System.out.println(v2[i]); }
This is very similar to C++'s style.
Object o1 = is. readObject(); Vector v1 = new Vector(); v1.addElement(Employee. class); v1.addElement(Dog. class); Vector v2 = new Vector(); v2.addElement("Welcome!"); v2.addElement("Lovely dog!"); for(int i = 0; i < v1.length; i++) { if(v1[i].isInstance(o1)) System.out.println(v2[i]); }
From the three RTTI methods you can see, using Class object especially its method isInstance( ) is more robust than keyword instanceof, because it does not require the exact type name to be typed as required by keyword instanceof. Therefore we can get rid of the if structure which is very poor software engineering - because whenever you add or remove a type you have to modify the code. With the use of Class object we can write all type information and the corresponding operations into vectors, and save them into a file. Then what our code need to do is only to read the two vectors from the file. Any modification of the types will not bring any affect on our code.