slanted W3C logo

Day 34 — Exceptions

Recap

Exceptions are exceptional events that disrupt the normal flow of a program.

An exception is thrown when a method hands an exception object to the runtime system; the exception object contains information about the error that caused the exception to be thrown.

The exception can be caught by an exception handler. An exception handler is a block of code that receives the exception object after it is thrown.

If there is no appropriate handler for the exception that was thrown, the program will stop execution in a largely uncontrolled fashion (it will crash).

The Exception Hierarchy

Java uses objects to represent exceptions. Like all other Java objects, the hierarchy is rooted at Object:


Throwable


Throwable is the superclass for all exception and error objects in Java. Only objects that are substitutable for Throwable may be thrown or caught.

Throwable defines all of the methods for exceptions and errors. In fact, if you look at the APIs for its subclasses, you will find no new methods for the subclasses.

Throwable

Throwable defines several methods to access the information about the error or exception. There are two you should know about for CSE1020:

String getMessage()
Returns the detail message string of this throwable.

try
{
   String s = "hello";
   s.substring(s.length() + 2);
}
catch (IndexOutOfBoundsException ex)
{
   System.out.println(ex.getMessage());
}

The above code fragment prints:

String index out of range: -2

Throwable

void printStackTrace()
Prints this throwable and all of the methods that were invoked to cause the throw.

try
{
   String s = "hello";
   s.substring(s.length() + 2);
}
catch (IndexOutOfBoundsException ex)
{
   ex.printStackTrace();
}

The above code fragment prints:

java.lang.StringIndexOutOfBoundsException: String index out of range: -1
        at java.lang.String.substring(String.java:1938)
        at java.lang.String.substring(String.java:1905)
        at Ex.main(Ex.java:7)

Error

An Error represents an exceptional condition that is external to the application, and that the application usually cannot anticipate or recover from. An example would be hardware failure (hard drive failure, out of memory, etc.).

There usually isn't anything the programmer can do if an Error is thrown; thus, it usually makes no sense to attempt to catch Errors and the compiler does not force the programmer to catch them.

Exception

An Exception object represents an unusual event that is internal to the application (ie. something is wrong with the Java code). There are two kinds of exceptions: checked and unchecked.

All exceptions, except subclasses of Error and RuntimeException, are checked exceptions. Probably most of the exceptions you have seen in your own code are unchecked exceptions.

Checked exceptions are unique to Java, and they are somewhat controversial. Other programming languages that use exceptions use unchecked exceptions.

Checked Exceptions

A checked exception is one that every well-written program should anticipate and handle.

The Java compiler enforces a Catch or Specify requirement whenever you use a method that may throw a checked exception. Your code must either:

  1. call the method from inside a try block that has a catch block that can handle the exception, or
  2. your main method must declare that it throws the exception

Checked Exceptions

You have already seen one common example of a checked exception. Whenever you previously created a Scanner for a File object you had to alter the header of your main method:

public static void main(String[] args) throws IOException
{
   Scanner fileInput = new Scanner(new File(someFileName));
}

If you look at the Scanner API, you will see that its constructor advertises that it throws a FileNotFoundException, which is a checked exception. If someFileName is the name of a non-existant file, the constructor call will throw an exception that the programmer must handle.

Checked Exceptions

Any method or constructor that throws a checked exception must declare so in its header:

public Scanner(File source) throws FileNotFoundException
Constructs a new Scanner that produces values scanned from the specified file...

Parameters:
   source - A file to be scanned
Throws:
   FileNotFoundException - if source is not found

RuntimeException

The third kind of exception is the runtime exception. These are exceptional conditions that are internal to the application, and that the application usually cannot anticipate or recover from. These usually indicate programming bugs, such as logic errors or improper use of an API.

—The Java Tutorials: Exceptions

Runtime exceptions help the programmer find programming errors in their code by telling the programmer where the exception has occurred and the sequence of methods that caused the exception to occur.

RuntimeException and all of its subclasses are unchecked exceptions.

Unchecked Exceptions

An unchecked exception is an exception that the compiler does not force the programmer to handle. Forcing the programmer to catch every possible runtime exception even for perfectly written code would be very tedious and would reduce the clarity of the program. Because unchecked exceptions are caused by programming errors, they can be eliminated by careful design, implementation, and debugging.

output.println("Enter a string : ");
String userInput = input.next();

// throws StringIndexOutOfBoundsException
//   if s.length() < 5
String s = userInput.substring(5);

// throws NumberFormatException
//   if userInput is not an integer
Integer i = Integer.parseInt(userInput);

// throws ArithmeticException
//   if i == 0
int quotient = 5 / i;

Unchecked Exceptions

Methods that throw unchecked exceptions may or may not declare so in their headers.

String.charAt may throw an IndexOutOfBoundsException but it does not declare so in its header.

Integer.parseInt may throw an NumberFormatException and it declares so in its header.

Handling Multiple Exceptions

It is common to have a block of code that contains many method invocations that could throw exceptions. Also, many methods can throw more than one kind of exception. In such circumstances, you can use more than one catch block.

You need to remember three things when using multiple catch blocks:

  1. The catch blocks are scanned in the order that they appear, and the first block that can handle the thrown exception is chosen as the handler.
  2. All catch blocks must be reachable; if not, then the compiler will issue an error.
  3. You cannot catch a checked exception that is not thrown by any of the methods in the try block. Doing so leads to a compiler error.

1. catch blocks are scanned in order

Because exceptions are objects, substitutability comes into play for exception handlers. Consider the following inheritance relationship:


and the following program ExSub.java:

import java.io.PrintStream;

public class ExSub
{
   public static void main(String[] args)
   {
      PrintStream output = System.out;
      
      try
      {
         char c = args[0].charAt(2);
      }
      catch (StringIndexOutOfBoundsException e)
      {
         output.println("Whoops, string is too short");
      }
      catch (IndexOutOfBoundsException e)
      {
         output.println("Whoops, no command line arguments");
      }
   }
}

1. catch blocks are scanned in order

If the program is run like so:

java ExSub

then there are no command line arguments (args has length zero) and attempting to access any element in args results in an ArrayIndexOutOfBoundsException being thrown. The first try block is examined to see if it can handle the exception:

      try
      {
         char c = args[0].charAt(2);
      }
      catch (StringIndexOutOfBoundsException e)
      {
         output.println("Whoops, string is too short");
      }
      catch (IndexOutOfBoundsException e)
      {
         output.println("Whoops, no command line arguments");
      }

1. catch blocks are scanned in order

ArrayIndexOutOfBoundsException is not substitutable for StringIndexOutOfBoundsException, so the second try block is examined:

      try
      {
         char c = args[0].charAt(2);
      }
      catch (StringIndexOutOfBoundsException e)
      {
         output.println("Whoops, string is too short");
      }
      catch (IndexOutOfBoundsException e)
      {
         output.println("Whoops, no command line arguments");
      }

1. catch blocks are scanned in order

ArrayIndexOutOfBoundsException is substitutable for IndexOutOfBoundsException, so control flows to the handler:

      try
      {
         char c = args[0].charAt(2);
      }
      catch (StringIndexOutOfBoundsException e)
      {
         output.println("Whoops, string is too short");
      }
      catch (IndexOutOfBoundsException e)
      {
         output.println("Whoops, no command line arguments");
      }

2. All catch blocks must be reachable

Substitutability affects the order in which you must place the catch blocks. What happens if we reverse the order the handlers?

      // THIS WILL NOT COMPILE
      try
      {
         char c = args[0].charAt(2);
      }
      catch (IndexOutOfBoundsException e)
      {
         output.println("Whoops, no command line arguments");
      }
      catch (StringIndexOutOfBoundsException e)
      {
         output.println("Whoops, string is too short");
      }

2. All catch blocks must be reachable

      // THIS WILL NOT COMPILE
      try
      {
         char c = args[0].charAt(2);
      }
      catch (IndexOutOfBoundsException e)
      {
         output.println("Whoops, no command line arguments");
      }
      catch (StringIndexOutOfBoundsException e)
      {
         output.println("Whoops, string is too short");
      }

Because of Rule 1 (blocks are scanned in order), all exceptions substitutable for IndexOutOfBoundsException will be caught by the first block; unfortunately, StringIndexOutOfBoundsException is one such exception.

We say that the StringIndexOutOfBoundsException handler is unreachable, and the compiler will issue a compilation error.

3. Checked exceptions that cannot be thrown

As a programmer, you must deal with all checked exceptions, but you cannot include a handler for an checked exception that is not thrown:

      // THIS WILL NOT COMPILE
      try
      {
         char c = args[0].charAt(2);
      }
      catch (IOException e)
      {
      }

All IOExceptions are checked exceptions, but no method in the try block can throw such an exception. The compiler will flag this as a compilation error.

Note that Java does not require unchecked exceptions to appear in the method header so there is no way for the compiler to verify if a method throws an unchecked exception; thus, you can catch whatever unchecked exception you want, even if the code in the try block can never cause such an exception to occur.