The need for RTTI

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

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:

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
handle
in the rest of the program.

//: Shapes.java
package c11;
import java.util.*;
 
interface Shape {
  void draw();
}
 
class Circle implements Shape {
  public void draw() {
    System.out.println("Circle.draw()");
  }
}
 
class Square implements Shape {
  public void draw() {
    System.out.println("Square.draw()");
  }
}
 
class Triangle implements Shape {
  public void draw() {
    System.out.println("Triangle.draw()");
  }
}
 
public class Shapes {
  public static void main(String[] args) {
    Vector s = new Vector();
    s.addElement(new Circle());
    s.addElement(new Square());
    s.addElement(new Triangle());
    Enumeration e = s.elements();
    while(e.hasMoreElements())
      ((Shape)e.nextElement()).draw();
  }
} ///:~ 

The
base class could be coded as an
interface,
an
abstract
class, or an ordinary class. Since
Shape
has
no concrete members (that is, members with definitions), and it’s not
intended that you ever create a plain
Shape
object, the most appropriate and flexible representation is an
interface.
It’s also cleaner because you don’t have all those
abstract
keywords lying about.

Each
of the derived classes overrides the base-class
draw
method so it behaves differently. In
main( ),
specific types of
Shape
are created and then added to a
Vector.
This is the point at which the upcast occurs because the
Vector
holds only
Objects.
Since everything in Java (with the exception of primitives) is an
Object,
a
Vector
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
Vector,
they are just
Objects.

At
the point you fetch an element out of the
Vector
with
nextElement( ),
things get a little busy. Since
Vector
holds only
Objects,
nextElement( )
naturally produces an
Object
handle. But we know it’s really a
Shape
handle, 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. That’s exactly what RTTI means: at run-time,
the type of an object is identified.

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.
That’s because the only thing we
know
at this point is that the
Vector
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.

Now
polymorphism takes over and the exact method that’s called for the
Shape
is determined by whether the handle 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.

The
Class object

Once
the
Class
object for that type is in memory, it is used to create all objects of that type.

If
this seems shadowy or if you don’t really believe it, here’s a
demonstration program to prove it:

//: SweetShop.java
// Examination of the way the class loader works
 
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 {
  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.out.println(
      "After Class.forName("Gum")");
    new Cookie();
    System.out.println("After creating Cookie");
  }
} ///:~ 

A
particularly interesting line is:

Class.forName("Gum");

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 it’s needed, and the
static
initialization is performed upon class loading.

Interestingly
enough, a different JVM yielded:

Loading Candy
Loading Cookie
inside main
After creating Candy
Loading Gum
After Class.forName("Gum")
After creating Cookie

It
appears that this JVM anticipated the need for
Candy
and
Cookie
by examining the code in
main( ),
but could not see
Gum
because it was created by a call to
forName( )
and not through a more typical call to
new.
While this JVM produces the desired effect because it does get the classes
loaded before they’re needed, it’s uncertain whether the behavior
shown is precisely correct.


Class
literals

Gum.class;

which
is not only simpler, but also safer since it’s checked at compile time.
Because it eliminates the method call, it’s also more efficient.


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

Checking
before a cast

So
far, you’ve seen RTTI forms including:

  1. The
    classic cast, e.g. “
    (Shape),
    which uses RTTI to make sure the cast is correct and throws a
    ClassCastException
    if you’ve performed a bad cast.
  2. The
    Class
    object representing the type of your object. The
    Class
    object can be queried for useful runtime information.
if(x instanceof Dog)
  ((Dog)x).bark();

Ordinarily,
you might be hunting for one type (triangles to turn purple, for example), but
the following program shows how to tally
all
of the objects using
instanceof.

//: PetCount.java
// Using instanceof
package c11.petcount;
import java.util.*;
 
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; }
 
public class PetCount {
  static String[] typenames = {
    "Pet", "Dog", "Pug", "Cat",
    "Rodent", "Gerbil", "Hamster",
  };
  public static void main(String[] args) {
    Vector pets = new Vector();
    try {
      Class[] petTypes = {
        Class.forName("c11.petcount.Dog"),
        Class.forName("c11.petcount.Pug"),
        Class.forName("c11.petcount.Cat"),
        Class.forName("c11.petcount.Rodent"),
        Class.forName("c11.petcount.Gerbil"),
        Class.forName("c11.petcount.Hamster"),
      };
      for(int i = 0; i < 15; i++)
        pets.addElement(
          petTypes[
            (int)(Math.random()*petTypes.length)]
            .newInstance());
    } catch(InstantiationException e) {}
      catch(IllegalAccessException e) {}
      catch(ClassNotFoundException e) {}
    Hashtable h = new Hashtable();
    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.elementAt(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.elementAt(i).getClass().toString());
    for(int i = 0; i < typenames.length; i++)
      System.out.println(
        typenames[i] + " quantity: " +
        ((Counter)h.get(typenames[i])).i);
  }
} ///:~ 
Of
course this example is contrived – you’d 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.


Using
class literals

//: PetCount2.java
// Using Java 1.1 class literals
package c11.petcount2;
import java.util.*;
 
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; }
 
public class PetCount2 {
  public static void main(String[] args) {
    Vector pets = new Vector();
    Class[] petTypes = {
      // Class literals work in Java 1.1+ only:
      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.addElement(
          petTypes[rnd].newInstance());
      }
    } catch(InstantiationException e) {}
      catch(IllegalAccessException e) {}
    Hashtable h = new Hashtable();
    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.elementAt(i);
      if(o instanceof Pet)
        ((Counter)h.get(
          "class c11.petcount2.Pet")).i++;
      if(o instanceof Dog)
        ((Counter)h.get(
          "class c11.petcount2.Dog")).i++;
      if(o instanceof Pug)
        ((Counter)h.get(
          "class c11.petcount2.Pug")).i++;
      if(o instanceof Cat)
        ((Counter)h.get(
          "class c11.petcount2.Cat")).i++;
      if(o instanceof Rodent)
        ((Counter)h.get(
          "class c11.petcount2.Rodent")).i++;
      if(o instanceof Gerbil)
        ((Counter)h.get(
          "class c11.petcount2.Gerbil")).i++;
      if(o instanceof Hamster)
        ((Counter)h.get(
          "class c11.petcount2.Hamster")).i++;
    }
    for(int i = 0; i < pets.size(); i++)
      System.out.println(
        pets.elementAt(i).getClass().toString());
    Enumeration keys = h.keys();
    while(keys.hasMoreElements()) {
      String nm = (String)keys.nextElement();
      Counter cnt = (Counter)h.get(nm);
      System.out.println(
        nm.substring(nm.lastIndexOf('.') + 1) +
        " quantity: " + cnt.i);
    }
  }
} ///:~ 

Here,
the
typenames
array has been removed in favor of getting the type name strings from the
Class
object. Notice the extra work for this: the class name is not, for example,
Gerbil,
but instead
c11.petcount2.Gerbil
since the package name is included. Notice also that the system can distinguish
between classes and interfaces.

You
can also see that the creation of
petTypes
does not need to be surrounded by a
try
block since it’s evaluated at compile time and thus won’t throw any
exceptions, unlike
Class.forName( ).

When
the
Pet
objects are dynamically created, you can see that the random number is
restricted so it is between 1 and
petTypes.length
and does not include zero. That’s 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.


A
dynamic instanceof

//: PetCount3.java
// Using Java 1.1 isInstance()
package c11.petcount3;
import java.util.*;
 
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; }
 
public class PetCount3 {
  public static void main(String[] args) {
    Vector pets = new Vector();
    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.addElement(
          petTypes[rnd].newInstance());
      }
    } catch(InstantiationException e) {}
      catch(IllegalAccessException e) {}
    Hashtable h = new Hashtable();
    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.elementAt(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.elementAt(i).getClass().toString());
    Enumeration keys = h.keys();
    while(keys.hasMoreElements()) {
      String nm = (String)keys.nextElement();
      Counter cnt = (Counter)h.get(nm);
      System.out.println(
        nm.substring(nm.lastIndexOf('.') + 1) +
        " quantity: " + cnt.i);
    }
  }
} ///:~ 

More by Author

Must Read