Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 119 trang
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
Nội dung
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 [...]... System.out.println("Testing x of type " + 466 Thinking in Java www.BruceEckel.com 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()... just use toString( ), as is done here, to produce a String with the entire method signature The rest of the code extracts the command line information, 474 Thinking in Java www.BruceEckel.com 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... {} ///:~ 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... for(int i = 0; i < m.length; i++) System.out.println( p.matcher(m[i].toString()).replaceAll("")); for(int i = 0; i < ctor.length; i++) 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... 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)) Chapter 10: Detecting Types 4 65 ((Counter)map.get(petTypes[j].toString())).i++; } // List each individual... 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( )... 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... 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"); 460 Thinking in Java www.BruceEckel.com System.exit(1); } AssociativeArray... }); } } ///:~ 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 Chapter 10: Detecting Types 461 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... 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 476 Thinking in Java www.BruceEckel.com . 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. 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. 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