1. Trang chủ
  2. » Công Nghệ Thông Tin

Thinking in Java 3rd Edition phần 5 ppt

119 491 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 119
Dung lượng 537,26 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 c

Trang 1

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 FeedbackExercises

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

Trang 2

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

Trang 3

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

Trang 4

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

Trang 5

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

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

Trang 6

};

for(int i = 0; i < shapeList.length; i++)

((Shape)shapeList[i]).draw(); // Must cast

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

Trang 7

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

Trang 8

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

public class SweetShop {

private static Test monitor = new Test();

public static void main(String[] args) {

Trang 9

}

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

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

Trang 10

(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

Trang 11

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 FeedbackOrdinarily, 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 {} ///:~

Trang 12

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

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

Trang 13

// 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++)

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

Trang 14

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

Trang 15

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:

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];

Trang 16

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++) {

// List each individual pet:

for(int i = 0; i < pets.length; i++)

});

}

} ///:~

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

Trang 17

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.*;

Trang 18

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];

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

for(int i = 0; i < petTypes.length; i++)

map.put(petTypes[i].toString(), new Counter()); for(int i = 0; i < pets.length; i++) {

Trang 19

if(o instanceof Rodent)

// List each individual pet:

for(int i = 0; i < pets.length; i++)

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

Trang 20

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];

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

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

Trang 21

((Counter)map.get(petTypes[j].toString())).i++; }

// List each individual pet:

for(int i = 0; i < pets.length; i++)

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

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 " +

Trang 22

System.out.println("x.getClass() == Derived.class " + (x.getClass() == Derived.class));

System.out.println("x.getClass().equals(Base.class)) "+ (x.getClass().equals(Base.class)));

"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",

Trang 23

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

// Comment out the following default constructor

// to see NoSuchMethodError from (*1*)

Trang 24

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() + "]");

Class[] faces = c.getInterfaces();

for(int i = 0; i < faces.length; i++)

"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]"

});

}

} ///:~

Trang 25

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

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

Trang 26

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 Feedback

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 this class that shows up later while it’s compiling the code for your program, how can you possibly use such a class? Feedback

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

Trang 27

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 Feedback

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 the JDK documentation.) Thus, the class information

for anonymous objects can be completely determined at run time, and nothing need be known at compile time Feedback

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

Trang 28

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 Feedback

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 12) and JavaBeans (Chapter 14) 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 JDK 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

consuming1 Fortunately, reflection provides a way to write a simple tool that will automatically show you the entire interface Here’s the way it works:

public class ShowMethods {

private static final String usage =

"usage: \n" +

"ShowMethods pified.class.name\n" +

"To show all methods in class or: \n" +

"ShowMethods pified.class.name word\n" +

"To search for methods involving 'word'";

private static Pattern p = Pattern.compile("\\w+\\.");

1 Especially in the past However, Sun has greatly improved its HTML Java documentation

Trang 29

public static void main(String[] args) {

System.out.println(

p.matcher(ctor[i].toString()).replaceAll("")); lines = m.length + ctor.length;

} else {

for(int i = 0; i < m.length; i++)

if(m[i].toString().indexOf(args[1]) != -1) { System.out.println(

p.matcher(m[i].toString()).replaceAll("")); lines++;

}

for(int i = 0; i < ctor.length; i++)

if(ctor[i].toString().indexOf(args[1]) != -1) { System.out.println(p.matcher(

toString( ), as is done here, to produce a String with the entire method

signature The rest of the code extracts the command line information,

Trang 30

determines if a particular signature matches your target string (using

indexOf( )), and strips off the name qualifiers Feedback

To strip the name qualifiers like “java.lang.” from “java.lang.String,”

Java JDK 1.4 regular expressions offer a powerful and succinct tool that

has been available in some languages for many years You’ve already seen

simple usage of regular expressions inside the expect( ) statements of the com.bruceeckel.simpletest.Test class In the above example, you

can see the basic coding steps necessary to use regular expressions in your own programs Feedback

After importing java.util.regex, you first compile the regular expression using the static Pattern.compile( ) method, which produces a Pattern

object using the string argument In this case, the argument is

"\\w+\\."

To understand this or any other regular expression, look at the JDK

documentation under java.util.regex.Pattern For this one, you’ll find that ‘\w’ means “a word character: [a-zA-Z_0-9].” The ‘+’ means “one or

more of the preceeding expression”—so in this case, one or more word characters—and the ‘\.’ produces a literal period (rather than the period operator which means “any character” in a regular expression) So this expression will match any sequence of word characters followed by a period, which is exactly what we need to strip off the qualifiers Feedback

After you have a compiled Pattern object, you use it by calling the

matcher( ) method, passing the string that you want to search The matcher( ) method produces a Matcher object, which has a set of

operations to choose from (you can see all of these in the JDK

documentation for java.util.regex.Matcher) Here, the replaceAll( )

method is used to replace all the matches with empty strings—that is, to delete the matches Feedback

As a more compact alternative, you can use the regular expressions built

into the String class For example, the last use of replaceAll( ) in the

above program could be rewritten from:

p.matcher(ctor[i].toString()).replaceAll("")

to

Trang 31

ctor[i].toString().replaceAll("\\w+\\.", "")

without precompiling the regular expression This form is good for shot uses of regular expressions, but the precompiled form is significantly more efficient if you need to use the regular expression more than once, as

single-is the case with thsingle-is example Feedback

This example 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 the JDK 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) Although initially this is something you may not think you’ll ever need, the value of full reflection can be quite surprising Feedback

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,

package access), the synthesized default constructor no longer shows up

in the output The synthesized default constructor is automatically given the same access as the class Feedback

Another interesting experiment is to invoke java ShowMethods

java.lang.String with an extra argument of char, int, String, etc

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 hunting through the index or class hierarchy in the JDK

documentation, or if you don’t know whether that class can do anything

with, for example, Color objects Feedback

Chapter 14 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 Feedback

Trang 32

Summary

RTTI allows you to discover type information from an anonymous 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

base-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 FeedbackHowever, 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, one 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 Feedback

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

Trang 33

you might not see this when you’re first solving the problem and could mistakenly assume that you must use RTTI Feedback

Finally, RTTI will sometimes solve efficiency problems Suppose 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 (see Chapter 15) 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 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 Feedback

2 Modify Exercise 1 so that it uses instanceof to check the type

before performing the downcast Feedback

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

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

5 Add a new type of Pet to PetCount3.java Verify that it is

created and counted correctly in main( ) Feedback

6 Write a method that takes an object and recursively prints all the

classes in that object’s hierarchy Feedback

Trang 34

7 Modify Exercise 6 so that it uses Class getDeclaredFields( ) to

also display information about the fields in a class Feedback

8 In ToyTest.java, comment out Toy’s default constructor and

explain what happens Feedback

9 Incorporate a new kind of interface into ToyTest.java and

verify that it is detected and displayed properly Feedback

10 Write a program to determine whether an array of char is a

primitive type or a true object Feedback

11 Implement clearSpitValve( ) as described in the summary

Feedback

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

13 In ToyTest.java, use reflection to create a Toy object using the

nondefault constructor Feedback

14 Look up the interface for java.lang.Class in the JDK

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 Feedback

15 Modify the regular expression in ShowMethods.java to

additionally strip off the keywords native and final (hint: use the

“or” operator ‘|’)

Trang 36

11: Collections of

Objects

It’s a fairly simple program that has only a fixed quantity

of objects with known lifetimes

In general, your programs will always be creating new objects based on

some criteria that will be known only at the time the program is running

You won’t know until runtime the quantity or even the exact type of the

objects you need To solve the general programming problem, you need to

be able to create any number of objects, anytime, anywhere So you can’t

rely on creating a named reference to hold each one of your objects:

MyObject myReference;

since you’ll never know how many of these you’ll actually need Feedback

Most languages provide some way to solve this rather essential problem

Java has several ways to hold objects (or rather, references to objects)

The built-in type is the array, which has been discussed before Also, the

Java utilities library has a reasonably complete set of container classes

(also known as collection classes, but because the Java 2 libraries use the

name Collection to refer to a particular subset of the library, I shall also

use the more inclusive term “container”) Containers provide

sophisticated ways to hold and even manipulate your objects Feedback

Arrays

Most of the necessary introduction to arrays is in the last section of

Chapter 4, which showed how you define and initialize an array Holding

objects is the focus of this chapter, and an array is just one way to hold

objects But there are a number of other ways to hold objects, so what

makes an array special? Feedback

Trang 37

There are two issues that distinguish arrays from other types of

containers: efficiency and type The array is the most efficient way that Java provides to store and randomly access a sequence of object

references The array is a simple linear sequence, which makes element access fast, but you pay for this speed: when you create an array object, its size is fixed and cannot be changed for the lifetime of that array object You might suggest creating an array of a particular size and then, if you run out of space, creating a new one and moving all the references from

the old one to the new one This is the behavior of the ArrayList class,

which will be studied later in this chapter However, because of the

overhead of this flexibility, an ArrayList is measurably less efficient than

an array Feedback

In C++, the vector container class does know the type of objects it holds,

but it has a different drawback when compared with arrays in Java: the

C++ vector’s operator[] doesn’t do bounds checking, so you can run

past the end1 In Java, you get bounds checking regardless of whether

you’re using an array or a container—you’ll get a RuntimeException if

you exceed the bounds This type of exception indicates a programmer error, and thus you don’t need to check for it in your code As an aside, the

reason the C++ vector doesn’t check bounds with every access is speed—

in Java you have the constant performance overhead of bounds checking all the time for both arrays and containers Feedback

The other generic container classes that will be studied in this chapter,

List, Set, and Map, all deal with objects as if they had no specific type That is, they treat them as type Object, the root class of all classes in

Java This works fine from one standpoint: you need to build only one container, and any Java object will go into that container (Except for primitives—these can be placed in containers as constants using the Java primitive wrapper classes, or as changeable values by wrapping in your own class.) This is the second place where an array is superior to the generic containers: when you create an array, you create it to hold a specific type This means that you get compile-time type checking to prevent you from putting the wrong type in, or mistaking the type that

1 It’s possible, however, to ask how big the vector is, and the at( ) method does perform

bounds checking

Trang 38

you’re extracting Of course, Java will prevent you from sending an

inappropriate message to an object, either at compile time or at run time

So it’s not much riskier one way or the other, it’s just nicer if the compiler points it out to you, faster at run time, and there’s less likelihood that the end user will get surprised by an exception Feedback

For efficiency and type checking it’s always worth trying to use an array However, when you’re trying to solve a more general problem arrays can

be too restrictive After looking at arrays, the rest of this chapter will be devoted to the container classes provided by Java Feedback

Arrays are first-class objects

Regardless of what type of array you’re working with, the array identifier

is actually a reference to a true object that’s created on the heap This is the object that holds the references to the other objects, and it can be created either implicitly, as part of the array initialization syntax, or

explicitly with a new expression Part of the array object (in fact, the only field or method you can access) is the read-only length member that tells you how many elements can be stored in that array object The ‘[]’ syntax

is the only other access that you have to the array object Feedback

The following example shows the various ways that an array can be

initialized, and how the array references can be assigned to different array objects It also shows that arrays of objects and arrays of primitives are almost identical in their use The only difference is that arrays of objects hold references, while arrays of primitives hold the primitive values directly Feedback

//: c11:ArraySize.java

// Initialization & re-assignment of arrays

import com.bruceeckel.simpletest.*;

class Weeble {} // A small mythical creature

public class ArraySize {

private static Test monitor = new Test();

public static void main(String[] args) {

// Arrays of objects:

Weeble[] a; // Null reference

Trang 39

Weeble[] c = new Weeble[4];

for(int i = 0; i < c.length; i++)

c[i] = new Weeble();

// The references inside the array are

// automatically initialized to null:

for(int i = 0; i < b.length; i++)

int[] e; // Null reference

int[] f = new int[5];

int[] g = new int[4];

for(int i = 0; i < g.length; i++)

// The primitives inside the array are

// automatically initialized to zero:

for(int i = 0; i < f.length; i++)

Trang 40

The array a is initially just a null reference, and the compiler prevents

you from doing anything with this reference until you’ve properly

initialized it The array b is initialized to point to an array of Weeble references, but no actual Weeble objects are ever placed in that array However, you can still ask what the size of the array is, since b is pointing

to a legitimate object This brings up a slight drawback: you can’t find out

how many elements are actually in the array, since length tells you only

how many elements can be placed in the array; that is, the size of the

array object, not the number of elements it actually holds However, when

an array object is created its references are automatically initialized to

null, so you can see whether a particular array slot has an object in it by checking to see whether it’s null Similarly, an array of primitives is automatically initialized to zero for numeric types, (char)0 for char, and false for boolean Feedback

Array c shows the creation of the array object followed by the assignment

of Weeble objects to all the slots in the array Array d shows the

“aggregate initialization” syntax that causes the array object to be created

Ngày đăng: 14/08/2014, 00:21

TỪ KHÓA LIÊN QUAN