slanted W3C logo

Day 33 — Exceptions

Exceptions

A successfully compiled program can still fail when it is run. Hopefully, such failures are rare occurrences.

The term exception is shorthand for the phrase "exceptional event." An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.

Client Caused Exceptions

The client programmer can cause errors to occur by violating preconditions or making logic errors:

List someIntegers = new ArrayList<Integer>();
int x = someIntegers.get(0);
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
        at java.util.ArrayList.rangeCheck(ArrayList.java:571)
        at java.util.ArrayList.get(ArrayList.java:349)
        at RangeEx.main(RangeEx.java:14)

Client Caused Exceptions

The client programmer can cause errors to occur by violating preconditions or making logic errors:

String s = "hello";
String t = s.substring(6);
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: -1
        at java.lang.String.substring(String.java:1949)
        at java.lang.String.substring(String.java:1916)
        at StringEx.main(StringEx.java:9)

Client Caused Exceptions

The client programmer can cause errors to occur by violating preconditions or making logic errors:

List<Integer> someIntegers = new Array<Integer>();

int sum = 0;
for (Integer i : someIntegers)
{
   sum += i;
}
int average = sum / someIntegers.size();
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at ArithmeticEx.main(ArithmeticEx.java:15)

Client Caused Exceptions

The client programmer can cause errors by failing to validate user input:

Enter a number: abc
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.lang.Integer.parseInt(Integer.java:481)
        at java.lang.Integer.parseInt(Integer.java:514)
        at ParseEx.main(ParseEx.java:10)

Environment Errors

The runtime environment that the program executes in can cause errors to occur.

String filename = "unreadable.txt";
Scanner fileInput = new Scanner(new File(filename));
Exception in thread "main" java.io.FileNotFoundException: unreadable.txt (Permission denied)
        at java.io.FileInputStream.open(Native Method)
        at java.io.FileInputStream.(FileInputStream.java:137)
        at java.util.Scanner.(Scanner.java:653)
        at FileEx.main(FileEx.java:9)

Similarly, if you try to write a file to a full hard drive an IOException will occur.

The Call Stack

List someIntegers = new ArrayList<Integer>();
int x = someIntegers.get(0);

What happens when RangeEx.main invokes ArrayList.get?

The Call Stack

  1. RangeEx.main invokes ArrayList.get
  2. ArrayList.get invokes ArrayList.rangeCheck
  3. ArrayList.rangeCheck checks if the passed index is valid


The Call Stack

In this example, the index is not valid, so an exception object is created inside ArrayList.rangeCheck. We say that the method throws an exception.


The Call Stack

From the Java Tutorials: What Is an Exception?

The runtime system searches the call stack for a method that contains a block of code that can handle the exception. This block of code is called an exception handler.

The search begins with the method in which the error occurred and proceeds through the call stack in the reverse order in which the methods were called. When an appropriate handler is found, the runtime system passes the exception to the handler.


The Call Stack


The Call Stack


The Call Stack

If no handler is found, the program terminates with an error message.

Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
        at java.util.ArrayList.rangeCheck(ArrayList.java:571)
        at java.util.ArrayList.get(ArrayList.java:349)
        at RangeEx.main(RangeEx.java:14)


Exception Terminology

When the virtual machine detects an invalid operation, it throws an exception. Throwing an exception involves creating an exception object that contains information about the error. The exception object is then handed to the Java runtime system.

When the runtime system receives an exception object, it searches for a block of code called an exception handler that can handle the exception. If such a block of code is found, the exception handler is said to catch the exception.

Thus far, you have not written any exception handlers. If the Java runtime system cannot find an exception handler, the program terminates in an uncontrolled fashion (it crashes).

A Simple Example

In Java, you use a try block to contain code that might throw an exception.

Immediately after the try block you use one or more catch blocks to handle any exceptions thrown by the try block.

int num = 0;
try
{
   num = Integer.parseInt(someString);
}
catch (NumberFormatException ex)
{
   output.println("not a number");
}

The Integer.parseInt contract tells you what kinds of exceptions it might throw.

Textbook Example

output.println("Enter a fraction");
String str = input.nextLine();
int slash = str.indexOf("/");
String left = str.substring(0, slash);
String right = str.substring(slash + 1);
int leftInt = Integer.parseInt(left);
int rightInt = Integer.parseInt(right);
int answer = leftInt / rightInt;
output.println("Quotient = " + answer);

All lines in red might cause an exception to be thrown.

Textbook Example

try
{
   output.println("Enter a fraction");
   String str = input.nextLine();
   int slash = str.indexOf("/");
   String left = str.substring(0, slash);
   String right = str.substring(slash + 1);
   int leftInt = Integer.parseInt(left);
   int rightInt = Integer.parseInt(right);
   int answer = leftInt / rightInt;
   output.println("Quotient = " + answer);
}
catch (IndexOutOfBoundsException e)
{
   output.println("No slash in input!");
}

Textbook Example

try
{
   output.println("Enter a fraction");
   String str = input.nextLine();
   int slash = str.indexOf("/");
   String left = str.substring(0, slash);
   String right = str.substring(slash + 1);
   int leftInt = Integer.parseInt(left);
   int rightInt = Integer.parseInt(right);
   int answer = leftInt / rightInt;
   output.println("Quotient = " + answer);
}
catch (IndexOutOfBoundsException e)
{
   output.println("No slash in input!");
}
catch (NumberFormatException e)
{
   output.println("Non-integer operands!");
}

Textbook Example

try
{
   output.println("Enter a fraction");
   String str = input.nextLine();
   int slash = str.indexOf("/");
   String left = str.substring(0, slash);
   String right = str.substring(slash + 1);
   int leftInt = Integer.parseInt(left);
   int rightInt = Integer.parseInt(right);
   int answer = leftInt / rightInt;
   output.println("Quotient = " + answer);
}
catch (IndexOutOfBoundsException e)
{
   output.println("No slash in input!");
}
catch (NumberFormatException e)
{
   output.println("Non-integer operands!");
}
catch (ArithmeticException e)
{
   output.println("Cannot divide by zero!");
}
output.println("Done!");

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 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.

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

      try
      {
         // throws IndexOutOfBoundsException
         "hello".substring(6);
      }
      catch (IndexOutOfBoundsException ex)
      {
         System.out.println(ex.getMessage());
      }

The above code fragment prints:

String index out of range: -1
      try
      {
         // throws IndexOutOfBoundsException
         "hello".substring(6);
      }
      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.

Checked Exceptions

A checked exception is one that every well-written program should anticipate and handle. The compiler forces the programmer to write an exception handler for such exceptions (or the programmer must indicate that their code throws an exception). If the programmer does not provide a handler, the compiler will issue a compilation error.

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.

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.

You have probably already encountered several different kinds of runtime exceptions:

      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;

Handling Checked Exceptions: try

The programmer must write a handler whenever a method that throws a checked exception is invoked. The first step in creating an exception handler is to place the code that can cause an exception in a try block:

   public static void main(String[] args)
   {
      // ...
      try
      {
         Scanner fileInput = new Scanner(
                                 new File(someFileName)
                                        );
         // read the file here
      }
   }

Handling Checked Exceptions: catch

The second step is to place the code that handles the exception in a catch block. The catch block goes directly after the try block; no code can go between the two blocks:

   public static void main(String[] args)
   {
      // ...
      try
      {
         Scanner fileInput = new Scanner(
                                 new File(someFileName)
                                        );
         // read the file here
      }
      catch (FileNotFoundException ex)
      {
         
      }
   }

The catch block says what type of exception it can handle, and it provides a name for the exception object. In this example, the catch block says that it can handle exceptions of type FileNotFoundException.

Handling Checked Exceptions: catch

The catch block contains code that is only run if the exception handler is invoked. In our example, we might print a message to the user that the file was not found:

   public static void main(String[] args)
   {
      // ...
      try
      {
         Scanner fileInput = new Scanner(
                                 new File(someFileName)
                                        );
         // read the file here
      }
      catch (FileNotFoundException ex)
      {
         output.printf("File named %s was not found.",
                       someFileName);  
      }
   }

Exception Handlers and Substitutability

Substitutability comes into play when you write an exception handler. The catch block:

      catch (FileNotFoundException ex)
      {
      }

says that it can handle an exception of type FileNotFoundException or any exception that is substitutable for FileNotFoundException.

Exception Handlers and Substitutability

FileNotFoundException is a subclass of IOException. The exception handler could have been written as:

      catch (IOException ex)
      {
      }

This catch block will handle an exception of type IOException or any exception that is substitutable for IOException. This means that it can handle FileNotFoundException because FileNotFoundException is substitutable for IOException; unfortunately, it also handles a lot of other exceptions as well.

Exception Handlers and Substitutability

The most general catch block that you can write is:

      catch (Throwable ex)
      {
      }

which will catch any Error or Exception. If you do this, you will have a hard time deciding what to do about the exceptional event because all you know is that a Throwable event has occurred; it would be far more useful if you knew exactly what kind of exception has occurred.

To Do For Next Lecture

Finish reading Chapter 11 if you haven't done so already.