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

Reflection and the Method Class - II

Java Programming, Lecture Notes # 261, Revised 02/13/98.

Preface

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

Introduction

One of the Java features introduced in JDK 1.1 is reflection. According to the documentation for JDK 1.1:
 
"(Reflection) Enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts on objects, within security restrictions. The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class."
A previous lesson reviewed many of the capabilities of reflection in general, and explored the capabilities of the Method class in particular. However, there was one important method of the Method class that we didn't explore - invoke(). We will begin our examination of this very important method in this lesson, and possibly continue into one or two additional lessons.

Sample Program

The purpose of this program is to demonstrate the invoke() method of the Method class using a very simple example program. More complicated programs which illustrate some of the benefits will be presented later.

This program assumes that the name of the method as well as the types of arguments in the formal argument list are known.

A test class named TstCls is defined which contains two methods named setFlds() and showFlds(). As you might expect, the setFlds() method is used to set values into the fields of an object of the class, and the showFlds() method displays those values.

The TstCls class has two instance variables. One is of type String and the other is of type Date.

An object of type TstCls is instantiated. The getClass() method is applied to this object to create an object of the Class class named theClass.

An array of Class objects is instantiated such that each of the elements in the array is a Class object that matches the class (type) of a corresponding item in the formal argument list of the method of interest. The method of interest is named "setFlds".

The getMethod() method of the Class class is then used in conjunction with the Class object named theClass and the name of the method of interest (as a String) and the array of class objects to create an object of type Method named theMethod that represents the method of interest.

An array of Object objects is instantiated and populated such that each of the elements in the array represents a parameter to be passed to the method of interest when it is invoked. Note that this must be an array of Object objects, and not an array of Class objects.

Then the invoke() method of the Method class is invoked on the object of type Method, named theMethod, passing a reference to theObj and the array of parameters. This causes the method of interest to be invoked on the object named theObj with the elements of the array being passed as parameters.

Note that the object of type Method is a generic representation of the method of interest which could be used to invoke that method on any object of that type. This is demonstrated later.

Finally, a conventional invocation of the showFlds() method on the object named theObj is executed to display the current values of the instance variables in the object named theObj to confirm that the setFlds() method was actually invoked in the roundabout manner described above.

After this, another object of the same type is instantiated and the Method object is used to invoke the same method on the new object, passing a different set of parameters.

This demonstrates that the Method object is a generic representation of the method of interest and can be used to invoke the method of interest on any object of the same type without any requirement to regenerate the Method object.

A large number of exceptions can be thrown so there is a correspondingly large number of catch blocks at the end of the program to deal with them.

Later on we will provide some examples to show how this may be useful.

This program was tested using JDK 1.1.3 under Win95.

The output from the program is shown in the full program listing later in the lesson.

Interesting Code Fragments

The first interesting code fragment is the test class shown below. An object of this class will be used to demonstrate that the invoke() method of the Method class can be used to invoke the methods of an object of the class.
 
//Define a test class
class TstCls{
  String strFld;
  Date dteFld;
  //-----------------------------------------------------//
  
  public void setFlds(String strFld, Date dteFld){
    this.strFld = strFld;
    this.dteFld = dteFld;
  }//end setFlds
  //-----------------------------------------------------//

  public void showFlds(){
    System.out.println(strFld);
    System.out.println(dteFld);
  }//end showFlds
  //-----------------------------------------------------//
}//end TstCls
The remainder of the program is all contained in one large class, which is the controlling class named Reflections04.

In fact, the entire remainder of the program is contained in the main() method of that class.

We will break this main() method up and discuss it in parts. The whole thing is enclosed in a try block which we will ignore.

Initially, we will instantiate an object of our test class, and apply the getClass() method to that object to obtain an object of type Class which we will reference with a reference variable named theClass.
 
      TstCls theObj = new TstCls();
      Class theClass = theObj.getClass();
Next, we need an array of Class objects that contains one object reference representing the correct type for each of the arguments in the formal argument list of the method of interest. In our case, the method of interest has a String parameter and a Date parameter. This means that the first element in our Class array must represent the String class, and the second element must represent the Date class.

We accomplish this with the following code fragment, assigning the array to the reference variable named theParams. Note that we are not simply instantiating new objects of the String and Date classes. Rather, we are using those new instances to create new instances of the Class class that represent those classes.
 
      Class[] theParams = new Class[2];
      theParams[0] = new String().getClass();
      theParams[1] = new Date().getClass();
The next step is to apply the getMethod() method of the Class class to the Class object representing the class of interest to create a Method object representing the method named setFlds whose formal argument list matches the contents of the array named theParams that we constructed above.

Recall that because of method overloading, the class may contain many methods having this same name, but it can only contain one method having both the name and the argument list specified.

Note that there are several similar-sounding methods of the Class class that will create Method objects. This particular one will only create Method objects for public methods.
 
      Method theMethod = 
                  theClass.getMethod("setFlds",theParams);
In a minute, we are going to use the invoke() method of the Method class to invoke the method represented by our Method object. However, in order to do that, we need to be able to pass the required parameters to the method.

The methodology for passing the parameters is to instantiate an array of type Object where each of the elements in the array is of the correct type and of the correct value to qualify as parameters to the method. Note that this is an array of type Object and is not an array of type Class.

Objects of type Object can, and in this case, should contain data. On the other hand, objects of type Class represent classes and do not contain data in the sense that we normally use the word data (I'll probably get called to task for this statement).

The following code fragment instantiates and populates an object of type Object with the first element being a String object and the second element being a Date object.
 
      Object[] setParams = new Object[2];
      setParams[0] = "One Object";
      setParams[1] = new Date();
Now we will pull it all together.

At this point, the Method object referred to as theMethod represents a method having a specific name and a specific formal argument list.

We will invoke the invoke() method on theMethod.

The purpose is to invoke another method having the name and a formal argument list represented by our Method object referred to as theMethod.

We will pass to the invoke() method a reference to the object that contains the method of interest.

We will also pass an array of type Object that contains the parameters that we want to pass to the method in that object that we are invoking..

Look this code over very carefully and make certain that you understand it. This is how the invoke() method works, and is similar to some other methods in the reflection API.
 
      theMethod.invoke(theObj,setParams);
Lets review. We create an object of type Method that represents a generic method having a specified name and a specified formal argument list. In effect, the Method object represents a method signature.

We invoke the invoke() method on that object to invoke a method having that signature. To cause it to invoke the method on the correct object, we pass a reference to the object as a parameter to the invoke() method. In other words, if many different objects existed within the scope of our code having methods with the same signature, we could use the same Method object to invoke that method on any of those objects.

Finally, in order to pass the necessary parameters to the method, we instantiate and populate an array of type Object containing the necessary object references to satisfy the required parameters.

If the formal argument list contains arguments of primitive types, we must wrap those primitive values in the standard Java wrappers for the specified type when we construct our array of objects. They will be automatically unwrapped and used when the method is invoked.

The above code should have cause some specific values to be set into the instance variables of the object on which the method was invoked. Following this, we use a conventional method invocation approach to invoke the showFlds() method on the object so that we can inspect those values and confirm that they were properly set.
 
      theObj.showFlds(); //display new state of object 
I mentioned earlier that the same Method object can be used to invoke a method with a matching signature on any object within the scope of the code. The following fragment instantiates another object and invokes the method on the new object without regenerating the Method object to demonstrate that this can be done.
 
      TstCls anotherObj = new TstCls();
      setParams[0] = "A Different Object";
      setParams[1] = new Date();
      theMethod.invoke(anotherObj,setParams);
      anotherObj.showFlds(); //display new state of object
We follow this with several catch blocks to complete the program.

As indicated earlier, this program was designed for clarity, so that you can see the basic mechanics of using the invoke() method without complicating things by trying to make it do something useful.

The next lesson will apply the invoke() method to a real world situation and develop a relatively sophisticated event-forwarding smart adapter class with built-in event filtering capability.

.

Program Listing

/*File Reflections04.java Copyright 1998, R.G.Baldwin

The purpose of this program is to demonstrate the invoke()
method of the java.lang.reflect.Method class using a 
very simple example program.  More complicated programs
which illustrate some of the benefits will be presented
later.

This program was tested using JDK 1.1.3 under Win95.

The output from the program is shown below.  

One Object
Tue Jan 06 15:08:01 CST 1998
A Different Object
Tue Jan 06 15:08:02 CST 1998
**********************************************************/
import java.lang.reflect.*;
import java.util.*;
//=======================================================//

//Define a test class
class TstCls{
  String strFld;
  Date dteFld;
  //-----------------------------------------------------//
  
  public void setFlds(String strFld, Date dteFld){
    this.strFld = strFld;
    this.dteFld = dteFld;
  }//end setFlds
  //-----------------------------------------------------//

  public void showFlds(){
    System.out.println(strFld);
    System.out.println(dteFld);
  }//end showFlds
  //-----------------------------------------------------//
}//end TstCls
//=======================================================//

class Reflections04 {
  
  public static void main(String[] args) {
    try{
      //Instantiate an object of the test class
      TstCls theObj = new TstCls();
    
      //Create a Class object for the test class
      Class theClass = theObj.getClass();
      
      //Create an array of Class objects which matches
      // the known formal argument list of the method
      // of interest.
      Class[] theParams = new Class[2];
      theParams[0] = new String().getClass();
      theParams[1] = new Date().getClass();
      
      //Use the name of the method and the array describing
      // the formal argument list to create an object of
      // type Method that represents the method of interest
      Method theMethod = 
                  theClass.getMethod("setFlds",theParams);
      
      //Create an array of Object objects that matches
      // the known formal argument list of the method of
      // interest.  Populate this array with parameter
      // values to be passed to the method.  Note that
      // this is an Object array and not a Class array.
      Object[] setParams = new Object[2];
      setParams[0] = "One Object";
      setParams[1] = new Date();
      
      //Use the Method object and the array of parameter
      // objects to invoke the method of interest.
      theMethod.invoke(theObj,setParams);
      
      //Use the conventional approach to invoke another
      // method on the same object to demonstrate that
      // the setFlds() method was properly invoked.
      theObj.showFlds(); //display new state of object 
      
      //Instantiate another object of the same type and
      // use the existing Method object to invoke the same
      // method on it, passing different parameters.
      TstCls anotherObj = new TstCls();
      setParams[0] = "A Different Object";
      setParams[1] = new Date();
      theMethod.invoke(anotherObj,setParams);
      anotherObj.showFlds(); //display new state of object

    }//end try block
    //Deal with all the possible exceptions here
    catch(SecurityException e){System.out.println(e);}
    catch(NoSuchMethodException e){System.out.println(e);}
    catch(IllegalAccessException e){System.out.println(e);}
    catch(IllegalArgumentException e){
                                    System.out.println(e);}
    catch(InvocationTargetException e){
                                    System.out.println(e);}
  }//end main()
}//end Reflections04
-end-