RTTI considered harmful?

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

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());

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.

More by Author

Must Read