Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

AhmadLang / Java, How To Program, 2004

.pdf
Скачиваний:
630
Добавлен:
31.05.2015
Размер:
51.82 Mб
Скачать

[Page 662 (continued)]

13.11. Declaring New Exception Types

Most Java programmers use existing classes from the Java API, third-party vendors and freely available class libraries (usually downloadable from the Internet) to build Java applications. The methods of those classes typically are declared to throw appropriate exceptions when problems occur. Programmers write code that processes these existing exceptions to make programs more robust.

If you are a programmer who builds classes that other programmers will use in their programs, you might find it useful to declare your own exception classes that are specific to the problems that can occur when another programmer uses your reusable classes.

[Page 663]

Software Engineering Observation 13.13

If possible, indicate exceptions from your methods by using existing exception classes, rather than creating new exception classes. The Java API contains many exception classes that might be suitable for the type of problem your method needs to indicate.

A new exception class must extend an existing exception class to ensure that the class can be used with the exception-handling mechanism. Like any other class, an exception class can contains fields and methods. However, a typical new exception class contains only two constructorsone that takes no arguments and passes a default exception message to the superclass constructor, and one that receives a customized exception message as a string and passes it to the superclass constructor.

Good Programming Practice 13.3

Associating each type of serious execution-time malfunction with an appropriately named Exception class improves program clarity.

Software Engineering Observation 13.14

When defining your own exception type, study the existing exception classes in the Java API and try to extend a related exception class. For example, if you are creating a new class to represent when a method attempts a division by zero, you might extend class ArithmeticException because division by zero occurs during arithmetic. If the existing classes are not appropriate superclasses for your new exception class, decide whether your new class should be a checked or an unchecked exception class. The new exception class should be a checked exception (i.e., extend Exception but not RuntimeException) if possible clients should be required to handle the exception. The client application should be able to reasonably recover from such an exception. The new exception class should extend RuntimeException if the client code should be able to ignore the

exception (i.e., the exception is an unchecked exception).

In Chapter 17, Data Structures, we provide an example of a custom exception class. We declare a reusable class called List that is capable of storing a list of references to objects. Some operations typically performed on a List are not allowed if the List is empty, such as removing an item from the front or back of the list (i.e., no items can be removed, as the List does not currently contain any items). For this reason, some List methods throw exceptions of exception class EmptyListException.

Good Programming Practice 13.4

By convention, all exception-class names should end with the word

Exception.

[Page 663 (continued)]

13.12. Preconditions and Postconditions

Programmers spends significant portions of their time on code maintenance and debugging. To facilitate these tasks and to improve the overall design, programmers generally specify the expected states before and after a method's execution. These states are called preconditions and postconditions, respectively.

A method's precondition is a condition that must be true when the method is invoked. Preconditions describe method parameters and any other expectations the method has about the current state of a program. If a user fails to meet the preconditions, then the method's behavior is undefinedit may throw an exception, proceed with an illegal value or attempt to recover from the error. However, you should never rely on or expect consistent behavior if the preconditions are not satisfied.

[Page 664]

A method's postcondition of a method is a condition that is true after the method successfully returns. Postconditions describe the return value and any other side-effects the method may have. When calling a method, you may assume that a method fulfills all of its postconditions. If you are writing your own method, you should document all postconditions so others know what to expect when they call the method.

As an example, examine String method charAt, which has one int parameteran index in the String. For a precondition, method charAt assumes that index is greater than or equal to zero and less than the length of the String. If the precondition is met, the postcondition states the method will return the character at the position in the String specified by the parameter index. Otherwise, the method throws an IndexOutOfBoundsException. We trust that method charAt satisfies its postcondition, provided that we meet the precondition. We do not need to be concerned with the details of how the method actually retrieves the character at the index. This allows the programmer to focus more on the overall design of the program rather than on the implementation details.

Some programmers state the preconditions and postconditions informally as part of the general method specification, while others prefer a more formal approach by explicitly defining them. When designing your own methods, you should state the preconditions and postconditions in a comment before the method declaration in whichever manner you prefer. Stating the preconditions and postconditions before writing a method will also help guide you as you implement the method.

[Page 664 (continued)]

13.13. Assertions

When implementing and debugging a class, it is sometimes useful to state conditions that should be true at a particular point in a method. These conditions, called assertions, help ensure a program's validity by catching potential bugs and identifying possible logic errors during development. Preconditions and postconditions are two types of assertions. Preconditions are assertions about a program's state when a method is invoked, and postconditions are assertions about a program's state after a method finishes.

While assertions can be stated as comments to guide the programmer during development, Java includes two versions of the assert statement for validating assertions programatically. The assert statement evaluates a boolean expression and determines whether it is true or false. The first form of the assert statement is

assert expression;

This statement evaluates expression and throws an AssertionError if the expression is false. The second form is

assert expression1 : expression2;

This statement evaluates expression1 and throws an AssertionError with expression2 as the error message if expression1 is false.

You can use assertions to programmatically implement preconditions and postconditions or to verify any other intermediate states that help you ensure your code is working correctly. The example in Fig. 13.9 demonstrates the functionality of the assert statement. Line 11 prompts the user to enter a number between 0 and 10, then line 12 reads the number from the command line. The assert statement on line 15 determines whether the user entered a number within the valid range. If the user entered a number that is out of range, then the program reports an error. Otherwise, the program proceeds normally.

[Page 665]

Figure 13.9. Checking with assert that a value is within range.

1// Fig. 13.9: AssertTest.java

2// Demonstrates the assert statement

3import java.util.Scanner;

4

5public class AssertTest

6{

7public static void main( String args[] )

8{

9Scanner input = new Scanner( System.in );

11System.out.print( "Enter a number between 0 and 10: " );

12int number = input.nextInt();

14// assert that the absolute value is >= 0

15

assert ( number >= 0 && number <= 10 ) : "bad number: " + number;

16

 

17System.out.printf( "You entered %d\n", number );

18} // end main

19} // end class AssertTest

Enter a number between 0 and 10: 5

You entered 5

Enter a number between 0 and 10: 50

Exception in thread "main" java.lang.AssertionError: bad number: 50 at AssertTest.main(AssertTest.java:15)

Assertions are primarily used by the programmer for debugging and identifying logic errors in a application. By default, assertions are disabled when executing a program because they reduce performance and are unnecessary for the program's user. To enable assertions at runtime, use the -ea command-line option when to the java command. To execute the program in Fig. 13.9 with assertions enabled, type

java -ea AssertTest

You should not encounter any AssertionErrors through normal execution of a properly written program. Such errors should only indicate bugs in the implementation. As a result, you should never catch an AssertionError. Rather, you should allow the program to terminate when the error occurs, so you can see the error message, then you should locate and fix the source of the problem. Since application users can choose not to enable assertions at runtime, you should not use the assert statement to indicate runtime problems in production code. Rather, you should use the exception mechanism for this purpose.

[Page 666]

13.14. Wrap-Up

In this chapter, you learned how to use exception handling to deal with errors in an application. You learned that exception handling enables programmers to remove error-handling code from the "main line" of the program's execution. You saw exception handling in the context of a divide-by-zero example. You learned how to use try blocks to enclose code that may throw an exception, and how to use catch blocks to deal with exceptions that may arise. You learned about the termination model of exception handling, which dictates that after an exception is handled, program control does not return to the throw point. You learned the difference between checked and unchecked exceptions, and how to specify with the throws clause that exceptions occurring in a method will be thrown by that method to its caller. Next, you learned how to use the finally block to release resources whether or not an exception occurred. In that discussion, you also learned how to throw and rethrow exceptions. You then learned how to obtain information about an exception using methods printStackTrace, getStackTrace and getMessage. The chapter continued with a discussion of chained exceptions, which allow programmers to wrap original exception information with new exception information. Next, we overviewed how to create your own exception classes. We then introduced preconditions and postconditions to help programmers using your methods understand conditions that must be true with the method is called and when it returns. Finally, we discuss the assert statement and how it can be used to help you debug your programs. In the next chapter, you will learn about file processing, including how persistent data is stored and how to manipulate it.

[Page 666 (continued)]

Summary

An exception is an indication of a problem that occurs during a program's execution.

Exception handling enables programmers to create applications that can resolve exceptions.

Exception handling enables programmers to remove error-handling code from the "main line" of the program's execution, improving program clarity and enhancing modifiability.

Exceptions are thrown when a method detects a problem and is unable to handle it.

An exception's stack trace includes the name of the exception in a descriptive message that

indicates the problem that occurred and the complete method-call stack (i.e., the call chain) at the time the exception occurred.

The point in the program at which an exception occurs is called the throw point.

A TRy block encloses the code that might throw an exception and the code that should not execute if that exception occurs.

Exceptions may surface through explicitly mentioned code in a TRy block, through calls to other methods or even through deeply nested method calls initiated by code in the try block.

A catch block begins with the keyword catch and an exception parameter followed by a block of

code that catches (i.e., receives) and handles the exception. This code executes when the try block detects the exception.

An uncaught exception is an exception that occurs for which there are no matching catch blocks.

An uncaught exception will cause a program to terminate early if that program contains only one

thread. If the program contains more than one thread, only the thread where the exception occurred will terminate. The rest of the program will run, but may yield adverse effects.

At least one catch block or a finally block must immediately follow the try block.

[Page 667]

Each catch block specifies in parentheses an exception parameter that identifies the exception

type the handler can process. The exception parameter's name enables the catch block to interact with a caught exception object.

If an exception occurs in a TRy block, the try block terminates immediately and program control

transfers to the first of the following catch blocks whose exception parameter type matches the type of the thrown exception.

After an exception is handled, program control does not return to the throw point because the try block has expired. This is known as the termination model of exception handling.

If there are multiple matching catch blocks when an exception occurs, only the first is executed.

After executing a catch block, this program's flow of control proceeds to the first statement after the last catch block.

A throws clause specifies the exceptions the method throws, and appears after the method's parameter list and before the method body.

The throws clause contains a comma-separated list of exceptions that the method will throw if a problem occurs when the method executes.

Exception handling is designed to process synchronous errors, which occur when a statement executes.

Exception handling is not designed to process problems associated with asynchronous events, which occur in parallel with, and independent of, the program's flow of control.

All Java exception classes inherit, either directly or indirectly, from class Exception. Because of

this fact, Java's exception classes form a hierarchy. Programmers can extend this hierarchy to create their own exception classes.

Class Throwable is the superclass of class Exception, and is therefore also the superclass of all exceptions. Only Throwable objects can be used with the exception-handling mechanism.

Class Throwable has two subclasses: Exception and Error.

Class Exception and its subclasses represent exceptional situations that could occur in a Java program and be caught by the application.

Class Error and its subclasses represent exceptional situations that could happen in the Java

runtime system. Errors happen infrequently, and typically should not be caught by an application.

Java distinguishes between two categories of exceptions: checked and unchecked.

Unlike checked exceptions, the Java compiler does not check the code to determine whether an

unchecked exception is caught or declared. Unchecked exceptions typically can be prevented by proper coding.

An exception's type determines whether the exception is checked or unchecked. All exception

types that are direct or indirect subclasses of class RuntimeException are unchecked exceptions. All exception types that inherit from class Exception but not from RuntimeException are checked.

Various exception classes can be derived from a common superclass. If a catch block is written

to catch exception objects of a superclass type, it can also catch all objects of that class's subclasses. This allows for polymorphic processing of related exceptions.

Programs that obtain certain types of resources must return them to the system explicitly to avoid so-called resource leaks. Resource-release code typically is placed in a finally block.

The finally block is optional. If it is present, it is placed after the last catch block.

Java guarantees that a provided finally block will execute whether or not an exception is

thrown in the corresponding try block or any of its corresponding catch blocks. Java also guarantees that a finally block executes if a TRy block exits by using a return, break or continue statement.

[Page 668]

If an exception that occurs in the TRy block cannot be caught by one of that TRy block's

associated catch handlers, the program skips the rest of the TRy block and control proceeds to the finally block, which releases the resource. Then the program passes to the next outer try blocknormally in the calling method.

If a catch block throws an exception, the finally block still executes. Then the exception is passed to the next outer try blocknormally in the calling method.

Programmers can throw exceptions by using the throw statement.

A throw statement specifies an object to be thrown. The operand of a tHRow can be of any class derived from class Throwable.

Exceptions are rethrown when a catch block, upon receiving an exception, decides either that it

cannot process that exception or that it can only partially process it. Rethrowing an exception defers the exception handling (or perhaps a portion of it) to another catch block.

When a rethrow occurs, the next enclosing TRy block detects the rethrown exception, and that TRy block's catch blocks attempt to handle the exception.

When an exception is thrown but not caught in a particular scope, the method-call stack is

unwound, and an attempt is made to catch the exception in the next outer try statement. This process is called stack unwinding.

Class THRowable offers a printStackTrace method that prints the method-call stack. Often, this is helpful in testing and debugging.

Class THRowable also provides a getStackTrace method that obtains stack-trace information printed by printStackTrace.

Class Throwable's getMessage method returns the descriptive string stored in an exception.

Method getStackTrace obtains the stack-trace information as an array of StackTraceElement objects. Each StackTraceElement represents one method call on the method-call stack.

StackTraceElement methods getClassName, getFileName, getLineNumber and getMethodName get the class name, file name, line number and method name, respectively.

Chained exceptions enable an exception object to maintain the complete stack-trace information, including information about previous exceptions that caused the current exception.

A new exception class must extend an existing exception class to ensure that the class can be used with the exception-handling mechanism.

A method's precondition is a condition that must be true when the method is invoked.

A method's postcondition is a condition that is true after the method successfully returns.

When designing your own methods, you should state the preconditions and postconditions in a comment before the method declaration.

Within an application, the programmer may state conditions that they assumed to be true at a

particular point. These conditions, called assertions, help ensure a program's validity by catching potential bugs and indentifying possible logic errors.

Java includes two versions of an assert statement for validating assertions programatically.

To enable assertions at run time, use the -ea switch when running the java command.

[Page 668 (continued)]

Terminology

ArithmeticException class

assert statement

assertion asynchronous event catch an exception catch block

catch clause

catch-or-declare requirement

[Page 669]

chained exception checked exception constructor failure enclosing try block

Error class

exception

Exception class exception handler exception handling exception parameter fault-tolerant program finally block finally clause

getClassName method of class StackTraceElement getFileName method of class StackTraceElement getLineNumber method of class StackTraceElement