The idea of run-time type identification (RTTI) seems fairly simple at first: it lets you find the exact type of an object when you only have a reference to the base type.
However, the need for RTTI uncovers a whole plethora of interesting (and often perplexing) OO design issues, and raises fundamental questions of how you should structure your programs. Comment
This chapter looks at the ways that Java allows you to discover information about objects and classes at run-time. This takes two forms: traditional RTTI, which assumes that you have all the types available at compile-time and run-time, and the reflection mechanism, which allows you to discover class information solely at run-time. The traditional RTTI will be covered first, followed by a discussion of reflection. Comment
Consider the now familiar example of a class hierarchy that uses polymorphism. The generic type is the base class Shape, and the specific derived types are Circle, Square, and Triangle:
This is a typical class hierarchy diagram, with the base class at the top and the derived classes growing downward. The normal goal in object-oriented programming is for the bulk of your code to manipulate references to the base type (Shape, in this case), so if you decide to extend the program by adding a new class (Rhomboid, derived from Shape, for example), the bulk of the code is not affected. In this example, the dynamically bound method in the Shape interface is draw( ), so the intent is for the client programmer to call draw( ) through a generic Shape reference. draw( ) is overridden in all of the derived classes, and because it is a dynamically bound method, the proper behavior will occur even though it is called through a generic Shape reference. Thats polymorphism. Comment
Thus, you generally create a specific object (Circle, Square, or Triangle), upcast it to a Shape (forgetting the specific type of the object), and use that anonymous Shape reference in the rest of the program. Comment
As a brief review of polymorphism and upcasting, you might code the above example as follows:
//: c12:Shapes.java package c12; import com.bruceeckel.simpletest.*; import java.util.*; class Shape { void draw() { System.out.println(this + ".draw()"); } } class Circle extends Shape { public String toString() { return "Circle"; } } class Square extends Shape { public String toString() { return "Square"; } } class Triangle extends Shape { public String toString() { return "Triangle"; } } public class Shapes { static Test monitor = new Test(); public static void main(String[] args) { List s = new ArrayList(); s.add(new Circle()); s.add(new Square()); s.add(new Triangle()); Iterator e = s.iterator(); while(e.hasNext()) ((Shape)e.next()).draw(); monitor.expect(new String[] { "Circle.draw()", "Square.draw()", "Triangle.draw()" }); } } ///:~
The base class contains a draw( ) method that indirectly uses toString( ) to print an identifier for the class by passing this to System.out.println( ). If that function sees an object, it automatically calls the toString( ) method to produce a String representation. Comment
Each of the derived classes overrides the toString( ) method (from Object) so that draw( ) ends up printing something different in each case. In main( ), specific types of Shape are created and then added to a List. This is the point at which the upcast occurs because the List holds only Objects. Since everything in Java (with the exception of primitives) is an Object, a List can also hold Shape objects. But during an upcast to Object, it also loses any specific information, including the fact that the objects are Shapes. To the ArrayList, they are just Objects. Comment
At the point you fetch an element out of the List with next( ), things get a little busy. Since the List holds only Objects, next( ) naturally produces an Object reference. But we know its really a Shape reference, and we want to send Shape messages to that object. So a cast to Shape is necessary using the traditional (Shape) cast. This is the most basic form of RTTI, since in Java all casts are checked at run-time for correctness. Thats exactly what RTTI means: at run-time, the type of an object is identified. Comment
In this case, the RTTI cast is only partial: the Object is cast to a Shape, and not all the way to a Circle, Square, or Triangle. Thats because the only thing we know at this point is that the List is full of Shapes. At compile-time, this is enforced only by your own self-imposed rules, but at run-time the cast ensures it. Comment
Now polymorphism takes over and the exact method thats called for the Shape is determined by whether the reference is for a Circle, Square, or Triangle. And in general, this is how it should be; you want the bulk of your code to know as little as possible about specific types of objects, and to just deal with the general representation of a family of objects (in this case, Shape). As a result, your code will be easier to write, read, and maintain, and your designs will be easier to implement, understand, and change. So polymorphism is the general goal in object-oriented programming. Comment
But what if you have a special programming problem thats easiest to solve if you know the exact type of a generic reference? For example, suppose you want to allow your users to highlight all the shapes of any particular type by turning them purple. This way, they can find all the triangles on the screen by highlighting them. Or perhaps your method needs to rotate a list of shapes, but it makes no sense to rotate a circle so youd like to skip only the circle objects. This is what RTTI accomplishes: you can ask a Shape reference the exact type that its referring to with RTTI you can select and isolate special cases. Comment
To understand how RTTI works in Java, you must first know how type information is represented at run-time. This is accomplished through a special kind of object called the Class object, which contains information about the class. (This is sometimes called a meta-class.) In fact, the Class object is used to create all of the regular objects of your class. Comment
Theres a Class object for each class that is part of your program. That is, each time you write and compile a new class, a single Class object is also created (and stored, appropriately enough, in an identically named .class file). At run-time, when you want to make an object of that class, the Java Virtual Machine (JVM) thats executing your program first checks to see if the Class object for that type is loaded. If not, the JVM loads it by finding the .class file with that name. Thus, a Java program isnt completely loaded before it begins, which is different from many traditional languages. Comment
Once the Class object for that type is in memory, it is used to create all objects of that type. Comment
If this seems shadowy or if you dont really believe it, heres a demonstration program to prove it:
//: c12:SweetShop.java // Examination of the way the class loader works. import com.bruceeckel.simpletest.*; class Candy { static { System.out.println("Loading Candy"); } } class Gum { static { System.out.println("Loading Gum"); } } class Cookie { static { System.out.println("Loading Cookie"); } } public class SweetShop { static Test monitor = new Test(); public static void main(String[] args) { System.out.println("inside main"); new Candy(); System.out.println("After creating Candy"); try { Class.forName("Gum"); } catch(ClassNotFoundException e) { e.printStackTrace(System.err); } System.out.println( "After Class.forName(\"Gum\")"); new Cookie(); System.out.println("After creating Cookie"); monitor.expect(new String[] { "inside main", "Loading Candy", "After creating Candy", "Loading Gum", "After Class.forName(" + '"' + "Gum" + '"' + ")", "Loading Cookie", "After creating Cookie" }); } } ///:~
Each of the classes Candy, Gum, and Cookie have a static clause that is executed as the class is loaded for the first time. Information will be printed to tell you when loading occurs for that class. In main( ), the object creations are spread out between print statements to help detect the time of loading. Comment
A particularly interesting line is:
Class.forName("Gum");
This method is a static member of Class (to which all Class objects belong). A Class object is like any other object and so you can get and manipulate a reference to it. (Thats what the loader does.) One of the ways to get a reference to the Class object is forName( ), which takes a String containing the textual name (watch the spelling and capitalization!) of the particular class you want a reference for. It returns a Class reference. Comment
The output of this program for one JVM is:
inside main Loading Candy After creating Candy Loading Gum After Class.forName("Gum") Loading Cookie After creating Cookie
You can see that each Class object is loaded only when its needed, and the static initialization is performed upon class loading. Comment
Java provides a second way to produce the reference to the Class object, using a class literal. In the above program this would look like:
Gum.class;
which is not only simpler, but also safer since its checked at compile-time. Because it eliminates the method call, its also more efficient. Comment
Class literals work with regular classes as well as interfaces, arrays, and primitive types. In addition, theres a standard field called TYPE that exists for each of the primitive wrapper classes. The TYPE field produces a reference to the Class object for the associated primitive type, such that:
... is equivalent to ... |
|
---|---|
boolean.class |
Boolean.TYPE |
char.class |
Character.TYPE |
byte.class |
Byte.TYPE |
short.class |
Short.TYPE |
int.class |
Integer.TYPE |
long.class |
Long.TYPE |
float.class |
Float.TYPE |
double.class |
Double.TYPE |
void.class |
Void.TYPE |
My preference is to use the .class versions if you can, since theyre more consistent with regular classes. Comment
So far, youve seen RTTI forms including:
In C++, the classic cast (Shape) does not perform RTTI. It simply tells the compiler to treat the object as the new type. In Java, which does perform the type check, this cast is often called a type safe downcast. The reason for the term downcast is the historical arrangement of the class hierarchy diagram. If casting a Circle to a Shape is an upcast, then casting a Shape to a Circle is a downcast. However, you know a Circle is also a Shape, and the compiler freely allows an upcast assignment, but you dont know that a Shape is necessarily a Circle, so the compiler doesnt allow you to perform a downcast assignment without using an explicit cast. Comment
Theres a third form of RTTI in Java. This is the keyword instanceof that tells you if an object is an instance of a particular type. It returns a boolean so you use it in the form of a question, like this:
if(x instanceof Dog) ((Dog)x).bark();
The above if statement checks to see if the object x belongs to the class Dog before casting x to a Dog. Its important to use instanceof before a downcast when you dont have other information that tells you the type of the object; otherwise youll end up with a ClassCastException. Comment
Ordinarily, you might be hunting for one type (triangles to turn purple, for example), but you can easily tally all of the objects using instanceof. Suppose you have a family of Pet classes:
//: c12:Pets.java package c12; class Pet {} class Dog extends Pet {} class Pug extends Dog {} class Cat extends Pet {} class Rodent extends Pet {} class Gerbil extends Rodent {} class Hamster extends Rodent {} class Counter { int i; } ///:~
The Counter class is used to keep track of the number of any particular type of Pet. You could think of it as an Integer that can be modified. Comment
Using instanceof, all the pets can be counted:
//: c12:PetCount.java // Using instanceof. package c12; import com.bruceeckel.simpletest.*; import java.util.*; public class PetCount { static Test monitor = new Test(); static String[] typenames = { "Pet", "Dog", "Pug", "Cat", "Rodent", "Gerbil", "Hamster", }; // Exceptions thrown out to console: public static void main(String[] args) throws Exception { ArrayList pets = new ArrayList(); try { Class[] petTypes = { Class.forName("c12.Dog"), Class.forName("c12.Pug"), Class.forName("c12.Cat"), Class.forName("c12.Rodent"), Class.forName("c12.Gerbil"), Class.forName("c12.Hamster"), }; for(int i = 0; i < 15; i++) pets.add( petTypes[ (int)(Math.random()*petTypes.length)] .newInstance()); } catch(InstantiationException e) { System.err.println("Cannot instantiate"); throw e; } catch(IllegalAccessException e) { System.err.println("Cannot access"); throw e; } catch(ClassNotFoundException e) { System.err.println("Cannot find class"); throw e; } HashMap h = new HashMap(); for(int i = 0; i < typenames.length; i++) h.put(typenames[i], new Counter()); for(int i = 0; i < pets.size(); i++) { Object o = pets.get(i); if(o instanceof Pet) ((Counter)h.get("Pet")).i++; if(o instanceof Dog) ((Counter)h.get("Dog")).i++; if(o instanceof Pug) ((Counter)h.get("Pug")).i++; if(o instanceof Cat) ((Counter)h.get("Cat")).i++; if(o instanceof Rodent) ((Counter)h.get("Rodent")).i++; if(o instanceof Gerbil) ((Counter)h.get("Gerbil")).i++; if(o instanceof Hamster) ((Counter)h.get("Hamster")).i++; } for(int i = 0; i < pets.size(); i++) System.out.println(pets.get(i).getClass()); for(int i = 0; i < typenames.length; i++) System.out.println( typenames[i] + " quantity: " + ((Counter)h.get(typenames[i])).i); monitor.expect(new Object[] { new TestExpression("%%class c12\\.(Dog|Pug|Cat|" + "Rodent|Gerbil|Hamster)", pets.size()), new TestExpression("%%(Pet|Dog|Pug|Cat|Rodent|" + "Gerbil|Hamster) quantity: \\d{1," + String.valueOf(pets.size()).length() + "}", typenames.length) }); } } ///:~
Theres a rather narrow restriction on instanceof: you can compare it to a named type only, and not to a Class object. In the example above you might feel that its tedious to write out all of those instanceof expressions, and youre right. But there is no way to cleverly automate instanceof by creating an ArrayList of Class objects and comparing it to those instead (stay tunedyoull see an alternative). This isnt as great a restriction as you might think, because youll eventually understand that your design is probably flawed if you end up writing a lot of instanceof expressions. Comment
Of course this example is contrivedyoud probably put a static data member in each type and increment it in the constructor to keep track of the counts. You would do something like that if you had control of the source code for the class and could change it. Since this is not always the case, RTTI can come in handy. Comment
Its interesting to see how the PetCount.java example can be rewritten using class literals. The result is cleaner in many ways:
//: c12:PetCount2.java // Using class literals. package c12; import com.bruceeckel.simpletest.*; import java.util.*; public class PetCount2 { static Test monitor = new Test(); public static void main(String[] args) throws Exception { ArrayList pets = new ArrayList(); Class[] petTypes = { // Class literals: Pet.class, Dog.class, Pug.class, Cat.class, Rodent.class, Gerbil.class, Hamster.class, }; try { for(int i = 0; i < 15; i++) { // Offset by one to eliminate Pet.class: int rnd = 1 + (int)( Math.random() * (petTypes.length - 1)); pets.add( petTypes[rnd].newInstance()); } } catch(InstantiationException e) { System.err.println("Cannot instantiate"); throw e; } catch(IllegalAccessException e) { System.err.println("Cannot access"); throw e; } HashMap h = new HashMap(); for(int i = 0; i < petTypes.length; i++) h.put(petTypes[i].toString(), new Counter()); for(int i = 0; i < pets.size(); i++) { Object o = pets.get(i); if(o instanceof Pet) ((Counter)h.get("class c12.Pet")).i++; if(o instanceof Dog) ((Counter)h.get("class c12.Dog")).i++; if(o instanceof Pug) ((Counter)h.get("class c12.Pug")).i++; if(o instanceof Cat) ((Counter)h.get("class c12.Cat")).i++; if(o instanceof Rodent) ((Counter)h.get("class c12.Rodent")).i++; if(o instanceof Gerbil) ((Counter)h.get("class c12.Gerbil")).i++; if(o instanceof Hamster) ((Counter)h.get("class c12.Hamster")).i++; } for(int i = 0; i < pets.size(); i++) System.out.println(pets.get(i).getClass()); Iterator keys = h.keySet().iterator(); while(keys.hasNext()) { String nm = (String)keys.next(); Counter cnt = (Counter)h.get(nm); System.out.println( nm.substring(nm.lastIndexOf('.') + 1) + " quantity: " + cnt.i); } monitor.expect(new Object[] { new TestExpression("%%class c12\\.(Dog|Pug|Cat|" + "Rodent|Gerbil|Hamster)", pets.size()), new TestExpression("%%(Pet|Dog|Pug|Cat|Rodent|" + "Gerbil|Hamster) quantity: \\d{1," + String.valueOf(pets.size()).length() + "}", petTypes.length) }); } } ///:~
Here, the typenames array has been removed in favor of getting the type name strings from the Class object. Notice that the system can distinguish between classes and interfaces. Comment
You can also see that the creation of petTypes does not need to be surrounded by a try block since its evaluated at compile-time and thus wont throw any exceptions, unlike Class.forName( ). Comment
When the Pet objects are dynamically created, you can see that the random number is restricted so it is between one and petTypes.length and does not include zero. Thats because zero refers to Pet.class, and presumably a generic Pet object is not interesting. However, since Pet.class is part of petTypes the result is that all of the pets get counted. Comment
The Class isInstance method provides a way to dynamically call the instanceof operator. Thus, all those tedious instanceof statements can be removed in the PetCount example:
//: c12:PetCount3.java // Using isInstance(). package c12; import com.bruceeckel.simpletest.*; import java.util.*; public class PetCount3 { static Test monitor = new Test(); public static void main(String[] args) throws Exception { ArrayList pets = new ArrayList(); Class[] petTypes = { Pet.class, Dog.class, Pug.class, Cat.class, Rodent.class, Gerbil.class, Hamster.class, }; try { for(int i = 0; i < 15; i++) { // Offset by one to eliminate Pet.class: int rnd = 1 + (int)( Math.random() * (petTypes.length - 1)); pets.add( petTypes[rnd].newInstance()); } } catch(InstantiationException e) { System.err.println("Cannot instantiate"); throw e; } catch(IllegalAccessException e) { System.err.println("Cannot access"); throw e; } HashMap h = new HashMap(); for(int i = 0; i < petTypes.length; i++) h.put(petTypes[i].toString(), new Counter()); for(int i = 0; i < pets.size(); i++) { Object o = pets.get(i); // Using isInstance to eliminate individual // instanceof expressions: for (int j = 0; j < petTypes.length; ++j) if (petTypes[j].isInstance(o)) { String key = petTypes[j].toString(); ((Counter)h.get(key)).i++; } } for(int i = 0; i < pets.size(); i++) System.out.println(pets.get(i).getClass()); Iterator keys = h.keySet().iterator(); while(keys.hasNext()) { String nm = (String)keys.next(); Counter cnt = (Counter)h.get(nm); System.out.println( nm.substring(nm.lastIndexOf('.') + 1) + " quantity: " + cnt.i); } monitor.expect(new Object[] { new TestExpression("%%class c12\\.(Dog|Pug|Cat|" + "Rodent|Gerbil|Hamster)", pets.size()), new TestExpression("%%(Pet|Dog|Pug|Cat|Rodent|" + "Gerbil|Hamster) quantity: \\d{1," + String.valueOf(pets.size()).length() + "}", petTypes.length) }); } } ///:~
You can see that the isInstance( ) method has eliminated the need for the instanceof expressions. In addition, this means that you can add new types of pets simply by changing the petTypes array; the rest of the program does not need modification (as it did when using the instanceof expressions). Comment
When querying for type information, theres an important difference between either form of instanceof (that is, instanceof or isInstance( ), which produce equivalent results) and the direct comparison of the Class objects. Heres an example that demonstrates the difference:
//: c12:FamilyVsExactType.java // The difference between instanceof and class package c12; import com.bruceeckel.simpletest.*; class Base {} class Derived extends Base {} public class FamilyVsExactType { static Test monitor = new Test(); static void test(Object x) { System.out.println("Testing x of type " + x.getClass()); System.out.println("x instanceof Base " + (x instanceof Base)); System.out.println("x instanceof Derived " + (x instanceof Derived)); System.out.println("Base.isInstance(x) " + Base.class.isInstance(x)); System.out.println("Derived.isInstance(x) " + Derived.class.isInstance(x)); System.out.println( "x.getClass() == Base.class " + (x.getClass() == Base.class)); System.out.println( "x.getClass() == Derived.class " + (x.getClass() == Derived.class)); System.out.println( "x.getClass().equals(Base.class)) " + (x.getClass().equals(Base.class))); System.out.println( "x.getClass().equals(Derived.class)) " + (x.getClass().equals(Derived.class))); } public static void main(String[] args) { test(new Base()); test(new Derived()); monitor.expect(new String[] { "Testing x of type class c12.Base", "x instanceof Base true", "x instanceof Derived false", "Base.isInstance(x) true", "Derived.isInstance(x) false", "x.getClass() == Base.class true", "x.getClass() == Derived.class false", "x.getClass().equals(Base.class)) true", "x.getClass().equals(Derived.class)) false", "Testing x of type class c12.Derived", "x instanceof Base true", "x instanceof Derived true", "Base.isInstance(x) true", "Derived.isInstance(x) true", "x.getClass() == Base.class false", "x.getClass() == Derived.class true", "x.getClass().equals(Base.class)) false", "x.getClass().equals(Derived.class)) true" }); } } ///:~
The test( ) method performs type checking with its argument using both forms of instanceof. It then gets the Class reference and uses == and equals( ) to test for equality of the Class objects. Here is the output:
Testing x of type class Base x instanceof Base true x instanceof Derived false Base.isInstance(x) true Derived.isInstance(x) false x.getClass() == Base.class true x.getClass() == Derived.class false x.getClass().equals(Base.class)) true x.getClass().equals(Derived.class)) false Testing x of type class Derived x instanceof Base true x instanceof Derived true Base.isInstance(x) true Derived.isInstance(x) true x.getClass() == Base.class false x.getClass() == Derived.class true x.getClass().equals(Base.class)) false x.getClass().equals(Derived.class)) true
Reassuringly, instanceof and isInstance( ) produce exactly the same results, as do equals( ) and ==. But the tests themselves draw different conclusions. In keeping with the concept of type, instanceof says are you this class, or a class derived from this class? On the other hand, if you compare the actual Class objects using ==, there is no concern with inheritanceits either the exact type or it isnt. Comment
Java performs its RTTI using the Class object, even if youre doing something like a cast. The class Class also has a number of other ways you can use RTTI. Comment
First, you must get a reference to the appropriate Class object. One way to do this, as shown in the previous example, is to use a string and the Class.forName( ) method. This is convenient because you dont need an object of that type in order to get the Class reference. However, if you do already have an object of the type youre interested in, you can fetch the Class reference by calling a method thats part of the Object root class: getClass( ). This returns the Class reference representing the actual type of the object. Class has many interesting methods, demonstrated in the following example:
//: c12:ToyTest.java // Testing class Class. import com.bruceeckel.simpletest.*; interface HasBatteries {} interface Waterproof {} interface ShootsThings {} class Toy { // Comment out the following default // constructor to see // NoSuchMethodError from (*1*) Toy() {} Toy(int i) {} } class FancyToy extends Toy implements HasBatteries, Waterproof, ShootsThings { FancyToy() { super(1); } } public class ToyTest { static Test monitor = new Test(); public static void main(String[] args) throws Exception { Class c = null; try { c = Class.forName("FancyToy"); } catch(ClassNotFoundException e) { System.err.println("Can't find FancyToy"); throw e; } printInfo(c); Class[] faces = c.getInterfaces(); for(int i = 0; i < faces.length; i++) printInfo(faces[i]); Class cy = c.getSuperclass(); Object o = null; try { // Requires default constructor: o = cy.newInstance(); // (*1*) } catch(InstantiationException e) { System.err.println("Cannot instantiate"); throw e; } catch(IllegalAccessException e) { System.err.println("Cannot access"); throw e; } printInfo(o.getClass()); monitor.expect(new String[] { "Class name: FancyToy is interface? [false]", "Class name: HasBatteries is interface? [true]", "Class name: Waterproof is interface? [true]", "Class name: ShootsThings is interface? [true]", "Class name: Toy is interface? [false]" }); } static void printInfo(Class cc) { System.out.println( "Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]"); } } ///:~
You can see that class FancyToy is quite complicated, since it inherits from Toy and implements the interfaces of HasBatteries, Waterproof, and ShootsThings. In main( ), a Class reference is created and initialized to the FancyToy Class using forName( ) inside an appropriate try block. Comment
The Class.getInterfaces( ) method returns an array of Class objects representing the interfaces that are contained in the Class object of interest. Comment
If you have a Class object you can also ask it for its direct base class using getSuperclass( ). This, of course, returns a Class reference that you can further query. This means that, at run-time, you can discover an objects entire class hierarchy. Comment
The newInstance( ) method of Class can, at first, seem like just another way to clone( ) an object. However, you can create a new object with newInstance( ) without an existing object, as seen here, because there is no Toy objectonly cy, which is a reference to ys Class object. This is a way to implement a virtual constructor, which allows you to say I dont know exactly what type you are, but create yourself properly anyway. In the example above, cy is just a Class reference with no further type information known at compile-time. And when you create a new instance, you get back an Object reference. But that reference is pointing to a Toy object. Of course, before you can send any messages other than those accepted by Object, you have to investigate it a bit and do some casting. In addition, the class thats being created with newInstance( ) must have a default constructor. In the next section, youll see how to dynamically create objects of classes using any constructor, with the Java reflection API. Comment
The final method in the listing is printInfo( ), which takes a Class reference and gets its name with getName( ), and finds out whether its an interface with isInterface( ). Comment
The output from this program is:
Class name: FancyToy is interface? [false] Class name: HasBatteries is interface? [true] Class name: Waterproof is interface? [true] Class name: ShootsThings is interface? [true] Class name: Toy is interface? [false]
Thus, with the Class object you can find out just about everything you want to know about an object. Comment
If you dont know the precise type of an object, RTTI will tell you. However, theres a limitation: the type must be known at compile-time in order for you to be able to detect it using RTTI and do something useful with the information. Put another way, the compiler must know about all the classes youre working with for RTTI. Comment
This doesnt seem like that much of a limitation at first, but suppose youre given a reference to an object thats not in your program space. In fact, the class of the object isnt even available to your program at compile-time. For example, suppose you get a bunch of bytes from a disk file or from a network connection and youre told that those bytes represent a class. Since the compiler cant know about the class while its compiling the code, how can you possibly use such a class? Comment
In a traditional programming environment this seems like a far-fetched scenario. But as we move into a larger programming world there are important cases in which this happens. The first is component-based programming, in which you build projects using Rapid Application Development (RAD) in an application builder tool. This is a visual approach to creating a program (which you see on the screen as a form) by moving icons that represent components onto the form. These components are then configured by setting some of their values at program time. This design-time configuration requires that any component be instantiable, that it exposes parts of itself, and that it allows its values to be read and set. In addition, components that handle GUI events must expose information about appropriate methods so that the RAD environment can assist the programmer in overriding these event-handling methods. Reflection provides the mechanism to detect the available methods and produce the method names. Java provides a structure for component-based programming through JavaBeans (described in Chapter 13). Comment
Another compelling motivation for discovering class information at run-time is to provide the ability to create and execute objects on remote platforms across a network. This is called Remote Method Invocation (RMI) and it allows a Java program to have objects distributed across many machines. This distribution can happen for a number of reasons: for example, perhaps youre doing a computation-intensive task and you want to break it up and put pieces on machines that are idle in order to speed things up. In some situations you might want to place code that handles particular types of tasks (e.g., Business Rules in a multitier client/server architecture) on a particular machine, so that machine becomes a common repository describing those actions and it can be easily changed to affect everyone in the system. (This is an interesting development, since the machine exists solely to make software changes easy!) Along these lines, distributed computing also supports specialized hardware that might be good at a particular taskmatrix inversions, for examplebut inappropriate or too expensive for general purpose programming. Comment
The class Class (described previously in this chapter) supports the concept of reflection, and theres an additional library, java.lang.reflect, with classes Field, Method, and Constructor (each of which implement the Member interface). Objects of these types are created by the JVM at run-time to represent the corresponding member in the unknown class. You can then use the Constructors to create new objects, the get( ) and set( ) methods to read and modify the fields associated with Field objects, and the invoke( ) method to call a method associated with a Method object. In addition, you can call the convenience methods getFields( ), getMethods( ), getConstructors( ), etc., to return arrays of the objects representing the fields, methods, and constructors. (You can find out more by looking up the class Class in your online documentation.) Thus, the class information for anonymous objects can be completely determined at run-time, and nothing need be known at compile-time. Comment
Its important to realize that theres nothing magic about reflection. When youre using reflection to interact with an object of an unknown type, the JVM will simply look at the object and see that it belongs to a particular class (just like ordinary RTTI) but then, before it can do anything else, the Class object must be loaded. Thus, the .class file for that particular type must still be available to the JVM, either on the local machine or across the network. So the true difference between RTTI and reflection is that with RTTI, the compiler opens and examines the .class file at compile-time. Put another way, you can call all the methods of an object in the normal way. With reflection, the .class file is unavailable at compile-time; it is opened and examined by the run-time environment. Comment
Youll rarely need to use the reflection tools directly; theyre in the language to support other Java features, such as object serialization (Chapter 11), JavaBeans (Chapter 13), and RMI (Chapter 15). However, there are times when its quite useful to be able to dynamically extract information about a class. One extremely useful tool is a class method extractor. As mentioned before, looking at a class definition source code or online documentation shows only the methods that are defined or overridden within that class definition. But there could be dozens more available to you that have come from base classes. To locate these is both tedious and time consuming[62]. Fortunately, reflection provides a way to write a simple tool that will automatically show you the entire interface. Heres the way it works:
//: c12:ShowMethods.java // Using reflection to show all the methods of // a class, even if the methods are defined in // the base class. import com.bruceeckel.simpletest.*; import java.lang.reflect.*; public class ShowMethods { static Test monitor = new Test(); static final String usage = "usage: \n" + "ShowMethods qualified.class.name\n" + "To show all methods in class or: \n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; public static void main(String[] args) { if(args.length < 1) { System.out.println(usage); System.exit(0); } int lines = 0; try { Class c = Class.forName(args[0]); Method[] m = c.getMethods(); Constructor[] ctor = c.getConstructors(); if(args.length == 1) { for (int i = 0; i < m.length; i++) System.out.println(m[i]); for (int i = 0; i < ctor.length; i++) System.out.println(ctor[i]); lines = m.length + ctor.length; } else { for (int i = 0; i < m.length; i++) if(m[i].toString() .indexOf(args[1])!= -1) { System.out.println(m[i]); lines++; } for (int i = 0; i < ctor.length; i++) if(ctor[i].toString() .indexOf(args[1])!= -1) { System.out.println(ctor[i]); lines++; } } } catch(ClassNotFoundException e) { System.err.println("No such class: " + e); } monitor.expect(new Object[] { new TestExpression("%%public .+\\(.*\\).*", lines) }); } } ///:~
The Class methods getMethods( ) and getConstructors( ) return an array of Method and Constructor, respectively. Each of these classes has further methods to dissect the names, arguments, and return values of the methods they represent. But you can also just use toString( ), as is done here, to produce a String with the entire method signature. The rest of the code is just for extracting command line information, determining if a particular signature matches with your target string (using indexOf( )), and printing the results. Comment
This shows reflection in action, since the result produced by Class.forName( ) cannot be known at compile-time, and therefore all the method signature information is being extracted at run-time. If you investigate your online documentation on reflection, youll see that there is enough support to actually set up and make a method call on an object thats totally unknown at compile-time (there will be examples of this later in this book). Again, this is something you may never need to do yourselfthe support is there for RMI and so a programming environment can manipulate JavaBeansbut its interesting. Comment
An enlightening experiment is to run
java ShowMethods ShowMethods
This produces a listing that includes a public default constructor, even though you can see from the code that no constructor was defined. The constructor you see is the one thats automatically synthesized by the compiler. If you then make ShowMethods a non-public class (that is, friendly), the synthesized default constructor no longer shows up in the output. The synthesized default constructor is automatically given the same access as the class. Comment
The output for ShowMethods is still a little tedious. For example, heres a portion of the output produced by invoking java ShowMethods java.lang.String:
public boolean java.lang.String.startsWith(java.lang.String,int) public boolean java.lang.String.startsWith(java.lang.String) public boolean java.lang.String.endsWith(java.lang.String)
It would be even nicer if the qualifiers like java.lang could be stripped off. The StreamTokenizer class introduced in the previous chapter can help create a tool to solve this problem:
//: com:bruceeckel:util:StripQualifiers.java package com.bruceeckel.util; import java.io.*; public class StripQualifiers { private StreamTokenizer st; public StripQualifiers(String qualified) { st = new StreamTokenizer( new StringReader(qualified)); st.ordinaryChar(' '); // Keep the spaces } public String getNext() { String s = null; try { int token = st.nextToken(); if(token != StreamTokenizer.TT_EOF) { switch(st.ttype) { case StreamTokenizer.TT_EOL: s = null; break; case StreamTokenizer.TT_NUMBER: s = Double.toString(st.nval); break; case StreamTokenizer.TT_WORD: s = new String(st.sval); break; default: // single character in ttype s = String.valueOf((char)st.ttype); } } } catch(IOException e) { System.err.println("Error fetching token"); } return s; } public static String strip(String qualified) { StripQualifiers sq = new StripQualifiers(qualified); String s = "", si; while((si = sq.getNext()) != null) { int lastDot = si.lastIndexOf('.'); if(lastDot != -1) si = si.substring(lastDot + 1); s += si; } return s; } } ///:~
To facilitate reuse, this class is placed in com.bruceeckel.util. As you can see, this uses the StreamTokenizer and String manipulation to do its work. Comment
The new version of the program uses the above class to clean up the output:
//: c12:ShowMethodsClean.java // ShowMethods with the qualifiers stripped // to make the results easier to read. import com.bruceeckel.simpletest.*; import java.lang.reflect.*; import com.bruceeckel.util.*; public class ShowMethodsClean { static Test monitor = new Test(); static final String usage = "usage: \n" + "ShowMethodsClean qualified.class.name\n" + "To show all methods in class or: \n" + "ShowMethodsClean qualif.class.name word\n" + "To search for methods involving 'word'"; public static void main(String[] args) { if(args.length < 1) { System.out.println(usage); System.exit(0); } int lines = 0; try { Class c = Class.forName(args[0]); Method[] m = c.getMethods(); Constructor[] ctor = c.getConstructors(); // Convert to an array of cleaned Strings: String[] n = new String[m.length + ctor.length]; for(int i = 0; i < m.length; i++) { String s = m[i].toString(); n[i] = StripQualifiers.strip(s); } for(int i = 0; i < ctor.length; i++) { String s = ctor[i].toString(); n[i + m.length] = StripQualifiers.strip(s); } if(args.length == 1) { for (int i = 0; i < n.length; i++) System.out.println(n[i]); lines = n.length; } else for (int i = 0; i < n.length; i++) if(n[i].indexOf(args[1])!= -1) { System.out.println(n[i]); lines++; } } catch(ClassNotFoundException e) { System.err.println("No such class: " + e); } monitor.expect(new Object[] { new TestExpression("%%public .+\\(.*\\).*", lines) }); } } ///:~
The class ShowMethodsClean is quite similar to the previous ShowMethods, except that it takes the arrays of Method and Constructor and converts them into a single array of String. Each of these String objects is then passed through StripQualifiers.Strip( ) to remove all the method qualification. Comment
This tool can be a real time-saver while youre programming, when you cant remember if a class has a particular method and you dont want to go walking through the class hierarchy in the online documentation, or if you dont know whether that class can do anything with, for example, Color objects. Comment
Chapter 13 contains a GUI version of this program (customized to extract information for Swing components) so you can leave it running while youre writing code, to allow quick lookups. Comment
RTTI allows you to discover type information from an anonymous base-class reference. Thus, its ripe for misuse by the novice since it might make sense before polymorphic method calls do. For many people coming from a procedural background, its difficult not to organize their programs into sets of switch statements. They could accomplish this with RTTI and thus lose the important value of polymorphism in code development and maintenance. The intent of Java is that you use polymorphic method calls throughout your code, and you use RTTI only when you must. Comment
However, using polymorphic method calls as they are intended requires that you have control of the base-class definition because at some point in the extension of your program you might discover that the base class doesnt include the method you need. If the base class comes from a library or is otherwise controlled by someone else, a solution to the problem is RTTI: You can inherit a new type and add your extra method. Elsewhere in the code you can detect your particular type and call that special method. This doesnt destroy the polymorphism and extensibility of the program because adding a new type will not require you to hunt for switch statements in your program. However, when you add new code in your main body that requires your new feature, you must use RTTI to detect your particular type. Comment
Putting a feature in a base class might mean that, for the benefit of one particular class, all of the other classes derived from that base require some meaningless stub of a method. This makes the interface less clear and annoys those who must override abstract methods when they derive from that base class. For example, consider a class hierarchy representing musical instruments. Suppose you wanted to clear the spit valves of all the appropriate instruments in your orchestra. One option is to put a clearSpitValve( ) method in the base class Instrument, but this is confusing because it implies that Percussion and Electronic instruments also have spit valves. RTTI provides a much more reasonable solution in this case because you can place the method in the specific class (Wind in this case), where its appropriate. However, a more appropriate solution is to put a prepareInstrument( ) method in the base class, but you might not see this when youre first solving the problem and could mistakenly assume that you must use RTTI. Comment
Finally, RTTI will sometimes solve efficiency problems. If your code nicely uses polymorphism, but it turns out that one of your objects reacts to this general purpose code in a horribly inefficient way, you can pick out that type using RTTI and write case-specific code to improve the efficiency. Be wary, however, of programming for efficiency too soon. Its a seductive trap. Its best to get the program working first, then decide if its running fast enough, and only then should you attack efficiency issueswith a profiler. Comment
Solutions to selected exercises can be found in the electronic document The Thinking in Java Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
[62] Especially in the past. However, Sun has greatly improved its HTML Java documentation so that its easier to see base-class methods.