Bruce Eckel's Thinking in Java Contents | Prev | Next

RTTI considered harmful?

Various designs in this chapter attempt to remove RTTI, which might give you the impression that it’s “considered harmful” (the condemnation used for poor, ill-fated goto, which was thus never put into Java). This isn’t true; it is the misuse of RTTI that is the problem. The reason our designs removed RTTI is because the misapplication of that feature prevented extensibility, while the stated goal was to be able to add a new type to the system with as little impact on surrounding code as possible. Since RTTI is often misused by having it look for every single type in your system, it causes code to be non-extensible: when you add a new type, you have to go hunting for all the code in which RTTI is used, and if you miss any you won’t get help from the compiler.

However, RTTI doesn’t automatically create non-extensible code. Let’s revisit the trash recycler once more. This time, a new tool will be introduced, which I call a TypeMap. It contains a Hashtable that holds Vectors, but the interface is simple: you can add( ) a new object, and you can get( ) a Vector containing all the objects of a particular type. The keys for the contained Hashtable are the types in the associated Vector. The beauty of this design (suggested by Larry O’Brien) is that the TypeMap dynamically adds a new pair whenever it encounters a new type, so whenever you add a new type to the system (even if you add the new type at run-time), it adapts.

Our example will again build on the structure of the Trash types in package c16.Trash (and the Trash.dat file used there can be used here without change):

//: DynaTrash.java 
// Using a Hashtable of Vectors and RTTI
// to automatically sort trash into
// vectors. This solution, despite the
// use of RTTI, is extensible.
package c16.dynatrash;
import c16.trash.*;
import java.util.*;

// Generic TypeMap works in any situation:
class TypeMap {
  private Hashtable t = new Hashtable();
  public void add(Object o) {
    Class type = o.getClass();
    if(t.containsKey(type))
      ((Vector)t.get(type)).addElement(o);
    else {
      Vector v = new Vector();
      v.addElement(o);
      t.put(type,v);
    }
  }
  public Vector get(Class type) {
    return (Vector)t.get(type);
  }
  public Enumeration keys() { return t.keys(); }
  // Returns handle to adapter class to allow
  // callbacks from ParseTrash.fillBin():
  public Fillable filler() { 
    // Anonymous inner class:
    return new Fillable() {
      public void addTrash(Trash t) { add(t); }
    };
  }
}

public class DynaTrash {
  public static void main(String[] args) {
    TypeMap bin = new TypeMap();
    ParseTrash.fillBin("Trash.dat",bin.filler());
    Enumeration keys = bin.keys();
    while(keys.hasMoreElements())
      Trash.sumValue(
        bin.get((Class)keys.nextElement()));
  }
} ///:~ 

Although powerful, the definition for TypeMap is simple. It contains a Hashtable, and the add( ) method does most of the work. When you add( ) a new object, the handle for the Class object for that type is extracted. This is used as a key to determine whether a Vector holding objects of that type is already present in the Hashtable. If so, that Vector is extracted and the object is added to the Vector. If not, the Class object and a new Vector are added as a key-value pair.

You can get an Enumeration of all the Class objects from keys( ), and use each Class object to fetch the corresponding Vector with get( ). And that’s all there is to it.

The filler( ) method is interesting because it takes advantage of the design of ParseTrash.fillBin( ), which doesn’t just try to fill a Vector but instead anything that implements the Fillable interface with its addTrash( ) method. All filler( ) needs to do is to return a handle to an interface that implements Fillable, and then this handle can be used as an argument to fillBin( ) like this:

ParseTrash.fillBin("Trash.dat", bin.filler());

To produce this handle, an anonymous inner class (described in Chapter 7) is used. You never need a named class to implement Fillable, you just need a handle to an object of that class, thus this is an appropriate use of anonymous inner classes.

An interesting thing about this design is that even though it wasn’t created to handle the sorting, fillBin( ) is performing a sort every time it inserts a Trash object into bin.

Much of class DynaTrash should be familiar from the previous examples. This time, instead of placing the new Trash objects into a bin of type Vector, the bin is of type TypeMap, so when the trash is thrown into bin it’s immediately sorted by TypeMap’s internal sorting mechanism. Stepping through the TypeMap and operating on each individual Vector becomes a simple matter:

    Enumeration keys = bin.keys();
    while(keys.hasMoreElements())
      Trash.sumValue(
        bin.get((Class)keys.nextElement())); 

As you can see, adding a new type to the system won’t affect this code at all, nor the code in TypeMap. This is certainly the smallest solution to the problem, and arguably the most elegant as well. It does rely heavily on RTTI, but notice that each key-value pair in the Hashtable is looking for only one type. In addition, there’s no way you can “forget” to add the proper code to this system when you add a new type, since there isn’t any code you need to add.

Contents | Prev | Next