by Joe Weber
Java 1.1 introduced a number of changes to the Java language. One of the most significant of these is called Reflection. Using Reflection objects can be inspected to reveal the methods and variables which they contain. Holding just the Class you can determine the nature of the constructors, and whole host of other things which were previously hidden from the system.
According to Sun, Reflection is a "small, type-safe and secure API which supports introspection about the classes and objects in the current JVM." This may need a bit of translation to some of you, if not most. Essentially the key word in the definition is introspection. Using Reflection, you can take an Object such as a Vector, look at it under a microscope, find out what classes it extends and what methods and variables it has, and you can do this without knowing that the Object is a Vector.
To accomplish the task of inspection Sun had to add a couple of classes in the java.lang package, including Field, Method, and Constructor. Each of these classes is used to obtain information about their respective characteristics from an object. In addition, to handle the rest of the class Sun has added the Array and Modifier classes.
Surrounding the whole use of Reflection is the enhanced Java Security Model. The security model prevents classes that don't have access to methods, fields, constructors, and so on, from being able to see them. How the security model works with Reflection is through a fairly tight coupling of some new class methods with the security manager. To do this the SecurityManager itself has been granted an additional method--checkMemberAccess(). When a Class is asked to produce its Method class (note: only a Class is allowed to create a Method, Field, or Constructor class) it first queries the SecurityManager to determine if it's okay to give the requesting class a copy of its Method class. If it is--fine, if not, the request is denied. If this sounds like someone trying far too hard to use the word "class," look at it this way: Say you have a scenario where the Object Requestor wants to know the methods of Object Provider. In Requestor you want to know what constructors are available in Provider. To do this you might create two classes as seen in Listing 20.1
/* * * Requestor * */ import java.lang.reflect.*; public class Requestor{ public void requestConstuctors(){ try{ Constructor con[]= Class.forName("Provider").getDeclaredConstructors(); for (int x=0;x<con.length;x++) System.out.println("Constructor "+x+" = "+con[x]); } catch (ClassNotFoundException se){ System.out.println("Not allowed to get class info"); } } public static void main(String args[]){ Requestor req = new Requestor(); req.requestConstuctors(); } } /* * Provider * */ class Provider{ public Provider(){ } public Provider(String s){ } }
Once you compile this class, which should be called Requestor.java, (note: you must compile it using the JDK 1.1; this won't work under 1.0) you can then run the Requestor program (also using 1.1). The output you get looks like this:
Constructor 0 = public Provider() Constructor 1 = public Provider(String)
That's a pretty neat trick, and one that you quite simply couldn't accomplish without Reflection. Under other languages, such as C/C++, access to methods can be accomplished using method pointers. Because Java has no pointers, it's necessary to have this Reflection model in order gain access to runtime methods.
Now, try something else: Instantiate the Provider class using the Provider (String) method. Under JDK 1.0 this was simply impossible (without using the direct approach, of course). Listing 20.2 shows just how to do this. Notice that I've cheated a bit by using the null parameter version of newInstance() method for the parameters (which can only be done because I know that the parameter (String) has a null constructor).
/* * * Requestor * */ import java.lang.reflect.*; public class Requestor{ public void requestConstuctors(){ Class cl; Constructor con[]; try{ cl = Class.forName("Provider"); con = cl.getDeclaredConstructors(); for (int x=0;x<con.length;x++) System.out.println("Constructor "+x+" = "+con[x]); Class param[] = con[1].getParameterTypes(); Object paramValues[] = new Object[param.length]; for (int x=0;x<param.length;x++){ if (!param[x].isPrimitive()){ System.out.println("param:"+param[x]); paramValues[x]=param[x].newInstance(); } } Object prov = con[1].newInstance(paramValues); } catch (InvocationTargetException e){ System.out.println("There was an InvocationEception and we were not allowed to get class info: "+e.getTargetException()); } catch (Exception e){ System.out.println("Exception during construction:"+e); } } public static void main(String args[]){ Requestor req = new Requestor(); req.requestConstuctors(); } } class Provider{ String me; public Provider(){ me = new String(); } public Provider(String x){ this.me=me; } }
Of course this whole system works, but probably isn't very practical. After all, it's not very often that all you want to do is construct a defaulted object, and at the same time that object doesn't have a null constructor of its own. The more likely time when this comes in handy is when you want to instantiate an object which has a constructor that you expect. For example, if you were to build up an API and the basis of one class is a constructor that takes several parameters, each class that extends your base class has to overload this constructor. That seems simple enough, right? For instance, take the ever popular car model. There will be two classes: Car and Tires (see Listing 20.3).
public class Tires { int number; float diameter; } public class Car{ Tires tires; public Car (Tires tires){ this.tires = tires; } }
There's no need to fill out the rest of this class, since it's irrelevant for the current discussion. However, the point to get here is that the class Car obviously needs to receive a Tires object from an outside source. When you go to create a subclasses, say Saturn and BMW, you still need to get the Tires model from an outside source as seen in Listing 20.4
public class Saturn extends Car{ public Saturn(Tires tires){ super(tires); } } public class BMW extends Car{ public BMW(Tires tires){ super(tires); } }
The only problem now is that under JDK 1.0 you had no truly object-oriented way to handle each new type of car without coding at least its constructor into the program. You might think that this sounds like an Interface solution, but unfortunately Interfaces aren't broad enough to handle this situation. Interfaces allow you to create "templates" for methods, but not constructors. This means that every time a new car line is introduced you would need to go back, find every instance where the new car needed to be added, and add it in manually. Now, using Reflection this becomes very easy to do, as seen in Listing 20.5.
/* * * CarShop * */ import java.lang.reflect.*; public class CarShop { Car carList[]; public CarShop (){ carList = new Car[2]; createCar("Saturn",0); createCar("BMW",1); } public void createCar(String carName,int carNum){ try{ //create the Tires array, which you'll use as //the parameter to the constructor Object constructorParam[] = new Tires[1]; constructorParam[0]= new Tires(); //get the class name for the car that you want Class cl = Class.forName(carName); //create an array of Classes, and use this //array to find the constructor that you want Class parameters[] = new Class[1]; parameters[0]= Class.forName("Tires"); Constructor con = cl.getDeclaredConstructor(parameters); //create a car instance for the carList carList[carNum] = (Car)con.newInstance(constructorParam); } catch (Exception e){ System.out.println("Error creating "+carName +":"+e); } } public static void main(String args[]){ new CarShop(); } }
In this example the most important thing is obviously the createCar() method. Here it's broken down step-by-step:
Object constructorParam[] = new Tires[1]; constructorParam[carNum]= new Tires();
As you saw earlier using Reflection, the method newInstance that allows you to create a new instance of a class takes as its parameter an array of Objects (Object []). As a result, take a look at the constructor you hope to use:
Car (Tires tire)
This constructor has one parameter and it is of type Tires, so what is needed is to create is an array (of one) with a Tire object as the first (and only) element.
Class cl = Class.forName(carName);
The next thing that you need to do is get the class that you're looking for. Notice that you're doing this just by knowing the name of the class. This means that you can even create a <PARAM> value that would contain a list of all the currently known cars.
Class parameters[] = new Class[1]; parameters[0]= Class.forName("Tires"); Constructor con = cl.getDeclaredConstructor(parameters);
The next thing you do is find the constructor that matches the one you're looking for. To do this you must create an array of classes. Each of the elements of this array is a class which match the order of the parameters for the constructor you're looking for.
carList[carNum] = (Car)con.newInstance(constructorParam);
Now that you have obtained the correct constructor and put the parameter array together, you can finally create a new instance of the car. And to this you're probably saying, to yourself...All this for what I could have written as:
carList[carNum] = new Saturn(new Tire());
Yes, that's true, but to really account for this situation you would have needed to have an if loop that looked like:
if (carName.equals("Saturn")) carList[carNum] = new Saturn(new Tire()); else if (carName.equals("BMW")) carList[carNum] = new BMW(new Tire());
Each time you added a new car you'd have to go back in and add another if loop. With Reflection this isn't necessary.
In addition to discovering Class methods, Reflection can also be used to help you discover the methods a class has. To do this, a very similar method to getDeclaredConstructors() has been created called getDeclaredMethods().
Going back to the Requestor/Provider example used in Listing 20.1, in addition to listing the constructors, you'll add a couple of methods to the Provider class, and see what happens when you request them in the Requestor class as shown in Listing 20.6.
/* * * Requestor * */ import java.lang.reflect.*; public class Requestor{ public void requestConstuctors(){ Class cl; Constructor con[]; Method meth[]; try{ cl = Class.forName("Provider"); con = cl.getDeclaredConstructors(); for (int x=0;x<con.length;x++) System.out.println("Constructor "+x+" = "+con[x]); meth = cl.getDeclaredMethods(); for (int x=0;x<meth.length;x++) System.out.println("Method "+x+" = "+meth[x]); } catch (NoSuchMethodException e){ System.out.println( "There was an exception and we were not allowed to get class info: "+e); } } public static void main(String args[]){ Requestor req = new Requestor(); req.requestConstuctors(); } } class Provider{ int x; public Provider(){ this.x=0; } public Provider(int x){ this.x=x; } public boolean testMe(boolean test){ return !test; } public int addThree(int num){ return num+3; } public char letterD(){ return `D'; } }
Now, when you compile Requestor.java and run it, the output you will see should look like this:
Constructor 0 = public Provider() Constructor 1 = public Provider(int) Method 0 = public boolean Provider.testMe(boolean) Method 1 = public int Provider.addThree(int) Method 2 = public char Provider.letterD()
As you can see, the method contains not only the modifiers and parameters as the constructor did, but also returns type. This should not be surprising to you since this is obviously a critical component of any method. However, make a change to the Provider class as shown below in Listing 20.7.
/* * * Provider * */ class Provider extends java.applet.Applet{ int x; public Provider(){ this.x=0; } public Provider(int x){ this.x=x; } public boolean testMe(boolean test){ return !test; } public int addThree(int num){ return num+3; } public char letterD(){ return `D'; } } Now if you run Requestor again, the output should look like this: Constructor 0 = public Provider() Constructor 1 = public Provider(int) Method 0 = public boolean Provider.testMe(boolean) Method 1 = public int Provider.addThree(int) Method 2 = public char Provider.letterD()
Can you tell the difference? No, you can't, because there isn't any. Despite the fact that you just made Provider extend java.applet.Applet which itself has a number of methods, these methods do not show up in the listing from getMethods(). This is because getMethods() returns all the methods that are declared by the current class (or interface) but does not return those methods the class obtains by inheritance.
You might be asking yourself, "Does this mean if I override or overload a method I won't be able to detect it since it was obtained through inheritance?" The answer is no on both counts--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 to avoid just this confusion.
As you may have guessed, just like with the constructor example, invoking a method just for the sake of invoking it really isn't very useful except in rare instances, like writing a debugger.
Just like its constructor counter part, getDeclaredConstructor(), getDeclaredMethods() has a sibling method, getDeclaredMethod(), that will obtain just the specific method you are looking for:
public Method getDeclaredMethod(String name, Class parameterTypes[])
Right away you may have noticed that getDeclaredMethod takes an additional parameter. This is due to the fact that, as with a Constructor, it's necessary to find a method based on its parameter list. But when you were looking for a Constructor, it wasn't necessary to know the name of the constructor, since every constructor's name is the same as the class. To find a method however, you need to specify the method name as well.
Just like its Constructor counterpart, getDeclaredMethod does throw a NoSuchMethodException if no method matches the signature you're looking for. This of course means that you need to enclose any getDeclaredMethod() in a try-catch block.
Now go back to the car example and make a few changes as shown in Listing 20.8.
class Car{ Tires tires; boolean running = false; public Car (Tires tires){ this.tires = tires; } } class Saturn extends Car{ public Saturn(Tires tires){ super(tires); } public boolean start(){ running = true; System.out.println("The Saturn is now running"); return true; } } class BMW extends Car{ public BMW(Tires tires){ super(tires); } public boolean start(){ running = true; System.out.println("The BMW is now running"); return true; } }
First modify the Car classes and add a start() method to the Saturn and BMW cars. Don't add the start() method to Car, that would make life too easy. Next you need to add a method to the CarShop class to allow it to start the cars. In this case, call the method startCar() and call it right after you've added the Saturn and BMW to your motorpool, as shown in Listing 20.9 below.
/* * * CarShop * */ import java.lang.reflect.*; public class CarShop { Car carList[]; public CarShop (){ carList = new Car[2]; createCar("Saturn",0); createCar("BMW",1); startCar(1); } public void createCar(String carName,int carNum){ try{ //create the Tires array, that you'll use as a //the parameter to the constructor Object constructorParam[] = new Tires[1]; constructorParam[0]= new Tires(); //get the class name for the car that you want Class cl = 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]= Class.forName("Tires"); Constructor con = cl.getDeclaredConstructor(parameters); //create a car instance for the carList carList[carNum] = (Car)con.newInstance(constructorParam); } catch (Exception e){ System.out.println("Error creating "+carName +":"+e); } } public void startCar(int num){ try{ //create an array of Classes, and use this to //array to find the method you want //since you are actually looking for a null parameter //this is an array of 0 Class parameters[] = new Class[0]; Class carType = carList[num].getClass(); Method meth = carType.getDeclaredMethod("start",parameters); //create a car instance for the carList meth.invoke(carList[num],parameters); } catch (Exception e){ System.out.println("Error starting car "+num +":"+e); } } public static void main(String args[]){ new CarShop(); } }
Now when you run this application, it should notify you that the BMW is now running. It's important to point out something about the invoke() method. invoke() requires two parameters. The second parameter is the array of parameters required to invoke the method just as the 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 first parameter of invoke() is the correct object. What happens if the object doesn't have a start() method? Well, it actually goes a bit further than that, if the object is not an instance of the class which declared the method then an Exception is thrown.
Also, going back to Listing 20.9 once more, notice that in order to obtain the method start() you need to be operating on the Class BMW or Saturn and not on the Object instances of these classes. Under JDK 1.1, java.lang.Object has been blessed with a new method called getClass() which, as you can see, helps you solve this problem easily.
At this point, a good object-oriented programmer might ask, "Why would I ever want to use this method to invoke a method?" After all, a much better design would have you using either an interface, or the start() method would be in the car() class. Under either of these two scenarios it would be unnecessary to find the method before invoking it. This is true, except for the fact that the world is not always perfect. Without the ability to invoke methods like this with Reflection, you have put a fairly substantial limitation on programming architecture. Much more importantly, getDeclaredMethod() can frequently be used to provide another method with a "method pointer." This means that the portion of the method signature that becomes important is the parameter list and not necessarily the method name. This level of extension allows you to create multiple methods, which require similar processing, without the need to create multiple process layers.
The final aspect of Reflection covered in this chapter is obtaining a list of the fields that a Class has. As you may have already guessed, the two methods most useful in this endeavor are getDeclaredFields() and getDeclaredField() Since in the previous Provider class (Listing 20.7) you already had a variable (x), all you need to do now to demonstrate this technology is take the Requestor class one step further as shown in Listing 20.10.
/* * * Requestor * */ import java.lang.reflect.*; public class Requestor{ public void requestConstuctors(){ Class cl; Constructor con[]; Method meth[]; Field field[]; try{ cl = Class.forName("Provider"); con = cl.getDeclaredConstructors(); for (int x=0;x<con.length;x++) System.out.println("Constructor "+x+" = "+con[x]); meth = cl.getDeclaredMethods(); for (int x=0;x<meth.length;x++) System.out.println("Method "+x+" = "+meth[x]); field = cl.getDeclaredFields(); for (int x=0;x<field.length;x++) System.out.println("Field "+x+" = "+field[x]); } catch (Exception e){ System.out.println("There was an exception and you were not allowed to get class info: "+e); } } public static void main(String args[]){ Requestor req = new Requestor(); req.requestConstuctors(); } }
If you've been following the previous two examples, you have probably already guessed that the resulting output from your new Requestor looks like this:
Constructor 0 = public Provider() Constructor 1 = public Provider(int) Method 0 = public boolean Provider.testMe(boolean) Method 1 = public int Provider.addThree(int) Method 2 = public char Provider.letterD() Method 3 = public boolean Provider.mouseDown(java.awt.Event,int,int) Field 0 = int Provider.x
Field itself can be used to provide a number of widening and narrowing conversions on Field types, but that area will be left to you for further investigation.