Saturday 11 March 2017

Exception-Handling Fundamentals, Using try and catch & Multiple catch Clauses - Java Tutorials

Exception-Handling Fundamentals

A Java exception is an object that describes an exceptional (that is, error) condition that has occurred in a piece of code. When an exceptional condition arises, an object representing that exception is created and thrown in the method that caused the error. That method may choose to handle the exception itself, or pass it on. Either way, at some point, the exception is caught and processed. Exceptions can be generated by the Java run-time system, or they can be manually generated by your code. Exceptions thrown by Java relate to fundamental errors that violate the rules of the Java language or the constraints of the Java execution environment. Manually generated exceptions are typically used to report some error condition to the caller of a method.

Java exception handling is managed via five keywords: try, catch, throw, throws, and finally. Briefly, here is how they work. Program statements that you want to monitor for exceptions are contained within a try block. If an exception occurs within the try block, it is thrown. Your code can catch this exception (using catch) and handle it in some rational manner. System-generated exceptions are automatically thrown by the Java run-time system. To manually throw an exception, use the keyword throw. Any exception that is thrown out of a method must be specified as such by a throws clause. Any code that absolutely must be executed before a method returns is put in a finally block.

This is the general form of an exception-handling block:

  try {
    // block of code to monitor for errors
  }

  catch (ExceptionType1 exOb) {
    // exception handler for ExceptionType1
  }
  catch (ExceptionType2 exOb) {
    // exception handler for ExceptionType2
  }
  // ...
    finally {
    // block of code to be executed before try block ends
  }

Here, ExceptionType is the type of exception that has occurred. The remainder of this chapter describes how to apply this framework.


Exception Types

All exception types are subclasses of the built-in class Throwable. Thus, Throwable is at the top of the exception class hierarchy. Immediately below Throwable are two subclasses that partition exceptions into two distinct branches. One branch is headed by Exception. This class is used for exceptional conditions that user programs should catch. This is also the class that you will subclass to create your own custom exception types. There is an important subclass of Exception, called RuntimeException. Exceptions of this type are automatically defined for the programs that you write and include things such as division by zero and invalid array indexing.

The other branch is topped by Error, which defines exceptions that are not expected to be caught under normal circumstances by your program. Exceptions of type Error are used by the Java run-time system to indicate errors having to do with the run-time environment, itself. Stack overflow is an example of such an error. This chapter will not be dealing with exceptions of type Error, because these are typically created in response to catastrophic failures that cannot usually be handled by your program.


Uncaught Exceptions

Before you learn how to handle exceptions in your program, it is useful to see what happens when you don’t handle them. This small program includes an expression that intentionally causes a divide-by-zero error.

  class Exc0 {
    public static void main(String args[]) {
      int d = 0;
      int a = 42 / d;
    }
  }

When the Java run-time system detects the attempt to divide by zero, it constructs a new exception object and then throws this exception. This causes the execution of Exc0 to stop, because once an exception has been thrown, it must be caught by an exception handler and dealt with immediately. In this example, we haven’t supplied any exception handlers of our own, so the exception is caught by the default handler provided by the Java run-time system. Any exception that is not caught by your program will ultimately be processed by the default handler. The default handler displays a string describing the exception, prints a stack trace from the point at which the exception occurred, and terminates the program.

Here is the output generated when this example is executed.

  java.lang.ArithmeticException: / by zero
           at Exc0.main(Exc0.java:4)

Notice how the class name, Exc0; the method name, main; the filename, Exc0.java; and the line number, 4, are all included in the simple stack trace. Also, notice that the type of the exception thrown is a subclass of Exception called ArithmeticException, which more specifically describes what type of error happened. As discussed later in this chapter, Java supplies several built-in exception types that match the various sorts of run-time errors that can be generated.

The stack trace will always show the sequence of method invocations that led up to the error. For example, here is another version of the preceding program that introduces the same error but in a method separate from main( ):

  class Exc1 {
    static void subroutine() {
      int d = 0;
      int a = 10 / d;
    }
    public static void main(String args[]) {
      Exc1.subroutine();
    }
  }

The resulting stack trace from the default exception handler shows how the entire call stack is displayed:

  java.lang.ArithmeticException: / by zero
      at Exc1.subroutine(Exc1.java:4)
      at Exc1.main(Exc1.java:7)

As you can see, the bottom of the stack is main’s line 7, which is the call to subroutine( ), which caused the exception at line 4. The call stack is quite useful for debugging, because it pinpoints the precise sequence of steps that led to the error.




Using try and catch

Although the default exception handler provided by the Java run-time system is useful for debugging, you will usually want to handle an exception yourself. Doing so provides two benefits. First, it allows you to fix the error. Second, it prevents the program from automatically terminating. Most users would be confused (to say the least) if your program stopped running and printed a stack trace whenever an error occurred! Fortunately, it is quite easy to prevent this.

To guard against and handle a run-time error, simply enclose the code that you want to monitor inside a try block. Immediately following the try block, include a catch clause that specifies the exception type that you wish to catch. To illustrate how easily this can be done, the following program includes a try block and a catch clause which processes the ArithmeticException generated by the division-by-zero error:

  class Exc2 {
    public static void main(String args[]) {
      int d, a;
    try { // monitor a block of code.
        d = 0;
        a = 42 / d;
        System.out.println("This will not be printed.");
     } catch (ArithmeticException e) { // catch divide-by-zero error
        System.out.println("Division by zero.");
      }
      System.out.println("After catch statement.");
    }
  }

This program generates the following output:

  Division by zero.
  After catch statement.

Notice that the call to println( ) inside the try block is never executed. Once an exception is thrown, program control transfers out of the try block into the catch block. Put differently, catch is not “called,” so execution never “returns” to the try block from a catch. Thus, the line “This will not be printed.” is not displayed. Once the catch statement has executed, program control continues with the next line in the program following the entire try/catch mechanism.

A try and its catch statement form a unit. The scope of the catch clause is restricted to those statements specified by the immediately preceding try statement. A catch statement cannot catch an exception thrown by another try statement (except in the case of nested try statements, described shortly). The statements that are protected by try must be surrounded by curly braces. (That is, they must be within a block.) You cannot use try on a single statement.

The goal of most well-constructed catch clauses should be to resolve the exceptional condition and then continue on as if the error had never happened. For example, in the next program each iteration of the for loop obtains two random integers. Those two integers are divided by each other, and the result is used to divide the value 12345. The final result is put into a. If either division operation causes a divide-by-zero error, it is caught, the value of a is set to zero, and the program continues.

  // Handle an exception and move on.
  import java.util.Random;

  class HandleError {
    public static void main(String args[]) {
      int a=0, b=0, c=0;
      Random r = new Random();

      for(int i=0; i<32000; i++) {
        try {
          b = r.nextInt();
          c = r.nextInt();
          a = 12345 / (b/c);
        } catch (ArithmeticException e) {
          System.out.println("Division by zero.");
          a = 0; // set a to zero and continue
        }
        System.out.println("a: " + a);
      }
    }
  }


Displaying a Description of an Exception

Throwable overrides the toString( ) method (defined by Object) so that it returns a string containing a description of the exception. You can display this description in a println( ) statement by simply passing the exception as an argument. For example, the catch block in the preceding program can be rewritten like this:

  catch (ArithmeticException e) {
    System.out.println("Exception: " + e);
    a = 0; // set a to zero and continue
  }

When this version is substituted in the program, and the program is run, each divide-by-zero error displays the following message:

  Exception: java.lang.ArithmeticException: / by zero

While it is of no particular value in this context, the ability to display a description of an exception is valuable in other circumstances—particularly when you are experimenting with exceptions or when you are debugging.




Multiple catch Clauses

In some cases, more than one exception could be raised by a single piece of code. To handle this type of situation, you can specify two or more catch clauses, each catching a different type of exception. When an exception is thrown, each catch statement is inspected in order, and the first one whose type matches that of the exception is executed. After one catch statement executes, the others are bypassed, and execution continues after the try/catch block. The following example traps two different exception types:

  // Demonstrate multiple catch statements.
  class MultiCatch {
    public static void main(String args[]) {
      try {
        int a = args.length;
        System.out.println("a = " + a);
        int b = 42 / a;
        int c[] = { 1 };
        c[42] = 99;
      } catch(ArithmeticException e) {
        System.out.println("Divide by 0: " + e);
      } catch(ArrayIndexOutOfBoundsException e) {
        System.out.println("Array index oob: " + e);
      }
      System.out.println("After try/catch blocks.");
    }
  }

This program will cause a division-by-zero exception if it is started with no commandline parameters, since a will equal zero. It will survive the division if you provide a command-line argument, setting a to something larger than zero. But it will cause an ArrayIndexOutOfBoundsException, since the int array c has a length of 1, yet the program attempts to assign a value to c[42].

Here is the output generated by running it both ways:

  C:\>java MultiCatch
  a = 0
  Divide by 0: java.lang.ArithmeticException: / by zero
  After try/catch blocks.

  C:\>java MultiCatch TestArg
  a = 1
  Array index oob: java.lang.ArrayIndexOutOfBoundsException
  After try/catch blocks.

When you use multiple catch statements, it is important to remember that exception subclasses must come before any of their superclasses. This is because a catch statement that uses a superclass will catch exceptions of that type plus any of its subclasses. Thus, a subclass would never be reached if it came after its superclass. Further, in Java, unreachable code is an error. For example, consider the following program:

  /* This program contains an error.

     A subclass must come before its superclass in
     a series of catch statements. If not,
     unreachable code will be created and a
     compile-time error will result.
  */
  class SuperSubCatch {
    public static void main(String args[]) {
      try {
        int a = 0;
        int b = 42 / a;
      } catch(Exception e) {
        System.out.println("Generic Exception catch.");
      }
      /* This catch is never reached because
         ArithmeticException is a subclass of Exception. */
      catch(ArithmeticException e) { // ERROR - unreachable
        System.out.println("This is never reached.");
      }
    }
  }

If you try to compile this program, you will receive an error message stating that the second catch statement is unreachable because the exception has already been caught. Since ArithmeticException is a subclass of Exception, the first catch statement will handle all Exception-based errors, including ArithmeticException. This means that the second catch statement will never execute. To fix the problem, reverse the order of the catch statements.

No comments:

Post a Comment