Inspecting a Class for Its Methods

   

Inspecting a Class for Its Methods

The previous examples have focused on constructors, but Reflection also allows you to discover the regular methods a class declares. This functionality is again provided by java.lang.Class.

Obtaining a List of Methods

Going back to the Requestor / Provider example used in Listing 28.1, a few simple changes allow the methods of a class to be queried and displayed along with its constructors. Listing 28.6 shows the necessary modifications.

Listing 28.6 Reflection Reveals Methods as Well as Constructors
 /*  * Requestor  */ import java.lang.reflect.*; public class Requestor {   public void requestConstuctors() {     try {       // load the Provider class and use Reflection       // to get an array of constructor information       Class provClass = Class.forName("Provider");       Constructor con[] = provClass.getDeclaredConstructors();       for (int x=0; x<con.length; x++)         System.out.println("Constructor " + x + " = " + con[x]);       // get an array of Provider method information       Method meth[] = provClass.getDeclaredMethods();       for (int x=0; x<meth.length; x++)         System.out.println("Method " + x + " = " + meth[x]);     }     catch (ClassNotFoundException ce) {       // Class.forName was unable to load Provider.class       System.out.println("Could not locate class");     }     catch (SecurityException se) {       // Reflection permission was not granted       System.out.println("Not allowed to get class info");     }   }   public static void main(String args[]) {     // construct a Requestor and ask it for the     // constructors and methods declared by Provider     Requestor req = new Requestor();     req.requestConstuctors();   } } /*  *  Provider  */ class Provider {   String attribute;   // construct with a String to assign to the attribute   public Provider(String s) {     attribute = s;   }   // construct with an int to assign to the attribute   private Provider(int i) {     attribute = String.valueOf(i);   }   // set the attribute String   public void setAttribute(String attribute) {     this.attribute = attribute;   }   // get the attribute String   public String getAttribute() {     return attribute;   } } 

Now, when you compile Requestor.java and run it, the output you see should look like this:

 Constructor 0 = private Provider(int) Constructor 1 = public Provider(java.lang.String) Method 0 = public void Provider.setAttribute(java.lang.String) Method 1 = public java.lang.String Provider.getAttribute() 

As you can see, the underlying toString operation for the Method class reports the same information as does the one for Constructor with the addition of the method return type. One point to note about getDeclaredMethods is that it only returns the methods declared by the class or interface being reflected and not any of its superclasses or superinterfaces.

This does not mean, however, that if you override or overload a method, you won't be able to detect it because it was obtained through inheritance. You will see these methods. Overloaded methods are actually new, so they are not obtained through inheritance; and overridden methods are included in the methods list because there is a new implementation for them in the subclass that is unique to that class.

Instead of getDeclaredMethods, you can call getMethods to access the methods declared in a class or interface plus those it inherits. As with getConstructors, this method only returns the public methods.

Using getDeclaredMethod() to Invoke a Method

As you might have guessed, just like with the constructor example, invoking a method for the sake of invoking it isn't useful. The only exception might be to support the needs of a debugger, but this type of requirement is rare for most programmers.

Like its constructor counterpart , getDeclaredMethods has a sibling method, getDeclaredMethod, which obtains a specific method based on its name and parameter list:

 public Method getDeclaredMethod(String name, Class parameterTypes[]) 

You might notice that getDeclaredMethod takes an additional parameter compared to getDeclaredConstructor. It is necessary to specify a parameter list in both cases, but all constructors share the same name, so the name parameter is only needed here.

Notice that the method name and the parameter list are all that are needed to differentiate a method from any others. Remember that differences in the modifiers, return types, or exceptions thrown by two methods are not enough for them to be recognized by the compiler as two legal declarations. They must differ in name or parameter list as well. Here, getDeclaredMethod takes advantage of this fact and only requires you to provide the minimum information necessary to locate a specific method.

If no method matches the signature you pass to getDeclaredMethod, a NoSuchMethodException is thrown. This is a checked exception, so you need to enclose any calls to this method in a try - catch block.

Now let's go back to the car example and add a method to each subclass as shown in Listing 28.7.

Listing 28.7 The Car Example with Methods Added
 class Car {   TireSpecification tires;   boolean running;   public Car (TireSpecification tires){     this.tires = tires;    } } class BMW_540i extends Car {   public BMW_540i(TireSpecification tires) {     super(tires);   }   public boolean start() {     running = true;     System.out.println("The 540i is now running");     return true;   } } class Volvo_V70 extends Car {   public Volvo_V70(TireSpecification tires) {     super(tires);   }   public boolean start() {     running = true;     System.out.println("The V70 is now running");     return true;   } } 

In Listing 28.7, both the BMW_540i and Volvo_V70 classes have had a start method added. If you're thinking ahead, a better design here is obviously to declare this method in Car and take advantage of polymorphism, but this example is a little harder to illustrate a point. Given that, you need to add a method to the CarShop class to allow it to start either type of car. Listing 28.8 shows the addition of the startCar method for this purpose.

Listing 28.8 The Complete CarShop Class
 /*  * CarShop  */ import java.lang.reflect.*; public class CarShop {   Car carList[];   public CarShop( String[] carTypes ) {     // create and start a car of each specified type     carList = new Car[carTypes.length];     for ( int i=0; i<carTypes.length; i++ ) {       carList[i] = createCar(carTypes[i], new TireSpecification());       startCar( carList[i] );     }   }   public Car createCar(String carName, TireSpecification tires) {     Car newCar = null;     try {       Object constructorParam[] = new TireSpecification[1];       constructorParam[0]= tires;       // get the class name for the car that you want       Class carClass = Class.forName(carName);       // create an array of Classes, and use this to       // array to find the constructor that you want       Class parameters[] = new Class[1];       parameters[0]= TireSpecification.class;       Constructor  con = carClass.getDeclaredConstructor(parameters);       // create a car instance for the carList       newCar = (Car)con.newInstance(constructorParam);     }     catch (Exception e) {       System.out.println("Error creating " + carName);     }     return newCar;   }   public void startCar(Car theCar) {     try {       // Define a zero-length Class array to represent       // the empty parameter list of the start method       Class parameters[] = new Class[0];       Class carType = theCar.getClass();       Method meth = carType.getDeclaredMethod("start", parameters);       // invoke the method on the specified car       meth.invoke(theCar,parameters);     }     catch (Exception e) {       System.out.println("Error starting car: " + e);     }   }   // supply names of car types as command line arguments   public static void main( String args[] ) {     // create a car shop that contains the specified car types     CarShop shop = new CarShop( args );     for ( int i=0; i<shop.carList.length; i++ ) {       System.out.println( shop.carList[i] );     }   } } 

Now when you execute this application, it should notify you that both car types are running. The invoke method (of the Method class), used here to execute start, requires two parameters. One of these is the array of parameters required to invoke the method, just as a parameter array was used in the newInstance method of Constructor. However, invoke also needs to know which object the method is being called upon, so the method call also includes an instance of the class associated with the method. If the object is not an instance of the class that declared the method, an exception is thrown.

Going back to Listing 28.8, notice that to obtain the method start, you need to be operating directly on the class BMW_540i or Volvo_V70, and not on an instance of either of these classes. This is easy to manage because java.lang.Object defines a getClass method that returns the Class associated with an object.

Now let's revisit the earlier observation that this particular problem could have been solved with an interface or an overridden method. Neither of these approaches requires retrieving a method and invoking it through Reflection. The approach used in this example is obviously more complex. Although true here, you will not always have the information at compile time to implement a method call. You'll see in Chapter 29 that JavaBeans programming relies on Reflection to make properties of a bean accessible to code that knows nothing about it until runtime. With the capability to invoke methods like this with Reflection, you have added flexibility in how method calls are specified. In particular, getDeclaredMethod can be used to provide another method with a reference to a method that behaves somewhat like a method pointer in other languages.

This means the portion of the method signature that becomes important is the parameter list and not necessarily the method name. This approach allows you to create multiple methods, which provide similar functionality, without the need to enforce an inheritance hierarchy or even a shared set of method signatures declared in an interface.

As with getDeclaredMethods, getDeclaredMethod has a counter part in getMethod. This method accepts the same two parameters as getDeclaredMethod but only returns a method that matches the specified criteria if the method is public.

Invoking Methods That Accept Primitive Types as Parameters

One limitation of the examples so far is that you have not seen how to invoke any methods or constructors that accept a primitive type as a parameter. For instance, consider the NewCar in Listing 28.9.

Listing 28.9 NewCar Has a Constructor That Accepts an int
 public class NewCar{   int numTires;   public NewCar(int numTires) {      this.numTires = numTires;   }   public int getTireCount(){     return numTires;   } } 

To create an instance of NewCar, you must provide an int to the constructor. The getDeclaredContructor method, however, requires an array of Class objects to locate a specific constructor based on its parameter list. How do you get the class for an int? It's actually a simple construct built into the language. You can obtain a class object representing any primitive type by appending .class to the name of the type (for example, int.class ).

After you locate a constructor that requires a primitive type as a parameter, the next challenge is to provide an argument to use with newInstance given that it also works only with objects. You have probably already guessed that you need to use the wrapper classes from java.lang. In this example, java.lang.Integer can be used to pass an int argument. Listing 28.10 shows how to instantiate a NewCar this way.

Listing 28.10 TestNewCar Creates an Instance of the NewCar Class.
 import java.lang.reflect.Constructor; public class TestNewCar{   public static void main(String args[]) {     try {       Class car = Class.forName("NewCar");       // create the array of parameter types and find the constructor       Class param[] = { int.class} ;       Constructor con = car.getDeclaredConstructor(param);       // use a wrapper class to hold the int argument 4       Object values[] = { new Integer(4)} ;       // create an instance of the class       NewCar carObj = (NewCar)con.newInstance(values);       System.out.println("The car has " + carObj.getTireCount() + " tires");     }     catch (Exception e){       System.out.println("Something went wrong while instantiating NewCar");       e.printStackTrace(System.err);     }   } } 
   


Special Edition Using Java 2 Standard Edition
Special Edition Using Java 2, Standard Edition (Special Edition Using...)
ISBN: 0789724685
EAN: 2147483647
Year: 1999
Pages: 353

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net