Section 10.4. Handling Exceptions within a Program

[Page 478]

10.4. Handling Exceptions within a Program

This section will describe how to handle exceptions within the program rather than leaving them to be handled by the JVM.

10.4.1. Trying, Throwing, and Catching an Exception

In Java, errors and other abnormal conditions are handled by throwing and catching exceptions. When an error or an exceptional condition is detected, you can throw an exception as a way of signaling the abnormal condition. This is like pulling a fire alarm. When an exception is thrown, an exception handler will catch the exception and deal with it (Fig. 10.6). We will discuss try blocks, which typically are associated with catching exceptions, later in the section.

Figure 10.6. Exception handling. When an exception occurs, an object will throw an Exception. The exception handler, possibly the same object, will catch it.

Pulling the program's fire alarm

If we go back to our avgFirstN() example, the typical way of handling this error in Java would be to throw an exception in the avgFirstN() method and catch it in the calling method. Of course, the calling method could be in the same object or it could belong to some other object. In the latter case, the detection of the error is separated from its handling. This division of labor opens up a wide range of possibilities. For example, a program could dedicate a single object to serve as the handler for all its exceptions. The object could be described as the program's "fire department."

To illustrate Java's TRy/throw/catch mechanism, let's revisit the CalcAvgTest program. The version shown in Figure 10.7 mimics the way Java's default exception handler works. If the avgFirstN() method is called with an argument that is zero or negative, an IllegalArgumentException is thrown. The exception is caught by the catch clause in the CalcAvgTest.main() method.

Figure 10.7. In this version of the calcAvgTest program, an IllegalArgumentException tHRown in CalcAverage.avgFirstN(), would be handled by the catch clause in CalcAvgTest.main().
(This item is displayed on page 479 in the print version)

public class CalcAverage {   /**    * Precondition:  N > 0    * Postcondition: avgFirstN() equals the average of (1+2+... +N)    */   public double avgFirstN(int N) {     int sum = 0;     if (N <= 0)       throw new IllegalArgumentException("ERROR: Illegal argument");     for (int k = 1; k <= N; k++)       sum += k;     return sum/N;                                            // What if N is 0?   } // avgFirstN() } // CalcAverage class public class CalcAvgTest {   public static void main(String args[]) {     try {       CalcAverage ca = new CalcAverage();       System.out.println( "AVG + " + ca.avgFirstN(0));     }     catch (IllegalArgumentException e) {                     // Exception Handler       System.out.println(e.getMessage());       e.printStackTrace();       System.exit(0);     }   } // main() } // CalcAvgTest class 

Let's go through this example step by step. The first thing to notice is that if the CalcAverage.avgFirstN() method has a zero or negative argument, it will throw an exception:

if (N <= 0)   throw new IllegalArgumentException("ERROR: Illegal argument"); 

Note the syntax of the throw statement. It creates a new IllegalArgumentException object and passes it a message that describes the error. This message becomes part of the exception object. It can be retrieved using the getMessage() method, which is inherited from the Throwable class (Fig. 10.4).

[Page 479]

When a throw statement is executed, the JVM interrupts the normal execution of the program and searches for an exception handler. We will describe the details of this search shortly. In this case, the exception handler is the catch clause contained in the CalcAvgTest.main() method:

catch (IllegalArgumentException e) {// Exception Handler    System.out.println(e.getMessage());    e.printStackTrace();    System.exit(0); } 

When an IllegalArgumentException is thrown, the statements within this catch clause are executed. The first statement uses the getMessage() method to print a copy of the error message. The second statement uses the printStackTrace() method, which is defined in Throwable and inherited by all Exceptions, to print a trace of the method calls leading up to the exception. The last statement causes the program to terminate.

[Page 480]

When we run this program, the following output will be generated as a result of the illegal argument error:

ERROR: Can't average 0 elements java.lang.IllegalArgumentException: ERROR: Illegal argument    at java.lang.Throwable.fillInStackTrace(Native Method)    at java.lang.Throwable.<init>(    at java.lang.Exception.<init>(    at java.lang.RuntimeException.<init>(    at java.lang.IllegalArgumentException.<init>(    at CalcAverage.avgFirstN(Compiled Code)    at CalcAvgTest.main( 

Thus, as in the previous example of Java's default exception handler, our exception handler also prints out a description of the error and a trace of the method calls that led up to the error. However, in this example, we are directly handling the exception rather than leaving it up to Java's default exception handler. Of course, this example is intended mainly for illustrative purposes. It would make little sense to write our own exception handler if it does nothing more than mimic Java's default handler.

Effective Design: Using an Exception

Unless your program's handling of an exception is significantly different from Java's default handling of it, the program should just rely on the default.

Finally, note that the catch clause is associated with a try block. The handling of exceptions in Java takes place in two parts: First, we try to execute some statements, which may or may not lead to an exception. These are the statements contained within the try clause:

try {     CalcAverage ca = new CalcAverage();     System.out.println( "AVG + " + ca.avgFirstN(0)); } 

Second, we provide one or more catch clauses to handle particular types of exceptions. In this case, we are only handling IllegalArgumentExceptions.

As we said earlier, throwing an exception is like pulling a fire alarm. The throw occurs somewhere within the scope of the try block. The "fire department" in this case is the code contained in the catch clause that immediately follows the try block. This is the exception handler for this particular exception. There's something like a game of catch going on here: a method within the try block throws an Exception object, which is caught and handled by the catch block located in another object (Fig. 10.8).

Figure 10.8. Playing catch: In this design, the IllegalArgumentException is thrown by the CalcAverage.avgFirstN() method and caught by the catch clause within the CalcAvgTest.main() method.
(This item is displayed on page 481 in the print version)

Responding to the fire alarm

10.4.2. Separating Error Checking from Error Handling

Divide and conquer

As we see in the CalcAvgTest example, an important difference between Java's exception handling and more traditional approaches is that error handling can be separated from the normal flow of execution within a program. The CalcAverage.avgFirstN() method still checks for the error and still throws IllegalArgumentException if N does not satisfy the method's precondition. But it does not contain code for handling the exception. The exception-handling code is located in the CalcAvgTest class.

[Page 481]

Thus, the CalcAvgTest program creates a clear separation between the normal algorithm and the exception-handling code. One advantage of this design is that the normal algorithm is uncluttered by error-handling code and, therefore, easier to read.

Another advantage is that the program's response to errors has been organized into one central location. By locating the exception handler in CalcAvgTest.main(), one exception handler can be used to handle other errors of that type. For example, this catch clause could handle all IllegalArgumentExceptions that get thrown in the program. Its use of printStackTrace() will identify exactly where the exception occurred. In fact, because a Java application starts in the main() method, encapsulating all of a program's executable statements within a single try block in the main() method will effectively handle all the exceptions that occur within a program.

Effective Design: Normal versus Exceptional Code

A key element of Java's exception-handling mechanism is that the exception handlerthe catch blockis distinct from the code that throws the exceptionthe TRy block. The try block contains the normal algorithm. The catch block contains code for handling exceptional conditions.

10.4.3. Syntax and Semantics of try/Throw/Catch

A try block begins with the keyword try followed by a block of code enclosed within curly braces. A catch clause, or catch block, consists of the keyword catch, followed by a parameter declaration that identifies the type of Exception being caught, followed by a collection of statements enclosed within curly braces. These are the statements that handle the exception by taking appropriate action.

try block

Catch block

Once an exception is thrown, control is transferred out of the try block to an appropriate catch block. Control does not return to the TRy block.

Java Language Rule: Try Block Control

If an exception is thrown, the try block is exited and control does not return to it.

[Page 482]

The complete syntax of the try/catch statement is summarized in Figure 10.9. The TRy block is meant to include a statement or statements that might throw an exception. The catch blocksthere can be one or moreare meant to handle exceptions thrown in the try block. A catch block will handle any exception that matches its parameter class, including subclasses of the class. The finally block clause is an optional clause that is always executed whether an exception is thrown or not.

Figure 10.9. Java's try/catch statement.

 try {                             // Block of statements                             // At least one of which may throw an exception      if ( /* Some condition obtains */ )           throw new ExceptionName();  } catch (ExceptionName ParameterName) {                             // Block of statements to be executed                             // If the ExceptionName exception is thrown in try  }  catch (ExceptionName2 ParameterName) {                             // Block of statements to be executed                             // If the ExceptionName2 exception is thrown in try ...                         // Possibly other catch clauses  } finally {                             // Optional block of statements that is executed                             // Whether an exception is thrown or not  } 

The statements in the try block are part of the program's normal flow of execution. By encapsulating a group of statements within a try block, you indicate that they may throw one or more exceptions, and that you intend to catch them. In effect, you are trying a block of code with the possibility that something might go wrong.

Normal flow of execution

If an exception is thrown within a TRy block, Java exits the block and transfers control to the first catch block that matches the particular kind of exception thrown. Exceptions are thrown by using the tHRow statement, which takes the following general form:

throw new ExceptionClassName(OptionalMessageString); 

Exceptional flow of execution

The keyword throw is followed by the instantiation of an object of the ExceptionClassName class. This is done the same way we instantiate any object in Java: by using the new operator and invoking one of the exception's constructor methods. Some of the constructors take an OptionalMessageString, which is the message returned by the exception's getMessage() method.

A catch block has the following general form:

catch (ExceptionClassName ParameterName) {     // Exception handling statements } 

[Page 483]

A catch block is very much like a method definition. It contains a parameter, which specifies the class of exception handled by the block. The ParameterName can be any valid identifier, but it is customary to use e as the catch block parameter. The parameter's scope is limited to the catch block and is used to refer to the caught exception.

The ExceptionClassName must be one of the classes in Java's exception hierarchy (see Fig. 10.4). A thrown exception will match any parameter of its own class or any of its superclasses. Thus, if an ArithmeticException is thrown, it will match both an ArithmeticException parameter and an Exception parameter, because ArithmeticException is a subclass of Exception.

Exceptions are objects

Note that there can be multiple catch clauses associated with a given try block, and the order in which they are arranged is important. A thrown exception will be caught by the first catch clause it matches. Therefore, catch clauses should be arranged in order from most specific to most general (see the exception hierarchy in Figure 10.4). If a more general catch clause precedes a more specific one, it will prevent the more specific one from executing. In effect, the more specific clause will be hidden by the more general one. You might as well just not have the more specific clause at all.

Arranging catch clauses

To illustrate how to arrange catch clauses, suppose an ArithmeticException is thrown in the following try/catch statement:

try {      // Suppose an ArithmeticException is thrown here } catch (ArithmeticException e) {      System.out.println("ERROR: " + e.getMessage() );      e.printStackTrace();      System.exit(1); } catch (Exception e) {      System.out.println("ERROR: " + e.getMessage() ); } 

In this case, the exception would be handled by the more specific ArithmeticException block. On the other hand, if some other kind of exception is raised, it will be caught by the second catch clause. The Exception class will match any exception thrown. Therefore, it should always occur last in a sequence of catch clauses.

Which handler to use?

Java Programming Tip: Arranging Catch Clauses

Catch clauses should be arranged from most specific to most general. The Exception clause should always be the last in the sequence.

10.4.4. Restrictions on the try/catch/finally Statement

There are several important restrictions that apply to Java's exception-handling mechanism. We will describe these in more detail later in the chapter.

  • A try block must be immediately followed by one or more catch clauses and a catch clause may only follow a try block.

  • [Page 484]
  • A throw statement is used to throw both checked exceptions and unchecked exceptions, where unchecked exceptions are those belonging to RuntimeException or its subclasses. Unchecked exceptions need not be caught by the program.

  • A throw statement must be contained within the dynamic scope of a try block, and the type of Exception tHRown must match at least one of the try block's catch clauses. Or the throw statement must be contained within a method or constructor that has a throws clause for the type of thrown Exception.

Java Language Rule: try/Catch Syntax

A try block must be followed immediatelywith no intervening codeby one or more catch blocks. A catch block can only be preceded by a try block or by another catch block. You may not place intervening code between catch blocks.

10.4.5. Dynamic versus Static Scoping

How does Java know that it should execute the catch clause in CalcAvgTest.main() when an exception is thrown in avgFirstN()? Also, doesn't the latest version of avgFirstN() (Fig. 10.7) violate the restriction that a tHRow statement must occur within a try block?

An exception can only be thrown within a dynamically enclosing TRy block. This means that the throw statement must fall within the dynamic scope of an enclosing try block. Let's see what this means.

Dynamic scope

Dynamic scoping refers to the way a program is executed. For example, in CalcAverage (Fig. 10.7), the avgFirstN() method is called from within the TRy block located in CalcAvgTest.main(). Thus, it falls within the dynamic scope of that TRy block.

Static scope

Contrast dynamic scoping with what you have learned about static scope, which we have used previously to define the scope of parameters and local variables (Fig. 10.10). Static scoping refers to the way a program is written. A statement or variable occurs within the scope of a block if its text is actually written within that block. For example, consider the definition of MyClass (Fig. 10.11). The variable X occurs within the (static) scope of method1(), and the variable Y occurs within the (static) scope of method2().

[Page 485]

Figure 10.10. Dynamic versus static scoping. Static scoping refers to how the program is written. Look at its definitions. Dynamic scoping refers to how the program executes. Look at what it actually does.
(This item is displayed on page 484 in the print version)

Figure 10.11. An example of dynamic versus static scoping.

public class MyClass {     public void method1() {         int X = 1;         System.out.println("Hello" + X);     }     public void method2() {         int Y = 2;         System.out.println("Hello" + Y);     }     public static void main( String argv[] ) {         MyClass myclass = new MyClass();         if (Math.random() > 0.5)             myclass.method2();         else             myclass.method1();     } } // MyClass class 

A method's parameters and local variables occur within its static scope. Also, in the MyClass definition, the System.out.println() statements occur within the static scope of method1() and method2(), respectively. In general, static scoping refers to where a variable is declared or where a statement is located. Static scoping can be completely determined by just reading the program.

Dynamic scoping can only be determined by running the program. For example, in MyClass the order in which its statements are executed depends on the result of Math.random(). Suppose that when random() is executed it returns the value 0.99. In that case, main() will call method2(), which will call System.out.println(), which will print "Hello2". In that case, the statement System.out.println("Hello" + Y) has the following dynamic scope:

main()     method2()         System.out.println("Hello" + Y); 

It occurs within the (dynamic) scope of method2(), which is within the (dynamic) scope of main(). On the other hand, if the result of random() had been 0.10, that particular println() statement would not have been executed at all. Thus, to determine the dynamic scope of a statement, you must trace the program's execution. In fact, this is what the printStackTrace() method does. It prints a trace of a statement's dynamic scope.

10.4.6. Exception Propagation: Searching for a Catch Block

When an exception is thrown, Java uses both static and dynamic scoping to find a catch clause to handle it. Java knows how the program is definedafter all, it compiled it. Thus, the static scope of a program's methods is determined by the compiler. Java also places a record of every method call the program makes on a method call stack. A method call stack is a data structure that behaves like a stack of dishes in a cafeteria. For each method call, a method call block is placed on top of the stack (like a dish), and when a particular method call returns, its block is removed from the top of the stack (Fig. 10.12).

[Page 486]

Figure 10.12. The method call stack for the Propagate program. The curved arrows give a trace of the method calls leading to the program's present state.

Method call stack

An important feature of the method call stack is that the current executing method is always represented by the top block on the method call stack. If an exception happens during a method call, you can trace backward through the method calls, if necessary, to find an exception handler for that exception. In Figure 10.12, you can visualize this back trace as a matter of reversing the direction of the curved arrows.

In order to find a matching catch block for an exception, Java uses its knowledge of the program's static and dynamic scope to perform a method stack trace. The basic idea is that Java traces backward through the program until it finds an appropriate catch clause. The trace begins within the block that threw the exception. Of course, one block can be nested (statically) within another block. If the exception is not caught by the block in which it is thrown, Java searches the enclosing block. This is static scoping. If it is not caught within the enclosing block, Java searches the next-higher enclosing block, and so on. This is still static scoping.

Method stack trace

If the exception is not caught at all within the method in which it was thrown, Java uses the method call stack (Fig. 10.12) to search backward through the method calls that were made leading up to the exception. This is dynamic scoping. In the case of our CalcAvgTest() example (Fig. 10.7), Java would search backward to the CalcAvgTest.main() method, which is where avgFirstN() was called, and it would find the catch clause there for handling IllegalArgumentExceptions. It would, therefore, execute that catch clause.

[Page 487]
Self-Study Exercises

Exercise 10.3

Suppose a program throws an ArrayIndexOutOfBoundsException. Using the exception hierarchy in Figure 10.4, determine which of the following catch clauses could handle that exception.

  1. catch (RunTimeException e)

  2. catch (StringIndexOutOfBoundsException e)

  3. catch (IndexOutOfBoundsException e)

  4. catch (Exception e)

  5. catch (ArrayStoreException e)

Exercise 10.4

In the program that follows, suppose that the first time random() is called it returns 0.98, and the second time it is called it returns 0.44. What output would be printed by the program?

class MyClass2 {     public void method1(double X) {         if (X > 0.95)             throw new ArithmeticException(X + " is out of range");         System.out.println("Hello " + X);     }     public void method2(double Y) {         if (Y > 0.5)              throw new ArithmeticException(Y + " is out of range");         System.out.println("Hello " + Y);     }     public static void main(String argv[]) {         MyClass2 myclass = new MyClass2();         try {             myclass.method1(Math.random());             myclass.method2(Math.random());         } catch (ArithmeticException e) {             System.out.println(e.getMessage());         }     } // main() } // MyClass2 class 

Exercise 10.5

For the values returned by random() in the preceding exercise, show what would be output if printStackTrace() were called in addition to printing an error message.

Exercise 10.6

In the MyClass2 program, suppose that the first time random() is called it returns 0.44, and the second time it is called it returns 0.98. What output would be printed by the program?

Exercise 10.7

For the values returned by random() in the preceding exercise, show what would be output if printStackTrace() were called instead of printing an error message.

[Page 488]
Exercise 10.8

Find the divide-by-zero error in the following program, and then show what stack trace would be printed by the program:

public class BadDivide {     public void method1 (int n) {         method2(100, n);     }     public void method2 (int n, int d) {          System.out.println(n / d);     }     public static void main(String args[]) {         BadDivide bd = new BadDivide();         for (int k = 0; k < 5; k++)             bd.method1(k);     } } 

Exercise 10.9

Modify method2() so that it handles the divide-by-zero exception itself instead of letting Java handle it. Have it print an error message and a stack trace.

Exercise 10.10

What would be printed by the following code segment if someValue equals 1000?

int M = someValue; try {     System.out.println("Entering try block");     if (M > 100)         throw new Exception(M + " is too large");     System.out.println("Exiting try block"); } catch (Exception e) {     System.out.println("ERROR: " + e.getMessage()); } 

Exercise 10.11

What would be printed by the code segment in the preceding question if someValue equals 50?

Exercise 10.12

Write a TRy/catch block that throws an Exception if the value of variable X is less than zero. The exception should be an instance of Exception, and, when it is caught, the message returned by getMessage() should be "ERROR: Negative value in X coordinate".

Java, Java, Java(c) Object-Orienting Problem Solving
Java, Java, Java, Object-Oriented Problem Solving (3rd Edition)
ISBN: 0131474340
EAN: 2147483647
Year: 2005
Pages: 275

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: