The
need for RTTI
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: This
is a typical class hierarchy diagram, with the base class at the top and the
derived classes growing downward. The normal goal in object-oriented programming
is for the bulk of your code to manipulate handles to the base type (
Shape,
in this case), so if you decide to extend the program by adding a new class (
Rhomboid,
derived from
Shape,
for example), the bulk of the code is not affected. In this example, the
dynamically bound method in the
Shape
interface is
draw( ),
so the intent is for the client programmer to call
draw( )
through a generic
Shape
handle.
draw( )
is overridden in all of the derived classes, and because it is a dynamically
bound method, the proper behavior will occur even though it is called through a
generic
Shape
handle. That’s polymorphism. 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.
As
a brief review of polymorphism and upcasting,
you might code the above example as follows: (See page
97
if you have trouble executing this 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.
But
what if you have a special programming problem that’s easiest to solve if
you know the exact type of a generic handle?
For example, suppose you want to allow your users to highlight all the shapes
of any particular type by turning them purple. This way, they can find all the
triangles on the screen by highlighting them. This is what RTTI accomplishes:
you can ask a handle to a
Shape
exactly what type it’s referring to.
The
Class object
To
understand how RTTI works in Java, you must first know how type information is
represented at run time. This is accomplished through a special kind of object
called the Class
object,
which contains information about the class. (This is sometimes called a meta-class.)
In fact, the
Class
object is used to create all of the “regular” objects of your class.
There’s
a
Class
object for each class that is part of your program. That is, each time you
write a new class, a single
Class
object is also created (and stored, appropriately enough, in an identically
named
.class
file).
At run time, when you want to make an object of that class, the Java
Virtual Machine (JVM) that’s executing your program first checks to see
if the
Class
object for that type is loaded. If not, the JVM loads it by finding the
.class
file
with that name. Thus, a Java program isn’t completely loaded before it
begins, which is different from many traditional languages.
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");
}
} ///:~
Each
of the classes
Candy,
Gum,
and
Cookie
has a static
clause that is executed as the class is loaded for the first time. Information
will be printed out to tell you when loading occurs for that class. In
main( ),
the object creations are spread out between print statements to help detect the
time of loading.
A
particularly interesting line is:
This
method is a
static
member of
Class
(to which all
Class
objects belong). A
Class
object is like any other object and so you can get and manipulate a handle to
it. (That’s what the loader does.) One of the ways to get a handle to the
Class
object is forName( ),
which takes a
String
containing the textual name (watch the spelling and capitalization!) of the
particular class you want a handle for. It returns a
Class
handle.
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
In
Java 1.1
you have a second way to produce the handle to the
Class
object: use the class
literal
.
In the above program this would look like:
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.
Class
literals work with regular classes as well as interfaces, arrays, and primitive
types. In addition, there’s a standard field called TYPE
that exists for each of the primitive wrapper classes. The
TYPE
field produces a handle to the
Class
object for the associated primitive type, such that:
Checking
before a cast
So
far, you’ve seen RTTI forms including:
- 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.
- The
Class
object representing the type of your object. The
Class
object can be queried for useful runtime information.
In
C++, the classic cast “
(Shape)”
does
not
perform
RTTI. It simply tells the compiler to treat the object as the new type. In
Java, which does perform the type check, this cast is often called a
“type safe downcast.”
The reason for the term “downcast” is the historical arrangement of
the class hierarchy diagram. If casting a
Circle
to a
Shape
is an upcast, then casting a
Shape
to a
Circle
is a downcast. However, you know a
Circle
is also a
Shape,
and the compiler freely allows an upcast assignment, but you
don’t
know that a
Shape
is necessarily a
Circle,
so the compiler doesn’t allow you to perform a downcast
assignment without using an explicit cast.
There’s
a third form of RTTI in Java. This is the keyword
instanceof
that tells you if an object is an instance of a particular type. It returns a
boolean
so
you use it in the form of a question, like this:
if(x instanceof Dog)
((Dog)x).bark();
The
above
if
statement checks to see if the object
x
belongs to the class
Dog
before
casting
x
to a
Dog.
It’s important to use
instanceof
before a downcast when you don’t have other information that tells you
the type of the object; otherwise you’ll end up with a ClassCastException. 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);
}
} ///:~
There’s
a rather narrow restriction on
instanceof
in
Java 1.0:
You can compare it to a named type only, and not to a
Class
object. In the example above you might feel that it’s tedious to write
out all of those
instanceof
expressions, and you’re right. But in Java 1.0 there is no way to
cleverly automate it by creating a
Vector
of
Class
objects and comparing it to those instead. This isn’t as great a
restriction as you might think, because you’ll eventually understand that
your design is probably flawed if you end up writing a lot of
instanceof
expressions.
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
It’s
interesting to see how the
PetCount.java
example can be rewritten using Java 1.1
class literals.
The result is cleaner in many ways:
//: 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
Java
1.1
has added the isInstance
method to the class
Class.
This allows you to dynamically call the
instanceof
operator, which you could do only statically in Java 1.0
(as previously shown). Thus, all those tedious
instanceof
statements can be removed in the
PetCount
example:
//: 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);
}
}
} ///:~
You
can see that the Java 1.1
isInstance( )
method has eliminated the need for the
instanceof
expressions. In addition, this means that you can add new types of pets simply
by changing the
petTypes
array; the rest of the program does not need modification (as it did when using
the
instanceof
expressions).