Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

JDK 1.1, Using Reflection with Object Serialization and Retrieved Objects

Java Programming, Lecture Notes # 270, Revised 12/17/98.


Introduction

Students in Prof. Baldwin's Advanced Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson.

This lesson contains a sample program that illustrates much of what has been discussed in earlier lessons on object serialization and reflection.

One of the features in JDK 1.1 is the ability to create persistent objects using object serialization.

Another new feature is the capability to create inner classes.

A third new feature is reflection.

Sample Program

The following sample program illustrates many, but not nearly all of the features discussed in previous lessons on object serialization and reflection.

Discussion, Overview

This application is designed to illustrate the reflection capability of JDK 1.1 when used in conjunction with Object Serialization and Inner Classes.

The application creates and displays a fairly complex object and uses Object Serialization to save it to a disk file.

The object contains several instance variables and one instance method.

One of the instance variables is an embedded object which is an instance of an Inner Class of the outer object class. (Hence the complexity.)

Both the outer and inner classes implement the Serializable interface which is required in order to use Object Serialization.

The embedded object also contains an instance variable and an instance method.

All instance variables and methods of both classes are public so that they can be accessed through reflection for illustration purposes.

When invoked, the instance methods in both classes display the instance variables of an object of that class.

The instance methods require a parameter which has no functional purpose other than to illustrate certain aspects of reflection.

After first displaying the object, the application closes the file and sets the reference variable to the object to null, making the object eligible for garbage collection.

The application then opens the file for reading.

The object is retrieved from the file using object serialization and referred to by a generic reference variable of type Object.

After the object is retrieved, reflection is used to investigate and display several important attributes of the object.

The reflection process culminates with the invocation of the instance method of the object using reflection methods.

Invocation of the instance method causes the instance variables of the object and its embedded object to be displayed.

This program was successfully tested using JDK 1.1.6 under Win95.

It was also tested using the first released version of JDK 1.2 under Win95 on 12/17/98. Unfortunately, that test was not successful. When compiled and run under JDK 1.2, the program threw an "IllegalArgumentException: field type mismatch" at the point in the program immediately following the display of the string "Assume decision here to investigate field 1". As of this writing on 12/17/98, I haven't had the time to investigate this apparent compatibility problem between JDK 1.1.6 and JDK 1.2.

The complete output from running this program on one particular occasion is shown in comments at the beginning of the program.

Interesting Code Fragments

This section highlights code fragments that are important in gaining an understanding of how reflection, Object Serialization, and inner classes are implemented in the sample program.

Note that the Core Reflection API supports a number of capabilities that are not illustrated by this program. Some of them will be discussed and illustrated in subsequent lessons.

The following code fragment instantiates an object named outStream that supports Object Serialization making it possible to write Serializable objects directly to an output file without a requirement to decompose them and write out their component parts. Serializable objects are instances of classes that implement the Serializable interface.

      FileOutputStream fout = new FileOutputStream("tmp");
      ObjectOutputStream  outStream  =  new  ObjectOutputStream(fout);

The next fragment instantiates and displays an object of type MyClass. This code passes a parameter to the showData() method which serves no useful purpose other than to later illustrate certain aspects of reflection (finding the number and the type of arguments in the argument list).

      MyClass temp = new MyClass();//instantiate object
      temp.showData("String parameter to display method.");//display

The next fragment uses Object Serialization to write the object to the output file.

      outStream.writeObject(temp);//save object

The next fragment instantiates an input stream object that can be used to read a Serializable object from the disk file.

      FileInputStream fin = new FileInputStream("tmp");
      ObjectInputStream inStream = new ObjectInputStream(fin);

The next fragment reads the object from the file and assigns it to a reference variable of the generic type Object. Recall that Object is the superclass of all objects in Java. Hence, a reference variable of type Object can be used to refer to any object.

A reference to an object as type Object imparts little information about the nature, makeup, or composition of the object. We will use reflection to obtain information about the object.

        Object obj = inStream.readObject();//retrieve the object

The next fragment displays the object using the overridden toString() method. All objects can be displayed using toString(). In this case, the value displayed was:

MyClass@1cd017

System.out.println(
            "The toString() representation of the object is: " 
            + obj.toString());

Begin Using Reflection

Now we have reached the point where we actually begin using the reflection capability of JDK 1.1.

The next fragment uses the getClass() method in class java.lang.Object to return the runtime class of the object named obj. This gives us access to information about the object at runtime.

Then the getName() method of class java.lang.Class is used to return the name of the class represented by the Class object as a String object.

In this case, the name MyClass is returned.

System.out.println("The class name is: " 
                                  + obj.getClass().getName());

Next, we use the frollowing fragment to return the name of the superclass of the object represented by the Class object. The returned value is class java.lang.Object.

System.out.println("The Superclass is: " 
                            + obj.getClass().getSuperclass());

Next, we turn our attention to the fields of the object.

The next fragment invokes the getFields() method on the class object which returns an array of type Field. Recall that Field is also one of the classes in the Core Reflection API.

Once we have an array of Field objects. we can access the length attribute of the array to determine the number of fields. In the sample program, the number of fields is reported to be 3.

We can also use the methods of the Field class to learn more about the individual fields as we will see later.

        Field[] myFields = obj.getClass().getFields();
        System.out.println("The number of fields is: " 
                                            + myFields.length);

Now that we have an array of Field objects where each object in the array contains information about one of the fields in the class, we will use methods of the Field class to investigate those fields.

The next code fragment uses the getName() method inside a loop to obtain and display the names of each of the fields in the object. This method returns the name of the field as a String object.

The output produced is (boldface added by this author for emphasis):

The name of field 0 is: myOuterString
The name of field 1 is: myIntVariable
The name of field 2 is: myEmbeddedObject

Later when we view a listing of the complete program, you will recognize these as the field names defined in the definition of the class.

        for(int cnt = 0; cnt < myFields.length; cnt++)
          System.out.println("The name of field " + cnt 
                          + " is: " + myFields[cnt].getName());

We can also use the methods of the Field class to determine the types of the individual fields, as in the next code fragment.

In this case, the output is:

The type of field 0 is: class java.lang.String
The type of field 1 is: int
The type of field 2 is: class MyClass$MyInnerClass

Pay particular attention to the type of field 2. This is the type indication for the class named MyInnerClass embedded as an inner class of the class named MyClass.

      for(int cnt = 0; cnt < myFields.length; cnt++)
          System.out.println("The type of field " + cnt 
                          + " is: " + myFields[cnt].getType());

Now that we have determined the type of each field, we can use methods of the class Field such as getInt() to obtain the values of each field. This is illustrated in the next code fragment where we use the getInt() method to obtain the value of field 1 only. The values of the other fields could also have been obtained using similar code.

Note the syntax here which is possibly less obvious that what we have seen up to this point.

The syntax requires you to invoke the method on a Field object that has been extracted from the object under investigation.

In addition, the syntax requires you to pass the object under investigation to the method as a parameter. In this case, the program reports that the field has a value of 4096 which will be as expected from a later examination of the class definition of the MyClass class.

         System.out.println("The value of field 1 is " 
              + myFields[1].getInt(obj));

Next we will turn our attention to an investigation of the instance methods of the object and culminate that investigation by invoking one of the methods.

Here we will declare a reference variable to an array of the Method class and then invoke the method named getMethods() on the class object to store information about each method of the class in the array.

The next fragment creates an array of Method objects and then iterates through the array using the getName() method of the Method class to obtain and display the names of the methods.

     Method[] myMethods = obj.getClass().getMethods();
      for(int cnt = 0; cnt < myMethods.length; cnt++)
        System.out.println("The name of method " 
           + cnt + " is: " + myMethods[cnt].getName());

At this point, we may be initially surprised by the output which is:

The name of method 0 is: getClass
The name of method 1 is: hashCode
The name of method 2 is: equals
The name of method 3 is: toString
The name of method 4 is: notify
The name of method 5 is: notifyAll
The name of method 6 is: wait
The name of method 7 is: wait
The name of method 8 is: wait
The name of method 9 is: showData

Recall that the instance methods of the object include all of the methods defined in the class of which that object is an instance, plus the methods of all the superclasses of that class. Hence we see a list of all the methods defined in the family tree of our class.

Next we will turn our attention to a more detailed investigation of the methods aimed at ultimately invoking one of the methods. For brevity, we will concentrate on method 9 which is the only method defined in the class of the object.

First, let's determine the types of parameters in the formal argument list for the method.

The Method class has a method named getParameterTypes() that returns an array of Class objects representing the formal parameter types, in declaration order, of the method represented by this Method object. The next fragment uses that method to obtain parameter types for method 9.

        Class[] myParams = 
                     myMethods[9].getParameterTypes();

Once we have an array of Class objects where each object contains information about one of the parameters, we can determine the number of parameters by extracting the length property of the array. In this case, the length of the aray was one element, telling us that there was only one parameter in the argument list. (Note that it was for the illustration of this capability that we included a parameter which had no functional purpose as mentioned earlier.)

Displaying a Class object gives the name of the class (the type). Hence, we can determine the type of each of the parameters using code such as the following. In this case, there was only one parameter, and its type was reported to be class java.lang.String.

for(int cnt = 0; cnt < myParams.length; cnt++) 
  System.out.println("The type for parameter " + cnt 
               + " of method 9 is: " + myParams[cnt]);

Our next objective will be to invoke the method. We will accomplish this by

We will use code such as the following.

myMethods[9].invoke(obj,args);

First, however, we will need to understand how to construct the argument list for the method named invoke().

A partial description of the invoke() method, as extracted from the JDK 1.1 documentation is as follows:

public Object invoke(Object obj, Object args[]) throws ...

Invokes the underlying method represented by the Method object, on the specified object with the specified parameters.

Individual parameters are automatically unwrapped to match primitive formal parameters, and both primitive and reference parameters are subject to widening conversions as necessary.

The value returned by the underlying method is automatically wrapped in an object if it has a primitive type.

If the underlying method is static, then the specified object argument is ignored. It may be null. (In our case, the underlying method is not static. Rather, it is an instance method so a valid object must be specified.)

Several possibilities exist for throwing exceptions. You should review the JDK 1.1 documentation to learn of these possibilities.

As you can see from the above description, the actual parameters which are passed to the underlying method when it is invoked are provided as an array of type Object.

Since Object is the superclass of all classes in Java, the elements in this array may be of any class including wrapper classes for the primitive types.

If the underlying method is an instance method, it is invoked using dynamic method lookup as documented in The Java Language Specification, section 15.11.4.4; in particular, overriding based on the runtime type of the target object will occur.

If the underlying method is static, it is invoked as exactly the method on the declaring class.

If the method completes normally, the value it returns is returned to the caller of invoke, and if it is of a primitive type, it is wrapped in an object. If the underlying method return type is void, the invocation returns null.

The next fragment first creates a one-element array of type Object named args and stores a string parameter in element 0.

Then the invoke() method is invoked on element [9] of the array of Method arguments, passing the object under investigation, obj, and the array of arguments, args, as parameters.

        Object[] args = new Object[1];
        args[0] = "Invoking method 9";
        try{
          myMethods[9].invoke(obj,args);
        }//end try block  

The output produced by this code fragment is:

Incoming parameter for this method: Invoking method 9
Value of the String variable: Instance Variable in the Outer Object
Value of the int variable: 4096
Value of String variable: Instance Variable in the Embedded Object

Remainder of the Program

That completes the portion of this program which is new material based on the Core Reflection API. The remainder of the program simply consists of a couple of class definitions used to construct the object in the first place. Material of this type has been discussed in many previous lessons and won't be discussed here.

The important thing to note when reviewing the remainder of the program is that

Program Listing

A listing of the program with additional comments follows:
/*File ObjSer03.java, Copyright 1997, R.G.Baldwin
Designed to be compiled and run under JDK 1.1

This application is designed to illustrate the new Reflection
capability of JDK 1.1 when used in conjunction with Object
Serialization and Inner Classes.

This program was tested using JDK 1.1 under Win95

The output from running this program on one particular occasion was:

Copyright 1997, R.G.Baldwin
Execution date: Sat Mar 01 08:49:00 PST 1997
Instantiate and display an object of type MyClass
Incoming parameter for this method: String parameter to display method.
Value of the String variable: Instance Variable in the Outer Object
Value of the int variable: 4096
Value of String variable: Instance Variable in the Embedded Object
Now save the object in a file using Object Serialization.
The original object has been saved in a disk file.
The reference to the original object has been set to null.
Now open the file for input.
Now retrieve the object from the disk file and disect it.
The toString() representation of the object is: MyClass@1cd017
The class name is: MyClass
The Superclass is: class java.lang.Object
Now investigate the fields of the object.
The number of fields is: 3
The name of field 0 is: myOuterString
The name of field 1 is: myIntVariable
The name of field 2 is: myEmbeddedObject
The type of field 0 is: class java.lang.String
The type of field 1 is: int
The type of field 2 is: class MyClass$MyInnerClass
Assume decision here to investigate field 1
The value of field 1 is 4096
Now investigate the methods of the object.
The name of method 0 is: getClass
The name of method 1 is: hashCode
The name of method 2 is: equals
The name of method 3 is: toString
The name of method 4 is: notify
The name of method 5 is: notifyAll
The name of method 6 is: wait
The name of method 7 is: wait
The name of method 8 is: wait
The name of method 9 is: showData
Assume decision here to investigate method 9.
The number of parameters for method 9 is: 1
The type for parameter 0 of method 9 is: class java.lang.String
Assume decision here to invoke method 9 which will display the object.
Incoming parameter for this method: Invoking method 9
Value of the String variable: Instance Variable in the Outer Object
Value of the int variable: 4096
Value of String variable: Instance Variable in the Embedded Object

*/
//==================================================================
import java.io.*;
import java.util.*;
import java.lang.Class.*;
import java.lang.reflect.*;
//==================================================================
class ObjSer03{
  static public void main(String[] args){
  MyProcess myProcess = new MyProcess();  
  }//end main()
}//end class ObjSer03
//==================================================================
class MyProcess{
  MyProcess(){//constructor
    try{
      FileOutputStream fout = new FileOutputStream("tmp");
      ObjectOutputStream  outStream  =  new  ObjectOutputStream(fout);

      System.out.println("Copyright 1997, R.G.Baldwin");
      System.out.println("Execution date: " + new Date().toString());      
      System.out.println(
          "Instantiate and display an object of type MyClass");
      MyClass temp = new MyClass();//instantiate object
      temp.showData("String parameter to display method.");//display
      
      System.out.println(
          "Now save the object in a file using Object Serialization.");
      outStream.writeObject(temp);//save object
      temp = null;//make object eligible for garbage collection
      outStream.flush();
      outStream.close();
      System.out.println(
          "The original object has been saved in a disk file.");
      System.out.println(
          "The reference to the original object has been set to null.");

      System.out.println("Now open the file for input.");      
      FileInputStream fin = new FileInputStream("tmp");
      ObjectInputStream inStream = new ObjectInputStream(fin);
      
      System.out.println(
          "Now retrieve the object from the disk file and disect it.");
      try{
        Object obj = inStream.readObject();//retrieve the object
        System.out.println(
            "The toString() representation of the object is: " 
            + obj.toString());
        System.out.println("The class name is: " 
            + obj.getClass().getName());
        System.out.println("The Superclass is: " 
            + obj.getClass().getSuperclass());
        System.out.println("Now investigate the fields of the object.");
        Field[] myFields = obj.getClass().getFields();
        System.out.println("The number of fields is: " 
            + myFields.length);
        for(int cnt = 0; cnt < myFields.length; cnt++)
          System.out.println("The name of field " + cnt + " is: " 
              + myFields[cnt].getName());
        for(int cnt = 0; cnt < myFields.length; cnt++)
          System.out.println("The type of field " + cnt + " is: " 
              + myFields[cnt].getType());
        
        System.out.println(
            "Assume decision here to investigate field 1");
        try{
          System.out.println("The value of field 1 is " 
              + myFields[1].getInt(obj));
        }catch(IllegalAccessException e){System.out.println(e);}
        
        System.out.println(
            "Now investigate the methods of the object.");
        Method[] myMethods = obj.getClass().getMethods();
        for(int cnt = 0; cnt < myMethods.length; cnt++)
          System.out.println("The name of method " + cnt + " is: " 
              + myMethods[cnt].getName());
          
        System.out.println(
            "Assume decision here to investigate method 9.");
        Class[] myParams =   myMethods[9].getParameterTypes();
        System.out.println(
            "The number of parameters for method 9 is: " 
                + myParams.length);
        for(int cnt = 0; cnt < myParams.length; cnt++) 
          System.out.println("The type for parameter " + cnt 
              + " of method 9 is: " + myParams[cnt]);
        
        System.out.println(
            "Assume decision here to invoke method 9 which will "
                + "display the object.");  
        Object[] args = new Object[1];
        args[0] = "Invoking method 9";
        try{
          myMethods[9].invoke(obj,args);
        }//end try block
        catch(IllegalAccessException e){System.out.println(e);}
        catch(InvocationTargetException e){System.out.println(e);}
        
      }catch(ClassNotFoundException e){System.out.println(e);}        
      inStream.close();  
    }catch(IOException e){System.out.println(e);}
  }//end constructor
}//end class MyProcess
//=============================================================


//Note that this class and the Inner Class both implement 
// the Serializable interface.

class MyClass implements Serializable{
  //Note the following inner class definition inside MyClass      
  class MyInnerClass implements Serializable{
    public String myEmbeddedString = //instance variable of inner class
        "Instance Variable in the Embedded Object";
        
    public void showEmbeddedData(){//instance method of inner class
      System.out.println("Value of String variable: " 
          + myEmbeddedString);
    }//end showEmbeddedData()  
  }//end class MyInnerClass

  //The following are instance variables of the outer class.
  public String myOuterString = 
      "Instance Variable in the Outer Object";
  public int myIntVariable = 4096;
  public MyInnerClass myEmbeddedObject = new MyInnerClass();

  public void showData(String inputStr){//instance method of outer class
    System.out.println("Incoming parameter for this method: " 
        + inputStr);
    System.out.println("Value of the String variable: " 
        + myOuterString);
    System.out.println("Value of the int variable: " + myIntVariable);
    myEmbeddedObject.showEmbeddedData();//display embedded object
  }//end showData()  

}//end class MyClass
//=============================================================  

-end-