Tải bản đầy đủ (.pdf) (119 trang)

Thinking in Java 3rd Edition phần 5 ppt

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (537.26 KB, 119 trang )


446 Thinking in Java www.BruceEckel.com
allows components to reliably communicate problems to client code.
Feedback

The goals for exception handling in Java are to simplify the creation of
large, reliable programs using less code than currently possible, and with
more confidence that your application doesn’t have an unhandled error.
Exceptions are not terribly difficult to learn, and are one of those features
that provide immediate and significant benefits to your project.
Feedback

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.

1. Create a class with a main( ) that throws an object of class
Exception inside a try block. Give the constructor for
Exception a String argument. Catch the exception inside a
catch clause and print the String argument. Add a finally clause
and print a message to prove you were there.
Feedback

2. Create your own exception class using the extends keyword.
Write a constructor for this class that takes a String argument
and stores it inside the object with a String reference. Write a
method that prints out the stored String. Create a try-catch
clause to exercise your new exception.
Feedback

3. Write a class with a method that throws an exception of the type


created in Exercise 2. Try compiling it without an exception
specification to see what the compiler says. Add the appropriate
exception specification. Try out your class and its exception inside
a try-catch clause.
Feedback

4. Define an object reference and initialize it to null. Try to call a
method through this reference. Now wrap the code in a try-catch
clause to catch the exception.
Feedback

5. Create a class with two methods, f( ) and g( ). In g( ), throw an
exception of a new type that you define. In f( ), call g( ), catch its
exception and, in the catch clause, throw a different exception (of
a second type that you define). Test your code in main( ).
Feedback


Chapter 9: Error Handling with Exceptions 447
6. Repeat the previous exercise, but inside the catch clause, wrap
g( )’s exception in a RuntimeException.
7. Create three new types of exceptions. Write a class with a method
that throws all three. In main( ), call the method but only use a
single catch clause that will catch all three types of exceptions.
Feedback

8. Write code to generate and catch an
ArrayIndexOutOfBoundsException.
Feedback


9. Create your own resumption-like behavior using a while loop that
repeats until an exception is no longer thrown.
Feedback

10. Create a three-level hierarchy of exceptions. Now create a base-
class A with a method that throws an exception at the base of your
hierarchy. Inherit B from A and override the method so it throws
an exception at level two of your hierarchy. Repeat by inheriting
class C from B. In main( ), create a C and upcast it to A, then call
the method.
Feedback

11. Demonstrate that a derived-class constructor cannot catch
exceptions thrown by its base-class constructor.
Feedback

12. Show that OnOffSwitch.java can fail by throwing a
RuntimeException inside the try block.
Feedback

13. Show that WithFinally.java doesn’t fail by throwing a
RuntimeException inside the try block.
Feedback

14. Modify Exercise 7 by adding a finally clause. Verify your finally
clause is executed, even if a NullPointerException is thrown.
Feedback

15. Create an example where you use a flag to control whether cleanup
code is called, as described in the second paragraph after the

heading “Constructors.”
Feedback

16. Modify StormyInning.java by adding an UmpireArgument
exception type, and methods that throw this exception. Test the
modified hierarchy.
Feedback


448 Thinking in Java www.BruceEckel.com
17. Remove the first catch clause in Human.java and verify that the
code still compiles and runs properly.
Feedback

18. Add a second level of exception loss to LostMessage.java so that
the HoHumException is itself replaced by a third exception.
Feedback

19. Add an appropriate set of exceptions to
c08:GreenhouseControls.java.
Feedback

20. Add an appropriate set of exceptions to c08:Sequence.java.
21. Change the file name string in MainException.java to name a
file that doesn’t exist. Run the program and note the result.

449
10: Detecting types
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.
Feedback

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.
Feedback

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:
Shape
draw()
Circle Square 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 your code to manipulate references to the
base type (Shape, in this case), so if you decide to extend the program by

450 Thinking in Java www.BruceEckel.com
adding a new class (such as Rhomboid, derived from Shape), 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.
Feedback

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.
Feedback

As a brief review of polymorphism and upcasting, you might code the
above example as follows:
//: c10:Shapes.java
import com.bruceeckel.simpletest.*;

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 {
private static Test monitor = new Test();
public static void main(String[] args) {
// Array of Object, not Shape:
Object[] shapeList = {
new Circle(),
new Square(),
new Triangle()

Chapter 10: Detecting Types 451
};
for(int i = 0; i < shapeList.length; i++)
((Shape)shapeList[i]).draw(); // Must cast
monitor.expect(new String[] {
"Circle.draw()",
"Square.draw()",
"Triangle.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 method sees an object, it automatically
calls the toString( ) method to produce a String representation. Each of
the derived classes overrides the toString( ) method (from Object) so

that draw( ) ends up (polymorphically) printing something different in
each case.
Feedback

In main( ), specific types of Shape are created and added to an array.
This array is a bit odd because it isn’t an array of Shape (although it
could be), but instead an array of the root class Object. The reason for
this is to start preparing you for Chapter 11, which presents tools called
collections (also called containers), whose sole job is to hold and manage
other objects for you. However, to be generally useful these collections
need to hold anything, therefore they hold Objects. So an array of Object
will demonstrate an important issue that you will encounter in the
Chapter 11 collections.
Feedback

In this example, the upcast occurs when the shape is placed in the array of
Objects. Since everything in Java (with the exception of primitives) is an
Object, an array of Objects can also hold Shape objects. But during the
upcast to Object, the fact is lost that the objects are Shapes. To the
array, they are just Objects.
Feedback

At the point that you fetch an element out of the array with the index
operator, things get a little busy. Since the array holds only Objects,
indexing 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

452 Thinking in Java www.BruceEckel.com

time for correctness. That’s exactly what RTTI means: at run time, the
type of an object is identified.
Feedback

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 array 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.
Feedback

Now polymorphism takes over and the exact code that’s executed 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 a general goal in object-oriented
programming.
Feedback

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. Or perhaps your method
needs to “rotate” a list of shapes, but it makes no sense to rotate a circle so
you’d like to skip only the circle objects. With RTTI, you can ask a Shape
reference the exact type that it’s referring to, and thus select and isolate

special cases.
Feedback

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. In fact, the Class object is used to create all of the
“regular” objects of your class.
Feedback

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

Chapter 10: Detecting Types 453
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.
Feedback

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:
Feedback

//: c10:SweetShop.java

// Examination of the way the class loader works.
import com.bruceeckel.simpletest.*;

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 {
private static Test monitor = new Test();
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) {

System.out.println("Couldn't find Gum");

454 Thinking in Java www.BruceEckel.com
}
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
monitor.expect(new String[] {
"inside main",
"Loading Candy",
"After creating Candy",
"Loading Gum",
"After Class.forName(\"Gum\")",
"Loading Cookie",
"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.
Feedback

You can see from the output that each Class object is loaded only when
it’s needed, and the static initialization is performed upon class loading.
Feedback


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, which is being ignored here—the call to forName( ) is
being made for its side effect, which is to load the class Gum if it isn’t
already loaded. In the process of loading, Gum’s static clause is
executed.
Feedback
In the above example, if Class.forName( ) fails because it can’t find the
class you’re trying to load, it will throw a ClassNotFoundException

Chapter 10: Detecting Types 455
(ideally, exception names tell you just about everything you need to know
about the problem). Here, we simply report the problem and move on, but
in more sophisticated programs you might try to fix the problem inside
the exception handler.
Feedback

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.
Feedback

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.
Feedback


456 Thinking in Java www.BruceEckel.com
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. This will throw 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 run time information.
Feedback

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.
Feedback

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.
Feedback

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:

//: c10:Pet.java
package c10;
public class Pet {} ///:~


Chapter 10: Detecting Types 457
//: c10:Dog.java
package c10;
public class Dog extends Pet {} ///:~

//: c10:Pug.java
package c10;
public class Pug extends Dog {} ///:~

//: c10:Cat.java
package c10;
public class Cat extends Pet {} ///:~

//: c10:Rodent.java
package c10;
public class Rodent extends Pet {} ///:~

//: c10:Gerbil.java
package c10;
public class Gerbil extends Rodent {} ///:~

//: c10:Hamster.java
package c10;
public class Hamster extends Rodent {} ///:~


In the coming example we want to to keep track of the number of any
particular type of Pet, so we’ll need a class that holds this number in an
int. You can think of it as a modifiable Integer:
Feedback

//: c10:Counter.java
package c10;

public class Counter {
int i;
public String toString() { return Integer.toString(i); }
} ///:~

Next, we need a tool that holds two things together: an indicator of the
Pet type, and a Counter to hold the pet quantity. That is, we want to be
able to say “how may Gerbil objects are there?” An ordinary array won’t
work here, because you refer to objects in an array by their index number.
What we want to do here is refer to the objects in the array by their Pet
type. We want to associate Counter objects with Pet objects. There is a
standard data structure for doing exactly this kind of thing, called an
associative array. Here is an extremely simple version:
Feedback

//: c10:AssociativeArray.java

458 Thinking in Java www.BruceEckel.com
// Associates keys with values.
package c10;
import com.bruceeckel.simpletest.*;


public class AssociativeArray {
private static Test monitor = new Test();
private Object[][] pairs;
private int index;
public AssociativeArray(int length) {
pairs = new Object[length][2];
}
public void put(Object key, Object value) {
if(index >= pairs.length)
throw new ArrayIndexOutOfBoundsException();
pairs[index++] = new Object[] { key, value };
}
public Object get(Object key) {
for(int i = 0; i < index; i++)
if(key.equals(pairs[i][0]))
return pairs[i][1];
throw new RuntimeException("Failed to find key");
}
public String toString() {
String result = "";
for(int i = 0; i < index; i++) {
result += pairs[i][0] + " : " + pairs[i][1];
if(i < index - 1) result += "\n";
}
return result;
}
public static void main(String[] args) {
AssociativeArray map = new AssociativeArray(6);
map.put("sky", "blue");
map.put("grass", "green");

map.put("ocean", "dancing");
map.put("tree", "tall");
map.put("earth", "brown");
map.put("sun", "warm");
try {
map.put("extra", "object"); // Past the end
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("Too many objects!");
}
System.out.println(map);

Chapter 10: Detecting Types 459
System.out.println(map.get("ocean"));
monitor.expect(new String[] {
"Too many objects!",
"sky : blue",
"grass : green",
"ocean : dancing",
"tree : tall",
"earth : brown",
"sun : warm",
"dancing"
});
}
} ///:~

Your first observation might be that this appears to be a general-purpose
tool, so why not put it in a package like com.bruceeckel.tools? Well, it
is indeed a general-purpose tool—so useful, in fact, that java.util
contains a number of associative arrays (which are also called maps) that

do a lot more than this one does, and do it a lot faster. A large portion of
Chapter 11 is devoted to associative arrays, but they are significantly more
complicated and so using this one will keep things simple and at the same
time begin to familiarize you with the value of associative arrays.
Feedback

In an associative array, the “indexer” is called a key and the associated
object is called a value. Here, we associate keys and values by putting
them in an array of two-element arrays, which you see here as pairs. This
will just be a fixed-length array which is created in the constructor, so we
need index to make sure we don’t run off the end. When you put( ) in a
new key-value pair, a new 2-element array is created and inserted at the
next available location in pairs. If index is greater than or equal to the
length of pairs, then an exception is thrown.
Feedback

To use the get( ) method, you pass in the key that you want it to look up,
and it produces the associated value as the result or throws an exception if
it can’t be found. The get( ) method is using what is possibly the least
efficient approach imaginable to locate the value: starting at the top of the
array and using equals( ) to compare keys. But the point here is
simplicity, not efficiency, and the real maps in Chapter 11 have solved the
performance problems, so we don’t need to worry about it here.
Feedback


460 Thinking in Java www.BruceEckel.com
The essential methods in an associative array are put( ) and get( ), but
for easy display toString( ) has been overridden to print the key-value
pairs. To show that it works, main( ) loads an AssociativeArray with

pairs of strings and prints the resulting map, followed by a get( ) of one of
the values.
Feedback

Now that all the tools are in place, we can use instanceof to count Pets:
//: c10:PetCount.java
// Using instanceof.
package c10;
import com.bruceeckel.simpletest.*;
import java.util.*;

public class PetCount {
private static Test monitor = new Test();
private static Random rand = new Random();
static String[] typenames = {
"Pet", "Dog", "Pug", "Cat",
"Rodent", "Gerbil", "Hamster",
};
// Exceptions thrown to console:
public static void main(String[] args) {
Object[] pets = new Object[15];
try {
Class[] petTypes = {
Class.forName("c10.Dog"),
Class.forName("c10.Pug"),
Class.forName("c10.Cat"),
Class.forName("c10.Rodent"),
Class.forName("c10.Gerbil"),
Class.forName("c10.Hamster"),
};

for(int i = 0; i < pets.length; i++)
pets[i] = petTypes[rand.nextInt(petTypes.length)]
.newInstance();
} catch(InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
} catch(ClassNotFoundException e) {
System.out.println("Cannot find class");

Chapter 10: Detecting Types 461
System.exit(1);
}
AssociativeArray map =
new AssociativeArray(typenames.length);
for(int i = 0; i < typenames.length; i++)
map.put(typenames[i], new Counter());
for(int i = 0; i < pets.length; i++) {
Object o = pets[i];
if(o instanceof Pet)
((Counter)map.get("Pet")).i++;
if(o instanceof Dog)
((Counter)map.get("Dog")).i++;
if(o instanceof Pug)
((Counter)map.get("Pug")).i++;
if(o instanceof Cat)
((Counter)map.get("Cat")).i++;
if(o instanceof Rodent)

((Counter)map.get("Rodent")).i++;
if(o instanceof Gerbil)
((Counter)map.get("Gerbil")).i++;
if(o instanceof Hamster)
((Counter)map.get("Hamster")).i++;
}
// List each individual pet:
for(int i = 0; i < pets.length; i++)
System.out.println(pets[i].getClass());
// Show the counts:
System.out.println(map);
monitor.expect(new Object[] {
new TestExpression("%% class c10\\."+
"(Dog|Pug|Cat|Rodent|Gerbil|Hamster)",
pets.length),
new TestExpression(
"%% (Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster)" +
" : \\d+", typenames.length)
});
}
} ///:~

In main( ) an array petTypes of Class objects is created using
Class.forName( ). Since the Pet objects are in package c09, the
package name must be used when naming the classes.
Feedback


462 Thinking in Java www.BruceEckel.com
Next, the pets array is filled by randomly inexing into petTypes and

using the selected Class object to generate a new instance of that class
with Class.newInstance( ), which uses the default (no-arg) class
constructor to generate the new object.
Feedback

Both forName( ) and newInstance( ) can generate exceptions, which
you can see handled in the catch clauses following the try block. Again,
the names of the exceptions are relatively useful explanations of what
went wrong (IllegalAccessException relates to a violation of the Java
security mechanism).
Feedback

After creating the AssociativeArray, it is filled with key-value pairs of
pet names and Counter objects. Then each Pet in the randomly-
generated array is tested and counted using instanceof. The array and
AssociativeArray are printed so you can compare the results.
Feedback

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 array 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.
Feedback

Of course this example is contrived—you’d probably put a static field 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.
Feedback

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:
//: c10:PetCount2.java
// Using class literals.
package c10;
import com.bruceeckel.simpletest.*;

Chapter 10: Detecting Types 463
import java.util.*;

public class PetCount2 {
private static Test monitor = new Test();
private static Random rand = new Random();
public static void main(String[] args) {
Object[] pets = new Object[15];
Class[] petTypes = {
// Class literals:
Pet.class,
Dog.class,
Pug.class,
Cat.class,
Rodent.class,
Gerbil.class,

Hamster.class,
};
try {
for(int i = 0; i < pets.length; i++) {
// Offset by one to eliminate Pet.class:
int rnd = 1 + rand.nextInt(petTypes.length - 1);
pets[i] = petTypes[rnd].newInstance();
}
} catch(InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
}
AssociativeArray map =
new AssociativeArray(petTypes.length);
for(int i = 0; i < petTypes.length; i++)
map.put(petTypes[i].toString(), new Counter());
for(int i = 0; i < pets.length; i++) {
Object o = pets[i];
if(o instanceof Pet)
((Counter)map.get("class c10.Pet")).i++;
if(o instanceof Dog)
((Counter)map.get("class c10.Dog")).i++;
if(o instanceof Pug)
((Counter)map.get("class c10.Pug")).i++;
if(o instanceof Cat)
((Counter)map.get("class c10.Cat")).i++;


464 Thinking in Java www.BruceEckel.com
if(o instanceof Rodent)
((Counter)map.get("class c10.Rodent")).i++;
if(o instanceof Gerbil)
((Counter)map.get("class c10.Gerbil")).i++;
if(o instanceof Hamster)
((Counter)map.get("class c10.Hamster")).i++;
}
// List each individual pet:
for(int i = 0; i < pets.length; i++)
System.out.println(pets[i].getClass());
// Show the counts:
System.out.println(map);
monitor.expect(new Object[] {
new TestExpression("%% class c10\\." +
"(Dog|Pug|Cat|Rodent|Gerbil|Hamster)",
pets.length),
new TestExpression("%% class c10\\." +
"(Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster) : \\d+",
petTypes.length)
});
}
} ///:~

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.
Feedback

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

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.
Feedback

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:

Chapter 10: Detecting Types 465
//: c10:PetCount3.java
// Using isInstance()
package c10;
import com.bruceeckel.simpletest.*;
import java.util.*;

public class PetCount3 {
private static Test monitor = new Test();
private static Random rand = new Random();
public static void main(String[] args) {
Object[] pets = new Object[15];
Class[] petTypes = {
// Class literals:

Pet.class,
Dog.class,
Pug.class,
Cat.class,
Rodent.class,
Gerbil.class,
Hamster.class,
};
try {
for(int i = 0; i < pets.length; i++) {
// Offset by one to eliminate Pet.class:
int rnd = 1 + rand.nextInt(petTypes.length - 1);
pets[i] = petTypes[rnd].newInstance();
}
} catch(InstantiationException e) {
System.out.println("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
}
AssociativeArray map =
new AssociativeArray(petTypes.length);
for(int i = 0; i < petTypes.length; i++)
map.put(petTypes[i].toString(), new Counter());
for(int i = 0; i < pets.length; i++) {
Object o = pets[i];
// Using Class.isInstance() to eliminate
// individual instanceof expressions:
for(int j = 0; j < petTypes.length; ++j)

if(petTypes[j].isInstance(o))

466 Thinking in Java www.BruceEckel.com
((Counter)map.get(petTypes[j].toString())).i++;
}
// List each individual pet:
for(int i = 0; i < pets.length; i++)
System.out.println(pets[i].getClass());
// Show the counts:
System.out.println(map);
monitor.expect(new Object[] {
new TestExpression("%% class c10\\." +
"(Dog|Pug|Cat|Rodent|Gerbil|Hamster)",
pets.length),
new TestExpression("%% class c10\\." +
"(Pet|Dog|Pug|Cat|Rodent|Gerbil|Hamster) : \\d+",
petTypes.length)
});
}
} ///:~

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).
Feedback

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:
//: c10:FamilyVsExactType.java
// The difference between instanceof and class
package c10;
import com.bruceeckel.simpletest.*;

class Base {}
class Derived extends Base {}

public class FamilyVsExactType {
private static Test monitor = new Test();
static void test(Object x) {
System.out.println("Testing x of type " +

Chapter 10: Detecting Types 467
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());
monitor.expect(new String[] {
"Testing x of type class c10.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 c10.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"
});

}
} ///:~


468 Thinking in Java www.BruceEckel.com
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. 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.
Feedback

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.
Feedback

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:
Feedback

//: c10:ToyTest.java
// Testing class Class.
import com.bruceeckel.simpletest.*;

interface HasBatteries {}
interface Waterproof {}
interface Shoots {}
class Toy {
// Comment out the following default constructor
// to see NoSuchMethodError from (*1*)
Toy() {}
Toy(int i) {}
}

class FancyToy extends Toy

Chapter 10: Detecting Types 469
implements HasBatteries, Waterproof, Shoots {
FancyToy() { super(1); }
}

public class ToyTest {
private static Test monitor = new Test();
static void printInfo(Class cc) {
System.out.println("Class name: " + cc.getName() +
" is interface? [" + cc.isInterface() + "]");
}

public static void main(String[] args) {
Class c = null;
try {
c = Class.forName("FancyToy");
} catch(ClassNotFoundException e) {
System.out.println("Can't find FancyToy");
System.exit(1);
}
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.out.println("Cannot instantiate");
System.exit(1);
} catch(IllegalAccessException e) {
System.out.println("Cannot access");
System.exit(1);
}
printInfo(o.getClass());
monitor.expect(new String[] {
"Class name: FancyToy is interface? [false]",
"Class name: HasBatteries is interface? [true]",
"Class name: Waterproof is interface? [true]",
"Class name: Shoots is interface? [true]",

"Class name: Toy is interface? [false]"
});
}
} ///:~


470 Thinking in Java www.BruceEckel.com
You can see that class FancyToy is quite complicated, since it inherits
from Toy and implements the interfaces HasBatteries,
Waterproof, and Shoots. In main( ), a Class reference is created and
initialized to the FancyToy Class using forName( ) inside an
appropriate try block.
Feedback

The Class.getInterfaces( ) method returns an array of Class objects
representing the interfaces that are contained in the Class object of
interest.
Feedback

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.
Feedback

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 (Application Programmer
Interface).
Feedback

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( ). Thus, with the Class object you can
find out just about everything you want to know about an object.
Feedback

×