12: Run-time Type Identification
[ Viewing Hints ]
[ Revision History ]
[ Book Home Page ]
[ Free Newsletter ]
[ Seminars ]
[ Seminars on CD ROM ]
[ Consulting ]
Thinking in Java, 2nd edition, Revision 12
©2000 by Bruce Eckel
[ Previous Chapter ]
[ Short TOC ]
[ Table of Contents ]
[ Index ]
[ Next Chapter ]
12: Run-time Type Identification
The idea
of run-time type identification (RTTI) seems fairly simple at first: it lets you
find the exact type of an object when you only have a reference to the base
type.
However, the need for RTTI
uncovers a whole plethora of interesting (and often perplexing) OO design
issues, and raises fundamental questions of how you should structure your
programs.
[ Add Comment ]
This chapter looks at the ways that Java
allows you to discover information about objects and classes at run-time. This
takes two forms: “traditional” RTTI, which assumes that you have all
the types available at compile-time and run-time, and the
“reflection” mechanism, which allows you to discover class
information solely at run-time. The “traditional” RTTI will be
covered first, followed by a discussion of reflection.
[ Add Comment ]
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 references 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 reference.
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 reference. That’s
polymorphism.
[ Add Comment ]
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 reference in the rest of the program.
[ Add Comment ]
As a brief review of polymorphism and
upcasting, you might code the above example as
follows:
//: c12:Shapes.java
import java.util.*;
class Shape {
void draw() {
System.out.println(this + ".draw()");
}
}
class Circle extends Shape {
public String toString() { return "Circle"; }
}
class Square extends Shape {
public String toString() { return "Square"; }
}
class Triangle extends Shape {
public String toString() { return "Triangle"; }
}
public class Shapes {
public static void main(String[] args) {
ArrayList s = new ArrayList();
s.add(new Circle());
s.add(new Square());
s.add(new Triangle());
Iterator e = s.iterator();
while(e.hasNext())
((Shape)e.next()).draw();
}
} ///:~
The base class contains a
draw( ) method that indirectly uses toString( ) to print
an identifier for the class by passing this to
System.out.println( ). If that function sees an object, it
automatically calls the toString( ) method to produce a
String representation.
[ Add Comment ]
Each of the derived classes overrides the
toString( ) method (from Object) so that draw( )
ends up printing something different in each case. In main( ),
specific types of Shape are created and then added to an
ArrayList. This is the point at which the upcast occurs
because the ArrayList holds only Objects. Since
everything in Java (with the exception of primitives) is an Object,
an ArrayList 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 ArrayList, they are just Objects.
[ Add Comment ]
At the point you fetch an element out of
the ArrayList with next( ), things get a
little busy. Since the ArrayList holds only
Objects, next( ) naturally produces an Object
reference. But we know it’s really a Shape reference, 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.
[ Add Comment ]
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 ArrayList
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.
[ Add Comment ]
Now polymorphism takes over and the exact
method that’s called for the Shape is determined by whether the
reference 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.
[ Add Comment ]
But what if you have a special
programming problem that’s easiest to solve if you know the exact type of
a generic reference? 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 Shape reference the exact type that it’s referring
to.
[ Add Comment ]
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.
[ Add Comment ]
There’s a Class object for
each class that is part of your program. That is, each time you write and
compile 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.
[ Add Comment ]
Once the Class object for that
type is in memory, it is used to create all objects of that type.
[ Add Comment ]
If this seems shadowy or if you
don’t really believe it, here’s a demonstration program to prove
it:
//: c12: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.err);
}
System.out.println(
"After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
}
} ///:~
Each of the classes Candy,
Gum, and Cookie have a static
clause that is executed as the class is loaded for the first time. Information
will be printed 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.
[ Add Comment ]
A particularly interesting line
is:
Class.forName("Gum");
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 reference to it.
(That’s what the loader does.) One of the ways to get a reference 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 reference for. It returns a
Class reference.
[ Add Comment ]
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.
[ Add Comment ]
Class literals
Java provides a second way to produce the
reference to the Class object, using a
class literal. In the
above program this would look like:
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.
[ Add Comment ]
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 reference to
the Class object for the associated primitive type, such
that:
... 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
My preference is to use the
“.class” versions if you can, since they’re more
consistent with regular classes.
[ Add Comment ]
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 run-time information.
[ Add Comment ]
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.
[ Add Comment ]
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.
[ Add Comment ]
Ordinarily, you might be hunting for one
type (triangles to turn purple, for example), but you can easily tally
all of the objects using instanceof. Suppose you have a family of
Pet classes:
//: c12:Pets.java
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; } ///:~
The Counter class is used to keep
track of the number of any particular type of Pet. You could think of it
as an Integer that can be modified.
[ Add Comment ]
Using instanceof, all the pets can
be counted:
//: c12:PetCount.java
// Using instanceof.
import java.util.*;
public class PetCount {
static String[] typenames = {
"Pet", "Dog", "Pug", "Cat",
"Rodent", "Gerbil", "Hamster",
};
// Exceptions thrown out to console:
public static void main(String[] args)
throws Exception {
ArrayList pets = new ArrayList();
try {
Class[] petTypes = {
Class.forName("Dog"),
Class.forName("Pug"),
Class.forName("Cat"),
Class.forName("Rodent"),
Class.forName("Gerbil"),
Class.forName("Hamster"),
};
for(int i = 0; i < 15; i++)
pets.add(
petTypes[
(int)(Math.random()*petTypes.length)]
.newInstance());
} catch(InstantiationException e) {
System.err.println("Cannot instantiate");
throw e;
} catch(IllegalAccessException e) {
System.err.println("Cannot access");
throw e;
} catch(ClassNotFoundException e) {
System.err.println("Cannot find class");
throw e;
}
HashMap h = new HashMap();
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.get(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.get(i).getClass());
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: 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 there is no way to cleverly automate instanceof by creating an
ArrayList of Class objects and comparing it to those instead (stay
tuned—you’ll see an alternative). 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.
[ Add Comment ]
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.
[ Add Comment ]
Using class literals
It’s interesting to see how the
PetCount.java example can be rewritten using class
literals. The result is cleaner
in many ways:
//: c12:PetCount2.java
// Using class literals.
import java.util.*;
public class PetCount2 {
public static void main(String[] args)
throws Exception {
ArrayList pets = new ArrayList();
Class[] petTypes = {
// Class literals:
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.add(
petTypes[rnd].newInstance());
}
} catch(InstantiationException e) {
System.err.println("Cannot instantiate");
throw e;
} catch(IllegalAccessException e) {
System.err.println("Cannot access");
throw e;
}
HashMap h = new HashMap();
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.get(i);
if(o instanceof Pet)
((Counter)h.get("class Pet")).i++;
if(o instanceof Dog)
((Counter)h.get("class Dog")).i++;
if(o instanceof Pug)
((Counter)h.get("class Pug")).i++;
if(o instanceof Cat)
((Counter)h.get("class Cat")).i++;
if(o instanceof Rodent)
((Counter)h.get("class Rodent")).i++;
if(o instanceof Gerbil)
((Counter)h.get("class Gerbil")).i++;
if(o instanceof Hamster)
((Counter)h.get("class Hamster")).i++;
}
for(int i = 0; i < pets.size(); i++)
System.out.println(pets.get(i).getClass());
Iterator keys = h.keySet().iterator();
while(keys.hasNext()) {
String nm = (String)keys.next();
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 that the system can distinguish between classes and interfaces.
[ Add Comment ]
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( ).
[ Add Comment ]
When the Pet objects are
dynamically created, you can see that the random number is restricted so it is
between one 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.
[ Add Comment ]
A dynamic instanceof
The Class
isInstance
method provides a way to dynamically call the instanceof operator. Thus,
all those tedious instanceof statements can be removed in the
PetCount example:
//: c12:PetCount3.java
// Using isInstance().
import java.util.*;
public class PetCount3 {
public static void main(String[] args)
throws Exception {
ArrayList pets = new ArrayList();
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.add(
petTypes[rnd].newInstance());
}
} catch(InstantiationException e) {
System.err.println("Cannot instantiate");
throw e;
} catch(IllegalAccessException e) {
System.err.println("Cannot access");
throw e;
}
HashMap h = new HashMap();
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.get(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.get(i).getClass());
Iterator keys = h.keySet().iterator();
while(keys.hasNext()) {
String nm = (String)keys.next();
Counter cnt = (Counter)h.get(nm);
System.out.println(
nm.substring(nm.lastIndexOf('.') + 1) +
" quantity: " + cnt.i);
}
}
} ///:~
You can see that the
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).
[ Add Comment ]
instanceof vs. Class equivalence
When querying for type information,
there’s an important difference between either form of instanceof
(that is, instanceof or isInstance( ), which produce
equivalent results) and the direct comparison of the Class objects.
Here’s an example that demonstrates the difference:
//: c12:FamilyVsExactType.java
// The difference between instanceof and class
class Base {}
class Derived extends Base {}
public class FamilyVsExactType {
static void test(Object x) {
System.out.println("Testing x of type " +
x.getClass());
System.out.println("x instanceof Base " +
(x instanceof Base));
System.out.println("x instanceof Derived " +
(x instanceof Derived));
System.out.println("Base.isInstance(x) " +
Base.class.isInstance(x));
System.out.println("Derived.isInstance(x) " +
Derived.class.isInstance(x));
System.out.println(
"x.getClass() == Base.class " +
(x.getClass() == Base.class));
System.out.println(
"x.getClass() == Derived.class " +
(x.getClass() == Derived.class));
System.out.println(
"x.getClass().equals(Base.class)) " +
(x.getClass().equals(Base.class)));
System.out.println(
"x.getClass().equals(Derived.class)) " +
(x.getClass().equals(Derived.class)));
}
public static void main(String[] args) {
test(new Base());
test(new Derived());
}
} ///:~
The test( ) method performs
type checking with its argument using both forms of instanceof. It then
gets the Class reference and uses == and equals( ) to
test for equality of the Class objects. Here is the
output:
Testing x of type class Base
x instanceof Base true
x instanceof Derived false
Base.isInstance(x) true
Derived.isInstance(x) false
x.getClass() == Base.class true
x.getClass() == Derived.class false
x.getClass().equals(Base.class)) true
x.getClass().equals(Derived.class)) false
Testing x of type class Derived
x instanceof Base true
x instanceof Derived true
Base.isInstance(x) true
Derived.isInstance(x) true
x.getClass() == Base.class false
x.getClass() == Derived.class true
x.getClass().equals(Base.class)) false
x.getClass().equals(Derived.class)) true
Reassuringly, instanceof and
isInstance( ) produce exactly the same results, as do
equals( ) and ==. But the tests themselves draw different
conclusions. In keeping with the concept of type, instanceof says
“are you this class, or a class derived from this class?” On the
other hand, if you compare the actual Class objects using ==,
there is no concern with inheritance—it’s either the exact type or
it isn’t.
[ Add Comment ]
RTTI syntax
Java performs its
RTTI using the Class
object, even if you’re doing something like a cast. The class Class
also has a number of other ways you can use RTTI.
[ Add Comment ]
First, you must get a reference to the
appropriate Class object. One way to do this, as shown in the previous
example, is to use a string and the Class.forName( ) method. This is
convenient because you don’t need an object of that type in order to get
the Class reference. However, if you do already have an object of the
type you’re interested in, you can fetch the Class reference by
calling a method that’s part of the Object root class:
getClass( ). This
returns the Class reference representing the actual type of the object.
Class has many interesting methods, demonstrated in the following
example:
//: c12:ToyTest.java
// Testing class Class.
interface HasBatteries {}
interface Waterproof {}
interface ShootsThings {}
class Toy {
// Comment out the following default
// constructor to see
// NoSuchMethodError from (*1*)
Toy() {}
Toy(int i) {}
}
class FancyToy extends Toy
implements HasBatteries,
Waterproof, ShootsThings {
FancyToy() { super(1); }
}
public class ToyTest {
public static void main(String[] args)
throws Exception {
Class c = null;
try {
c = Class.forName("FancyToy");
} catch(ClassNotFoundException e) {
System.err.println("Can't find FancyToy");
throw e;
}
printInfo(c);
Class[] faces = c.getInterfaces();
for(int i = 0; i < faces.length; i++)
printInfo(faces[i]);
Class cy = c.getSuperclass();
Object o = null;
try {
// Requires default constructor:
o = cy.newInstance(); // (*1*)
} catch(InstantiationException e) {
System.err.println("Cannot instantiate");
throw e;
} catch(IllegalAccessException e) {
System.err.println("Cannot access");
throw e;
}
printInfo(o.getClass());
}
static void printInfo(Class cc) {
System.out.println(
"Class name: " + cc.getName() +
" is interface? [" +
cc.isInterface() + "]");
}
} ///:~
You can see that class FancyToy is
quite complicated, since it inherits from Toy and implements the
interfaces of HasBatteries, Waterproof, and
ShootsThings. In main( ), a Class reference is created
and initialized to the FancyToy Class using forName( )
inside an appropriate try block.
[ Add Comment ]
The Class.getInterfaces( )
method returns an array of
Class objects representing the interfaces that are contained in the
Class object of interest.
[ Add Comment ]
If you have a Class object you can
also ask it for its direct base class using
getSuperclass( ).
This, of course, returns a Class reference that you can further query.
This means that, at run-time, you can discover an object’s entire class
hierarchy.
[ Add Comment ]
The
newInstance( )
method of Class can, at first, seem like just another way to
clone( ) an object. However, you can create a new object with
newInstance( ) without an existing object, as seen here,
because there is no Toy object—only cy, which is a reference
to y’s Class object. This is a way to implement a
“virtual constructor,” which allows you to say “I don’t
know exactly what type you are, but create yourself properly anyway.” In
the example above, cy is just a Class reference with no further
type information known at compile-time. And when you create a new instance, you
get back an Object reference. But that reference is pointing to a
Toy object. Of course, before you can send any messages other than those
accepted by Object, you have to investigate it a bit and do some casting.
In addition, the class that’s being created with
newInstance( ) must have a default constructor. In the next section,
you’ll see how to dynamically create objects of classes using any
constructor, with the Java reflection API.
[ Add Comment ]
The final method in the listing is
printInfo( ), which
takes a Class reference and gets its name with
getName( ), and
finds out whether it’s an interface with
isInterface( ).
[ Add Comment ]
The output from this program
is:
Class name: FancyToy is interface? [false]
Class name: HasBatteries is interface? [true]
Class name: Waterproof is interface? [true]
Class name: ShootsThings is interface? [true]
Class name: Toy is interface? [false]
Thus, with the Class object you
can find out just about everything you want to know about an object.
[ Add Comment ]
Reflection: run-time class
information
If you don’t know the precise type
of an object, RTTI will tell you. However, there’s a limitation: the type
must be known at compile-time in order for you to be able to detect it using
RTTI and do something useful with the information. Put another way, the compiler
must know about all the classes you’re working with for RTTI.
[ Add Comment ]
This doesn’t seem like that much of
a limitation at first, but suppose you’re given a reference to an object
that’s not in your program space. In fact, the class of the object
isn’t even available to your program at compile-time. For example, suppose
you get a bunch of bytes from a disk file or from a network connection and
you’re told that those bytes represent a class. Since the compiler
can’t know about the class while it’s compiling the code, how can
you possibly use such a class?
[ Add Comment ]
In a traditional programming environment
this seems like a far-fetched scenario. But as we move into a larger programming
world there are important cases in which this happens. The first is
component-based programming, in which you build projects using
Rapid Application Development (RAD) in an
application builder tool. This is a visual approach to creating a program (which
you see on the screen as a “form”) by moving icons that represent
components onto the form. These components are then configured by setting some
of their values at program time. This design-time configuration requires that
any component be instantiable, that it exposes parts of itself, and that it
allows its values to be read and set. In addition, components that handle GUI
events must expose information about appropriate methods so that the RAD
environment can assist the programmer in overriding these event-handling
methods. Reflection provides the mechanism to detect the available methods and
produce the method names. Java provides a structure for
component-based programming through JavaBeans (described in Chapter 13).
[ Add Comment ]
Another compelling motivation for
discovering class information at run-time is to provide the ability to create
and execute objects on remote platforms across a network. This is called
Remote Method Invocation (RMI) and it allows a Java program to have
objects distributed across many machines. This distribution can happen for a
number of reasons: for example, perhaps you’re doing a
computation-intensive task and you want to break it up and put pieces on
machines that are idle in order to speed things up. In some situations you might
want to place code that handles particular types of tasks (e.g., “Business
Rules” in a multitier client/server architecture) on a particular machine,
so that machine becomes a common repository describing those actions and it can
be easily changed to affect everyone in the system. (This is an interesting
development, since the machine exists solely to make software changes easy!)
Along these lines, distributed computing also supports specialized hardware that
might be good at a particular task—matrix inversions, for
example—but inappropriate or too expensive for general purpose
programming.
[ Add Comment ]
The class Class (described
previously in this chapter) supports the concept of reflection, and
there’s an additional library, java.lang.reflect, with classes
Field,
Method, and
Constructor (each of
which implement the Member interface). Objects of these types are created
by the JVM at run-time to represent the corresponding member in the unknown
class. You can then use the Constructors to create new objects, the
get( ) and set( ) methods to read and modify the fields
associated with Field objects, and the invoke( ) method to
call a method associated with a Method object. In addition, you can call
the convenience methods getFields( ), getMethods( ),
getConstructors( ), etc., to return arrays of the objects
representing the fields, methods, and constructors. (You can find out more by
looking up the class Class in your online documentation.) Thus,
the class information for anonymous objects can be completely determined at
run-time, and nothing need be known at compile-time.
[ Add Comment ]
It’s important to realize that
there’s nothing magic about reflection. When you’re using reflection
to interact with an object of an unknown type, the JVM will simply look at the
object and see that it belongs to a particular class (just like ordinary RTTI)
but then, before it can do anything else, the Class object must be
loaded. Thus, the .class file for that particular type must still be
available to the JVM, either on the local machine or across the network. So the
true difference between RTTI and
reflection is that with RTTI, the compiler opens and examines the .class
file at compile-time. Put another way, you can call all the methods of an object
in the “normal” way. With reflection, the .class file is
unavailable at compile-time; it is opened and examined by the run-time
environment.
[ Add Comment ]
A class method extractor
You’ll rarely need to use the
reflection tools directly; they’re in the language to support other Java
features, such as object serialization (Chapter 11), JavaBeans (Chapter 13), and
RMI (Chapter 15). However, there are times when it’s quite useful to be
able to dynamically extract information about a class. One extremely useful tool
is a class method extractor. As mentioned before, looking at a class definition
source code or online documentation shows only the methods that are defined or
overridden within that class definition. But there could be dozens more
available to you that have come from base classes. To locate these is both
tedious and time
consuming[60].
Fortunately, reflection provides a way to write a simple tool that will
automatically show you the entire interface. Here’s the way it
works:
//: c12:ShowMethods.java
// Using reflection to show all the methods of
// a class, even if the methods are defined in
// the base class.
import java.lang.reflect.*;
public class ShowMethods {
static final String usage =
"usage: \n" +
"ShowMethods qualified.class.name\n" +
"To show all methods in class or: \n" +
"ShowMethods qualified.class.name word\n" +
"To search for methods involving 'word'";
public static void main(String[] args) {
if(args.length < 1) {
System.out.println(usage);
System.exit(0);
}
try {
Class c = Class.forName(args[0]);
Method[] m = c.getMethods();
Constructor[] ctor = c.getConstructors();
if(args.length == 1) {
for (int i = 0; i < m.length; i++)
System.out.println(m[i]);
for (int i = 0; i < ctor.length; i++)
System.out.println(ctor[i]);
} else {
for (int i = 0; i < m.length; i++)
if(m[i].toString()
.indexOf(args[1])!= -1)
System.out.println(m[i]);
for (int i = 0; i < ctor.length; i++)
if(ctor[i].toString()
.indexOf(args[1])!= -1)
System.out.println(ctor[i]);
}
} catch(ClassNotFoundException e) {
System.err.println("No such class: " + e);
}
}
} ///:~
The Class methods
getMethods( ) and
getConstructors( )
return an array of Method and Constructor, respectively. Each of
these classes has further methods to dissect the names, arguments, and return
values of the methods they represent. But you can also just use
toString( ), as is done here, to produce a String with the
entire method signature. The rest of the code is just for extracting command
line information, determining if a particular signature matches with your target
string (using
indexOf( )), and
printing the results.
[ Add Comment ]
This shows reflection in action, since
the result produced by Class.forName( ) cannot be known at
compile-time, and therefore all the method signature information is being
extracted at run-time. If you investigate your online documentation on
reflection, you’ll see that there is enough support to actually set up and
make a method call on an object that’s totally unknown at compile-time
(there will be examples of this later in this book). Again, this is something
you may never need to do yourself—the support is there for RMI and so a
programming environment can manipulate JavaBeans—but it’s
interesting.
[ Add Comment ]
An enlightening experiment is to run
java ShowMethods ShowMethods
This produces a listing that includes a
public default constructor, even though you can see from the code that no
constructor was defined. The constructor you see is the one that’s
automatically synthesized by the compiler. If you then make ShowMethods a
non-public class (that is, friendly), the synthesized default constructor
no longer shows up in the output. The
synthesized default constructor
is automatically given the same access as the class.
[ Add Comment ]
The output for ShowMethods is
still a little tedious. For example, here’s a portion of the output
produced by invoking java ShowMethods java.lang.String:
public boolean
java.lang.String.startsWith(java.lang.String,int)
public boolean
java.lang.String.startsWith(java.lang.String)
public boolean
java.lang.String.endsWith(java.lang.String)
It would be even nicer if the qualifiers
like java.lang could be stripped off. The
StreamTokenizer class
introduced in the previous chapter can help create a tool to solve this
problem:
//: com:bruceeckel:util:StripQualifiers.java
package com.bruceeckel.util;
import java.io.*;
public class StripQualifiers {
private StreamTokenizer st;
public StripQualifiers(String qualified) {
st = new StreamTokenizer(
new StringReader(qualified));
st.ordinaryChar(' '); // Keep the spaces
}
public String getNext() {
String s = null;
try {
int token = st.nextToken();
if(token != StreamTokenizer.TT_EOF) {
switch(st.ttype) {
case StreamTokenizer.TT_EOL:
s = null;
break;
case StreamTokenizer.TT_NUMBER:
s = Double.toString(st.nval);
break;
case StreamTokenizer.TT_WORD:
s = new String(st.sval);
break;
default: // single character in ttype
s = String.valueOf((char)st.ttype);
}
}
} catch(IOException e) {
System.err.println("Error fetching token");
}
return s;
}
public static String strip(String qualified) {
StripQualifiers sq =
new StripQualifiers(qualified);
String s = "", si;
while((si = sq.getNext()) != null) {
int lastDot = si.lastIndexOf('.');
if(lastDot != -1)
si = si.substring(lastDot + 1);
s += si;
}
return s;
}
} ///:~
To facilitate reuse, this class is placed
in com.bruceeckel.util. As you can see, this uses the
StreamTokenizer and String manipulation to do its work.
[ Add Comment ]
The new version of the program uses the
above class to clean up the output:
//: c12:ShowMethodsClean.java
// ShowMethods with the qualifiers stripped
// to make the results easier to read.
import java.lang.reflect.*;
import com.bruceeckel.util.*;
public class ShowMethodsClean {
static final String usage =
"usage: \n" +
"ShowMethodsClean qualified.class.name\n" +
"To show all methods in class or: \n" +
"ShowMethodsClean qualif.class.name word\n" +
"To search for methods involving 'word'";
public static void main(String[] args) {
if(args.length < 1) {
System.out.println(usage);
System.exit(0);
}
try {
Class c = Class.forName(args[0]);
Method[] m = c.getMethods();
Constructor[] ctor = c.getConstructors();
// Convert to an array of cleaned Strings:
String[] n =
new String[m.length + ctor.length];
for(int i = 0; i < m.length; i++) {
String s = m[i].toString();
n[i] = StripQualifiers.strip(s);
}
for(int i = 0; i < ctor.length; i++) {
String s = ctor[i].toString();
n[i + m.length] =
StripQualifiers.strip(s);
}
if(args.length == 1)
for (int i = 0; i < n.length; i++)
System.out.println(n[i]);
else
for (int i = 0; i < n.length; i++)
if(n[i].indexOf(args[1])!= -1)
System.out.println(n[i]);
} catch(ClassNotFoundException e) {
System.err.println("No such class: " + e);
}
}
} ///:~
The class ShowMethodsClean is
quite similar to the previous ShowMethods, except that it takes the
arrays of Method and Constructor and converts them into a single
array of String. Each of these String objects is then passed
through StripQualifiers.Strip( ) to remove all the method
qualification.
[ Add Comment ]
This tool can be a real time-saver while
you’re programming, when you can’t remember if a class has a
particular method and you don’t want to go walking through the class
hierarchy in the online documentation, or if you don’t know whether that
class can do anything with, for example, Color objects.
[ Add Comment ]
Chapter 13 contains a GUI version of this
program (customized to extract information for Swing components) so you can
leave it running while you’re writing code, to allow quick lookups.
[ Add Comment ]
Summary
RTTI allows you to discover type
information from an anonymous base-class reference. Thus, it’s ripe for
misuse by the novice since it might make sense before
polymorphic method calls do. For many people coming from a procedural
background, it’s difficult not to organize their programs into sets of
switch statements. They could accomplish this with RTTI and thus lose the
important value of polymorphism in code development and
maintenance. The intent of Java is that you use polymorphic method calls
throughout your code, and you use RTTI only when you must.
[ Add Comment ]
However, using polymorphic method calls
as they are intended requires that you have control of the base-class definition
because at some point in the extension of your program you might discover that
the base class doesn’t include the method you need. If the base class
comes from a library or is otherwise controlled by someone else, a solution to
the problem is RTTI: You can inherit a new type and add your extra method.
Elsewhere in the code you can detect your particular type and call that special
method. This doesn’t destroy the polymorphism and extensibility of the
program because adding a new type will not require you to hunt for switch
statements in your program. However, when you add new code in your main body
that requires your new feature, you must use RTTI to detect your particular
type.
[ Add Comment ]
Putting a feature in a base class might
mean that, for the benefit of one particular class, all of the other classes
derived from that base require some meaningless stub of a method. This makes the
interface less clear and annoys those who must override abstract methods when
they derive from that base class. For example, consider a class hierarchy
representing musical instruments. Suppose you wanted to clear the spit valves of
all the appropriate instruments in your orchestra. One option is to put a
clearSpitValve( ) method in the base class Instrument, but
this is confusing because it implies that Percussion and
Electronic instruments also have spit valves. RTTI provides a much more
reasonable solution in this case because you can place the method in the
specific class (Wind in this case), where it’s appropriate.
However, a more appropriate solution is to put a
prepareInstrument( ) method in the base class, but you might not see
this when you’re first solving the problem and could mistakenly assume
that you must use RTTI.
[ Add Comment ]
Finally, RTTI will sometimes solve
efficiency problems. If your code nicely uses polymorphism, but it turns out
that one of your objects reacts to this general purpose code in a horribly
inefficient way, you can pick out that type using RTTI and write case-specific
code to improve the efficiency. Be wary, however, of programming for efficiency
too soon. It’s a seductive trap. It’s best to get the program
working first, then decide if it’s running fast enough, and only
then should you attack efficiency issues—with a profiler.
[ Add Comment ]
Exercises
Solutions to selected exercises
can be found in the electronic document The Thinking in Java Annotated
Solution Guide, available for a small fee from
www.BruceEckel.com.
Add Rhomboid to
Shapes.java. Create a Rhomboid, upcast it to a Shape, then
downcast it back to a Rhomboid. Try downcasting to a Circle and
see what happens.
[ Add Comment ]
Modify
Exercise 1 so that it uses instanceof to check the type before performing
the downcast.
[ Add Comment ]
Modify
Shapes.java so that it can “highlight” (set a flag) in all
shapes of a particular type. The toString( ) method for each derived
Shape should indicate whether that Shape is
“highlighted.”
[ Add Comment ]
Modify
SweetShop.java so that each type of object creation is controlled by a
command-line argument. That is, if your command line is “java SweetShop
Candy,” then only the Candy object is created. Notice how you
can control which Class objects are loaded via the command-line argument.
[ Add Comment ]
Add
a new type of Pet to PetCount3.java. Verify that it is created and
counted correctly in main( ).
[ Add Comment ]
Write
a method that takes an object and recursively prints all the classes in that
object’s hierarchy.
[ Add Comment ]
Modify
Exercise 6 so that it uses
Class.getDeclaredFields( )
to also display information about the fields in a class.
[ Add Comment ]
In
ToyTest.java, comment out Toy’s default constructor and
explain what happens.
[ Add Comment ]
Incorporate
a new kind of interface into ToyTest.java and verify that it is
detected and displayed properly.
[ Add Comment ]
Create
a new type of container that uses a private ArrayList to hold the
objects. Capture the type of the first object you put in it, and then
allow the user to insert objects of only that type from then on.
[ Add Comment ]
Write
a program to determine whether an array of char is a primitive type or a
true object.
[ Add Comment ]
Implement
clearSpitValve( ) as described in the summary.
[ Add Comment ]
Implement
the rotate(Shape) method described in this chapter, such that it checks
to see if it is rotating a Circle (and, if so, doesn’t perform the
operation).
[ Add Comment ]
Modify
Exercise 6 so that it uses reflection instead of RTTI.
[ Add Comment ]
Modify
Exercise 7 so that it uses reflection instead of RTTI.
[ Add Comment ]
In
ToyTest.java, use reflection to create a Toy object using the
nondefault constructor.
[ Add Comment ]
Look
up the interface for java.lang.Class in the HTML Java documentation from
java.sun.com. Write a program that takes the name of a class as a
command-line argument, then uses the Class methods to dump all the
information available for that class. Test your program with a standard library
class and a class you create.
[ Add Comment ]
[60]
Especially in the past. However, Sun has greatly improved its HTML Java
documentation so that it’s easier to see base-class
methods.
[ Previous Chapter ]
[ Short TOC ]
[ Table of Contents ]
[ Index ]
[ Next Chapter ]
Last Update:05/21/2001
Wyszukiwarka
Podobne podstrony:
chap12CHAP12CHAP12chap12CHAP12chap12bb5 chap12chap12więcej podobnych podstron