Creating a Class Knowing Only the List of Constructors

   

After you have retrieved the constructors associated with a class, it is also possible to use them to instantiate an object. Listing 28.2 shows a simple example of how to do this.

Listing 28.2 Requestor Creates a Provider Without Knowing the Constructor Details in Advance
 /*  * Requestor  */ import java.lang.reflect.*; public class Requestor {   // Return an array of Provider instances that   // includes an instance created with each   // constructor declared by the class   public static Provider[] constructProvider() {     Provider[] prov = null;     try {       // load the class and retrieve its constructors       Class cl = Class.forName("Provider");       Constructor[] con = cl.getDeclaredConstructors();       // allocate the Provider array based on the       // number of constructors found       prov = new Provider[con.length];       for (int i=0; i<con.length; i++) {         // get parameter info for the constructor         Class param[] = con[i].getParameterTypes();         // create an array to hold the number of         // parameters required by this constructor         Object paramValues[] = new Object[param.length];         // look at each parameter type         for (int x=0; x<param.length; x++) {           if (!param[x].isPrimitive()) {             System.out.println("param:" + param[x]);             // create an object of this parameter type             // using its no-argument constructor             paramValues[x] = param[x].newInstance();           }         }         // the parameter array has been loaded, so create         // an object using this Provider constructor         prov[i] = (Provider)con[i].newInstance(paramValues);       }     }     catch (InvocationTargetException e) {       System.out.println("Could not get class info");     }     catch (Exception e) {       System.out.println("Exception during construction");     }     // return the array of Providers     return prov;   }   public static void main(String args[]) {     // create a Provider instance from each constructor     Provider[] prov = Requestor.constructProvider();     for (int i=0; i<prov.length; i++) {       // display the Provider's one attribute to see which       // constructor it was created by       System.out.println("Created provider with attribute: " +         prov[i].attribute); }   } } /*  * Provider  *   A simple class with one field and two constructors  */ class Provider{     String attribute;     // Declare a no-argument constructor     public Provider(){         attribute = "Not specified";     }     // Declare a single-argument constructor     public Provider(String s){         attribute = s;     } } 

This example cheats somewhat by taking advantage of the fact that the single-argument constructor of Provider expects a String, which can be constructed with a no-argument constructor.

Creating a Class Instance

Perhaps the most important aspect of Java demonstrated in Listing 28.2 is that you are not limited to a single approach for creating an instance of a class. The most common method is to use the new operator introduced in Chapter 7 to instantiate a class by specifying a constructor to call, as in the following:

 String string1 = new String(); String string2 = new String("My String"); 

As you have begun to see in this chapter, there are other mechanisms for instantiating a class as well. For example, the forName and newInstance methods of Class provide a way to instantiate a class using its no-argument constructor:

 Class stringClass = Class.forName("java.lang.String"); String string3 = stringClass.newInstance(); 

As shown here, forName loads a Class object based on a specified name . An already accessible class is used in the example, but this approach is most often used for classes that are not known until runtime. For example, you might have several classes that implement a particular interface and want to defer the decision of which implementation to use until runtime. The forName method allows you to provide the class name as a parameter, perhaps as a command line argument, to your program that can be used to load the class dynamically. Assuming the class has a no-argument constructor, the newInstance method of Class can then be used to create an instance.

As first shown in Listing 28.2, Class and Constructor can also be used to instantiate a class using a constructor that accepts parameters. The following example shows how this can be done to construct a String instance using the constructor that accepts another String as an argument:

 // get the String class object Class stringClass = Class.forName("java.lang.String"); // build a one-element array that holds the class of the // constructor parameter Class[] parameterType = {  String.class } ; // find the constructor that accepts a String as an argument Constructor con = stringClass.getConstructor( parameterType ); // build a one-element array that holds the argument Object[] parameter = {  "My String" } ; // use Reflection to call the desired constructor String string4 = (String)con.newInstance( parameter ); 

The example in Listing 28.2 works, but it's not practical. After all, it's not often that all you want to do is construct an object without assigning any useful information to it. This concept is more meaningful when you want to instantiate an object that has a constructor of a form that you expect. For example, if you were to build up an API that includes a superclass with a constructor that accepts several parameters, each class that extends it should probably declare a constructor that accepts the same parameter list so that the parameters can be passed on to the superclass during instantiation.

As an example, look at a factory approach for building subclass instances where the superclass constructor requires a single parameter. In this case, the superclass and its constructor parameter type are the Car and TireSpecification classes shown in Listing 28.3.

Listing 28.3 A Car with a TireSpecification
 public class TireSpecification {   float treadDepth;   float diameter; } public class Car {   TireSpecification tires;   public Car (TireSpecification tires){     this.tires = tires;    } } 

The other details of these classes are left out to put the focus only on what is necessary to work with the Car constructor. Based on these declarations, the class Car needs to receive a TireSpecification object from some source whenever an instance is created. When you create subclasses to represent specific car models, say BMW_540i and Volvo_V70, you will need to obtain this same information and provide it to the superclass constructor as shown in Listing 28.4.

Listing 28.4 BMW_540i and Volvo_V70 Subclass Constructors
 public class BMW_540i extends Car {   public BMW_540i(TireSpecification tires) {     super(tires);   } } public class Volvo_V70 extends Car {   public Volvo_V70(TireSpecification tires) {     super(tires);   } } 

If every program that uses the subclasses of Car has knowledge of them in advance, those programs can access the constructors for classes such as BMW_540i and Volvo_V70 to create instances as needed. The drawback is that this requires code to be changed whenever a new type of car is defined, even though some programs might not exercise any behavior unique to a particular subclass. You might think that this sounds like a problem that could be addressed with polymorphism or an interface, but neither of these works when dealing with constructors. Reflection does offer a solution is this case, however. Given that every subclass declares a constructor that accepts a TireSpecification, you can use Reflection to make the programs that use Car subclasses aware of new car types dynamically. Such an approach is shown in Listing 28.5.

Listing 28.5 CarShop Creates Car Instances Using Reflection
 /*  * CarShop  */ import java.lang.reflect.*; public class CarShop {   Car carList[];   public CarShop( String[] carTypes ) {     // create 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());     }   }   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       // 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;   }   // supply names of the 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] );     }   } } 

Listing 28.5 should be saved as CarShop.java and it should be executed using the command

 java CarShop BMW_540i Volvo_V70 

In this example, the most important operation is obviously the createCar method. Look at each of the steps, starting with the creation of the constructor parameters. These two lines place the TireSpecification provided to the method into a single element array.

 Object constructorParam[] = new TireSpecification[1]; constructorParam[0]= tires; 

As you saw earlier, the method newInstance of Constructor accepts an array of objects for its parameter list. Given that the constructors of Car and its subclasses accept a single parameter of type TireSpecification, constructorParam now holds all that is necessary.

The next step is to obtain a Class instance associated with the type of car being created. This is done using the name of the class:

 Class carClass = Class.forName(carName); 

Now the desired constructor for the class can be obtained using the getDeclaredConstructor method of Class. The whole point here is to take advantage of the fact that every Car subclass should have a constructor that accepts a TireSpecification. The getDeclaredConstructor method accepts an array of classes and attempts to find a constructor whose parameters match the class types and order represented in this array.An array is passed in with a single element containing the TireSpecification class:

 Class parameters[] = new Class[1]; parameters[0]= TireSpecification.class; Constructor  con = carClass.getDeclaredConstructor(parameters); 

The correct constructor is then used to create the specified type of car:

 newCar = (Car)con.newInstance(constructorParam); 

You might be questioning all the effort when a BMW could have been created much easier using:

 newCar = new BMW_540i(tires); 

This seems true, but to really account for this situation you would need a conditional expression that looked like the following:

 if (carName.equals("BMW_540i"))   newCar = new BMW_540i(tires); else if (carName.equals("Volvo_V70"))   newCar = new Volvo_V70(tires); 

Each time you add a new car, you would have to go back in and add another conditional statement. With Reflection, this isn't necessary. Instead, new car types can be used by CarShop by doing nothing more than including the name of the new Car subclass as one of the command line arguments to the program.

   


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