Exceptions

We'll cover the purpose and use of exceptions following this order. The one sentence summary is “Exceptions are like software interrupts—they are generated by error conditions like division by zero, and they divert the flow of control to a place where you have said you will handle this kind of error.

First, we'll look at the basics of:

  • Why exceptions are in the language.

  • What causes an exception (implicitly and explicitly).

Once an exception has occurred, you'll want to know how to take steps to deal with it:

  • How to handle (“catch”) an exception within the method where it was thrown.

  • Handling groups of related exceptions.

You'll also need to know what happens if you do not provide code to handle each type of exception. You can skip this on a first reading if you just want the exception basics. These sections have information saying how methods tell the compiler about exceptions they might generate but do not handle:

  • How the exception propagates if not handled in the method where it was thrown.

  • How and why methods declare the exceptions that can propagate out of them.

  • Fancy exception stuff.

The purpose of exceptions

Exceptions are for changing the flow of control when some important or unexpected event, usually an error, has occurred. They divert processing to a part of the program that can try to cope with the error, or at least die gracefully.

There are many possible errors that can happen in non-trivial programs: these range from “unable to open a file” to “array subscript out of range” to “no memory left to allocate” to “division by zero.” It would obscure the code greatly to check for all possibilities at all places where they may happen. Exceptions provide a clean way for you to write general or specific error handlers, and have the run-time system watch for these errors and transfer control when necessary. Your error handler code thus does not clutter up the mainstream processing.

Java exceptions are adapted from C++, which itself borrowed them from the functional language ML, developed by Bell Labs, Princeton, and recently Yale. Java exception terminology is shown in Table 10-1.

Table 10-1. Exception terminology of Java

Note

Java

Some other languages

An error condition that happens at run-time

Exception

Exception

Causing an exception to occur

Throwing

Raising

Capturing an exception that has just occurred and executing statements to resolve it in some way

Catching

Handling

The block that does this

Catch clause

Handler

The sequence of method calls that brought control to the point where the exception happened

Stack trace

Call chain

What happens when an exception is thrown

An exception is set in motion by the program doing some illegal or invalid action, such as trying to reference through a null pointer. An exception can also be caused explicitly by the “throw” statement. Two things happen:

  • An exception object is instantiated to record the details of what went wrong

  • The run-time system then diverts the normal flow of control, to search back up the call chain for a place where you have put a statement saying you can handle this kind of exception object.

That place can be in the same method, in the method that called the one where the exception occurred, or in the one that called that method, and so on. You might want to refresh your memory on how the stack keeps track of method invocations, by looking at Figure 10-1.

Because control propagates back up the call chain, we say that the exception object is thrown. It's not really “thrown” anywhere; the exception object is really held ready to one side, while the run-time system looks further and further up the call chain for a catch clause that covers this kind of exception.

If the run-time system gets to the top where your program execution started, and no handler for the exception has yet been found, then program execution will cease with an explanatory message.

How to cause an exception (implicitly and explicitly)

As stated previously, exceptions occur when:

  • The program does something illegal (common case), or

  • The program explicitly generates an exception by executing the throw statement (less common case)

The throw statement has this general form:

throw ExceptionObject;

The ExceptionObject is an object of a class that extends the class java.lang.Exception.

Example of causing an exception

Here is a simple program that results in an “int division by zero” exception:

class example10 {
   public static void main(String[] a) {
      int i=1, j=0, k;

      k = i/j;    // line 5 causes division-by-zero exception
   }
}

Compiling and running this program gives this result:

> javac example10.java
> java example10
     java.lang.ArithmeticException: / by zero
           at example10.main(example10.java:5)

The message “/ by zero” is the unknown compiler writer's goofy shorthand for “division by zero”. There are a certain number of predefined exceptions, like ArithmeticException, known as the run-time exceptions. Actually, since all exceptions are run-time events, a better name would be the “irrecoverable” exceptions. The Javaheads mean “run-time” in the sense of “thrown by the run-time library code, not your code.”

Run-time exceptions contrast with the user-defined exceptions which are generally held to be less severe, and in some instances can be recovered from. If a filename cannot be opened, prompt the user to enter a new name. If a data structure is found to be full, overwrite some element that is no longer needed.

You don't have to (but can) make provisions for catching run-time exceptions. You do have to make provision for user-defined exception types. The user-defined exceptions are called checked exceptions because the compiler checks your code for consistency across: where they are thrown, the methods that say they can throw them, and where they are caught (handled).

The class hierarchy for exceptions looks like Figure 10-2.

Figure 10-2. Class hierarchy for exceptions

image

User-defined exceptions

Here is an example of how to create your own exception class by extending class java.lang.Exception:

class OutofGas extends Exception {}

class Airplane {
      /* more code */
      if (fuel < 0.1) throw   new OutofGas();
}

Any method that throws a user-defined exception must also either catch it or declare it as part of the method interface. What, you may ask, is the point of throwing an exception if you are going to catch it in the same method? The answer is that exceptions don't reduce the amount of work you have to do to handle errors. Their advantage is they let you collect it all in well localized places in your program so you don't obscure the main flow of control with zillions of checks of return values.

Figure 10-3. Exception notice an error, but don't necessarily allow recovery

image

© 1996 Matthew Burtch

How to handle (“catch”) an exception within the method where it was thrown

Here is the general form of how to catch an exception:

image

The try statement says, “Try these statements and see if you get an exception.” The try statement must be followed by one (or both) of these two clauses:

  • The catch clause

  • The finally clause

You can have multiple catch clauses, and you can have catch clauses with a finally clause.

Each catch says, “I will handle any exception that matches my argument.” Matching an argument means that the thrown exception could legally be assigned to the argument exception (assignment compatible—it's the same class or a subclass of the argument exception). There can be several successive catches, each looking for a different exception. Here's an example try statement that catches just one type of exception.

try {
   i = method_A(len, 0);
   j = method_B( );
} catch (CharConversionException cce) {
   n = assignDefaults();
}

It's generally regarded as bad style to attempt to catch all exceptions with one clause, like this:

catch (Exception e) { /* more code */

That is too general to be of use and you might catch more than you expected. You should catch specific checked exceptions and let run-time exceptions propagate up to give you a reasonable error message when they hit the top. However, you'll see it in books and sample programs, where people are trying to avoid “code explosion” of unnecessary detail.

The finally block, if present, is a “last chance to clean up” block. It is always executed—even if something in one of the other blocks did a “return!” The finally block is executed whether an exception occurred or not and whether it was caught or not. It is executed after the catch block, if present, and, regardless of the path taken, through the try block and the catch block.

The finally block can be useful in the complete absence of any exceptions. It is a piece of code that is executed irrespective of what happens in the try block. There may be numerous paths through a large and complicated try block. The finally block can contain the housekeeping tasks that must always be done (counts updated, locks released, and so on) when finishing this piece of code.

After the whole try ... catch ... finally series of blocks is executed, if one of those blocks doesn't divert it, execution continues after the last catch or finally (whichever is present). The kinds of things that could make execution divert to elsewhere are the regular things: a continue, break, return, or the raising of a different exception. If a finally clause also has a transfer of control statement, then that is the one that is obeyed.

Handling groups of related exceptions

We mentioned before that “matching an argument” means that the thrown exception can be assigned legally to the argument exception. This permits a refinement. It allows a handler to catch any of several related exception objects with common parentage. Look at this example:

class Grumpy extends Exception {}
class TooHot   extends Grumpy {}
class TooTired extends Grumpy {}
class TooCross extends Grumpy {}
class TooCold  extends Grumpy {}
    /* more code */

      try {
        if ( temp > 40 ) throw (new TooHot() );
        if ( sleep < 8 ) throw (new TooTired() );
      }
      catch (Grumpy g) {
           if (g instanceof TooHot)
              {System.out.println("caught too hot!"); return;}
           if (g instanceof TooTired)
              {System.out.println("caught too tired!"); return;}
      }
      finally {System.out.println("in the finally clause.");}
   }

The catch clauses are checked in the order in which they appear in the program. If there is a match, then the block is executed. The instanceof operator can be used to learn the exact identity of the exception.

How the exception propagates if not handled in the method where it was thrown

If none of the catch clauses match the exception that has been thrown, then the finally clause is executed (if there is one). At this point (no handler for this exception), what happens is the same as if the exception was raised and there wasn't a try statement. The flow of control abruptly leaves this method, and a premature return is done to the method that called this one. If that call was in the scope of a try statement, then we look for a matching exception again, and so on.

Figure 10-4 shows what happens when an exception is not dealt within the routine where it occurs. The run-time system looks for a “try . . . catch” block further up the call chain, enclosing the method call that brought us here. If the exception propagates all the way to the top of the call stack without finding a matching exception handler, then execution ceases with a message. You can think of this as Java setting up a default catch block for you around the program entry point that just prints an error message and quits.

Figure 10-4. The result of an exception not handled within the occurring routine

image

There is no overhead to putting some statements in a try statement. The only overhead comes when an exception occurs.

How and why methods declare the exceptions that can propagate out of them

Earlier we mentioned that a method must either catch the exception that it throws or declare it along with its signature,[1] meaning it must announce the exception to the outside world. This is so that anyone who writes a call to that method is alerted to the fact that an exception might come back instead of a normal return. It also allows the compiler to check that you are using exceptions consistently and have covered all the cases.

When a programmer sees that a method can raise an exception, he or she makes the choice between handling the exception or allowing it to propagate further up the call stack. A method declares the exceptions that might be thrown out of it by listing them in a throws clause like this:

public static int parseInt(String s, int radix)
                          throws NumberFormatException {
     /* body of method */
}

If the routine can throw more than one exception, list them all, separating the names with commas. The names must be exception or error names (that is, any type that is assignable to the predefined type Throwable). Note that just as a method declaration specifies the return type, it specifies the exception type that can be thrown, rather than an exception object.

The rules for how much and what must match, when one method that throws an exception overrides another, work in the obvious way. Namely, if you never do this, you will never obviously be bothered by it. Well, OK, another way to think about it is to consider the exception as an extra parameter that must be assignment-compatible with the exception in the class being overridden.

Fancy exception stuff

When you create a new exception by subclassing an existing exception class, you have the chance to associate a message string with it. The message string can be retrieved by a method. Usually, the message string will be some kind of more detailed indication about what's gone wrong.

class OutofGas extends Exception {
     OutofGas(String s) {super(s);}  // constructor
}

    /* more code */
    // in use, it may look like this
    try {
        if (fuel<1) throw  new OutofGas("less than 1 litre fuel");
    }
    catch ( OutofGas o) {
         System.out.println( o.getMessage() );
    }

At run-time, if there is less than one unit of fuel where it is tested, the OutOfGas exception will be thrown, and the handler will cause this message to appear:

less than 1 litre fuel

You might log the message, or write it to a diagnostics file instead of displaying it. Another method that is inherited from the superclass Throwable is printStackTrace().

Invoking this method on an exception will cause the call chain at the point where the exception was thrown (not where it is being handled) to be printed out. For example:

// catching an exception in a calling method

class test5p {
   static int myArray[] = {0,1,2,3,4};

   public static void main(String[] a) {
      try {
          bob();
      } catch (Exception e) {
          System.out.println("caught exception in main()");
          e.printStackTrace;
      }
   }

   static void bob() {

      try {
         myArray[-1] = 4; //obvious out of bounds exception
      }
      catch (NullPointerException e) {
         System.out.println("caught a different exception");
      }

   }
}

We have a handler for null pointer, but that has nothing to do with the exception that occurred. So the exception propagated back to the top and execution ended. At run-time it will produce output like this:

caught exception in main()
java.lang.ArrayIndexOutOfBoundsException: -1
     at test.bob(test5p.java:19)
     at test.main(test5p.java:9)

Summary of exceptions

  • Their purpose is to allow safer programming by providing a distinct path to deal with errors.

  • Many methods in the API throw exceptions, so you can't avoid using them. Do define your own error conditions. But don't over-use them and make them do the work that an if statement should do. Exceptions are a useful tool for organized handling of true error conditions.

  • The main use of exceptions is getting a decent error message explaining what failed, where, and why. It's a bit much to always expect recovery. Graceful degradation is often the most you can obtain.

We will next review the assert statement.


[1] The exceptions a method throws are not part of the signature, though.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.17.27