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:

//: 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.

The Class object

//: 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");
  }
} ///:~ 

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

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

//: 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);
  }
} ///:~ 

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);
    }
  }
} ///:~ 

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:

//: 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.

The Class object

//: 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");
  }
} ///:~ 

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

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

//: 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);
  }
} ///:~ 

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);
    }
  }
} ///:~ 



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: September 10, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild". This loop of continuous delivery and continuous feedback is …

  • Live Event Date: September 19, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT In response to the rising number of data breaches and the regulatory and legal impact that can occur as a result of these incidents, leading analysts at Forrester Research have developed five important design principles that will help security professionals reduce their attack surface and mitigate vulnerabilities. Check out this upcoming eSeminar and join Chris Sherman of Forrester Research to learn how to deal with the influx of new device …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds