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.
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 TireSpecificationpublic 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 Constructorspublic 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. |