Recommended: Sing it, brah! 5 fabulous songs for developers
JW's Top 5
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 5 of 5
So how can you take advantage of java.lang.Class and reflection in your designs? In general, you should avoid using reflection in your designs. Reflection is intended for
special cases like JavaBeans builder tools, object serialization mechanisms, object inspectors, debuggers, and so on. Reflection
is useful in situations in which you have absolutely no idea what's coming. When a programmer drops a JavaBean into a bean
builder, for example, the builder hasn't a clue about the class of the bean. The builder must use reflection to introspect
the bean class to discover its class, its properties, its methods, and so on. Because the bean builder has no idea what's
coming, it must use reflection to figure out how to use the beans it is given.
One situation in which you may know what's coming, but will still want to use java.lang.Class and possibly the reflection API, is when you are creating an instance of a class loaded into a different name space. One
of the main reasons for using class loaders and dynamic extension is so your program doesn't have to know at compile time
all the classes and interfaces it will load and use at runtime. Although you should prefer designs in which you assume that
dynamically loaded objects will implement a known interface or descend from a known class, in order to instantiate an object
of the dynamically loaded class, you'll still have to use either Class.newInstance() or the newInstance() method of class java.lang.reflect.Constructor().
Whenever possible, you should design programs such that they know what's coming to a sufficient extent that objects can be
used without consulting instanceof or reflection. In cases where you need to ask an object, Can you do a particular thing for me?, you should choose instanceof over reflection to make the query.
To every rule, of course, there are exceptions, and this is also true of the rule that you should always strive to design code that knows what's coming. Occasionally, I have encountered a few creative uses of runtime class information that I felt didn't negatively impact flexibility and that made for a good design. In these situations, the code doesn't know what's coming, but, nevertheless, probably represents the best way to offer the services provided.
The most common examples of reasonably-designed Java classes that don't know what's coming are collection classes. Collection
classes accept references of type Object to add to their collection, so they can be used with any type of object. Since Java objects all share a common base class
(java.lang.Object), and since Java doesn't have templates like C++, accepting Object references in the add() method probably is the best way to design collection classes in Java.
For another example of a useful class that doesn't know what's coming, take a look at this ObjectSorterclass:
// In file rtci/ex4/ObjectSorter.java import java.util.*;
public class ObjectSorter {
private HashMap hm = new HashMap();
public void add(Object o) {
Class key = o.getClass();
if (hm.containsKey(key)) {
ArrayList value = (ArrayList) hm.get(key); value.add(o); } else {
ArrayList value = new ArrayList(); value.add(o); hm.put(key, value); } }
// Returns an iterator for all the Class objects public Iterator getClassIterator() { return (hm.keySet()).iterator(); }
public Collection getObjects(Class key) { return (Collection) hm.get(key); } }
This ObjectSorter class was inspired by a class originally designed by Larry O'Brien and ultimately presented by Bruce Eckel in the "Design
Patterns" chapter of his book, Thinking in Java. (My version of the class uses Java 2 collections and follows my own naming sensibilities.) Class ObjectSorter sorts objects passed to its add() method based on class, by placing objects into a HashMap using the objects' Class object as a key.
Here's an application that uses ObjectSorter:
// In file rtci/ex4/Example4.java import java.util.Iterator; import java.util.Collection;
class Example4 {
public static void main(String[] args) {
ObjectSorter os = new ObjectSorter();
os.add(new String("Hi")); os.add(new String("There")); os.add(new String("!")); os.add(new String("What's")); os.add(new String("Happening")); os.add(new String("?")); os.add(new Object()); os.add(new Object[3]); os.add(new Object[3]); os.add(new Object[3]); os.add(new Object[2]); os.add(new Object[2]); os.add(new Example4()); os.add(new Example4()); os.add(new Example4()); os.add(new Example4());
Iterator classIt = os.getClassIterator();
while (classIt.hasNext()) {
Class key = (Class) classIt.next();
Collection col = os.getObjects(key);
String className = key.getName(); int objectCount = col.size(); System.out.println(className + ": " + objectCount); } } }
The Example4 application prints:
[Ljava.lang.Object;: 5 java.lang.String: 6 java.lang.Object: 1 Example4: 4
Example4 demonstrates that the ObjectSorter is able to assign objects into bins by class. Note that all five one-dimensional arrays of Object share the same class, even though some are of different lengths. The length of an array does not affect its class -- just
its dimensionality and element type. (The string "[Ljava.lang.Object" is a type descriptor that means one-dimensional array of element type java.lang.Object.) Example4 itself uses runtime class information when it calls getName() on the Class object to print out the name.
To summarize the advice given in this article, here are four guidelines:
Try to keep the types of your variables as high up the inheritance hierarchy as possible.
This guideline encourages you to treat objects as generically as possible, which in turn encourages you to take advantage
of dynamic binding and polymorphism. When the type of a reference is a supertype of the actual class of the referenced object,
the JVM will use dynamic binding to locate the correct implementation of a method you invoke on that reference.
Prefer polymorphism and dynamic binding over instanceof and downcasting.
This guideline encourages you to program in the object-oriented way, letting polymorphism and dynamic binding work for you.
A telltale sign of code that is disobeying this fundamental rule of thumb is a series of if-else statements doing instanceof tests.
Prefer code that 'knows what's coming.'
This guideline counterbalances the above guidelines, stating that although you should make the type of your variables as far
up the inheritance hierarchy as possible, you shouldn't make them so far up that you can't use the object without downcasting.
You should give variables the type furthest up the inheritance hierarchy that still offers the methods you need to manipulate
the object. That very type is the type of object you know is coming.
Use instanceof to ask 'What can you do for me?' and downcasting to access the functionality.
When it comes time to ask the question, What can you do for me? prefer instanceof over reflection. You may know you have a reference to some subclass of Animal and may invoke methods declared in Animal on that object. But you still may want to also invoke playFetch() on the object if it is a Dog. Rather than using reflection to look for a playFetch() method, use instanceof to see if the object really is a Dog. Or perhaps better yet, use instanceof to see if the object implements the PlaysFetch interface.
Once you determine that an object is an instance of some class or interface in which you are interested, use downcasting --
not reflection -- to get at the interesting methods. For example, once you determine you have an Animal object that implements the PlaysFetch interface, don't use reflection to invoke playFetch() on the Animal reference. Instead, downcast the Animal reference to PlaysFetch, and invoke playFetch on the PlaysFetch reference.
In short, don't use reflection for mainstream designs. Use it only for things like bean builders, object serialization mechanisms, object inspectors and debuggers.
In next month's Design Techniques article, I'll talk about designing with type information.
I encourage your comments, criticisms, suggestions, flames -- all kinds of feedback -- about the material presented in this column. If you disagree with something, or have something to add, please let me know.
You can either participate in a discussion forum devoted to this material, enter a comment via the form at the bottom of the article, or e-mail me directly using the link provided in my bio below.
Read more about Core Java in JavaWorld's Core Java section.