Chapter 14: Run-Time Type Identification

14.1: Class Objects

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:

  1. Creates a Class object from the string name of the class (Employee) - you must be very careful with the spelling:
   
   Class c1 = Class.forName("Employee");
  1. Create a Class object with class literal:
   
   Class c1 = Employee.class;
  1. Create a Class object from an object of that class by calling class Object's method getClass( ), which returns a Class object - this allows you to create a Class object from an unknown object:
   
   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.

14.2: Loading a Class File

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.

14.3: Two Levels of RTTI

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.

14.4: Low-Level RTTI

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:

* RTTI with if structure and keyword instanceof

   
   Object o1 = is. readObject();
   if(o1 instanceof Employee)
   {
      System.out.println("Welcome!");
   }
   if(o1 instanceof Dog)
   {
      System.out.println("Lovely dog!");
   }

* RTTI with Class object and string comparing

   
   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.

* RTTI with Class object and its method isInstance( )

   
   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.

Make your own free website on Tripod.com