This handout repeats and expands on material from 21 Sep.

Assertions

When we have written methods, we have often documented conditions that are the part of the contract for using the method. What must be true before the method is called is the precondition, what the method promises will be true upon return is the postcondition. There are also other points in a program at which certain conditions should be true.

Java allows us to both document and check conditions using assert statements. All we do is follow the reserved word assert with a Boolean expression, as in

    assert i >= 0;

The only strict requirement is that the expression must be compilable; in practice, it should also be something that is not extremely expensive to evaluate.

These statements function like comments, in that they make it clear to the reader that the condition must be true. You can tell Java to check that all assertions are true by adding the argument -enableassertions after the java command.

Exceptions in Java

When an unusual event occurs—such as trying to use an out-of-bounds index to access an array—Java turns the event into an exception. In Java’s vocabulary, we would say that the event throws the exception. What we have seen so far is that a program that throws an exception normally ends its execution immediately, printing a message about the exception.

Java provides ways that the methods you write can define new kinds of exceptions, throw exceptions under appropriate circumstances, and even catch an exception to handle it. This can be used to make a program easier to understand, because it separates the code for dealing with unusual events from what happens normally. For now, we will limit our concern to catching exceptions.

Handling exceptions

The try statement

The first new mechanism that we need to understand is the try statement and its clauses. Its most basic form looks like this:

    try {
        statements-1
    }
    catch (exception-specifier) {
        statements-2
    }

The first part is called the try block; the second half is called a catch clause. In the normal case, the statements of the try block are executed in order, as would be the case for any block. However, if any of those statements throws an exception that matches the exception specifier, we say that the clause catches it, and the statements in the catch clause are executed, and then whatever follows the whole statement. If there is an exception thrown that does not match the specifier, then it is as if the entire try statement throws the exception.

The behavior that we have previously seen is what happens when we do not provide any handlers; it is as if the main() method were called from inside a try like this:

    try {
        call the main method
    }
    catch (Exception e) {
        print information about the exception
        terminate the program
    }

An exception (the thing that is thrown) is a Java object that is an instance of one of the exception classes.

Matching exceptions

The exception specifier in a try clause looks like the declaration of a formal paramter for a method; it is made up of a type name and a parameter name (with e being a very common choice for the parameter name). A catch clause matches an exception when the exception object is an instance of the specified type. The many different exceptions that are predefined in Java form a hierarchy of types; the ones that we have encountered so far are all subtypes of either Exception or Error. (The difference between the two is that the events indicated by an Error class are ones that are almost certainly a mistake by the programmer. It is very unusual to want to catch an error class.) The exceptions we have seen, as for an index out of bounds, are subtypes of RuntimeException, itself a subtype of Exception. Later, when we begin to define and throw our own exceptions, we will make them subtypes of the class Exception, too.

Predefined exceptions

The many different exceptions that are predefined in Java form a hierarchy of types; the ones that we have encountered so far are all subtypes of either Exception or Error. The difference between the two is that the events indicated by an Error class are ones that are almost certainly a mistake by the programmer. It is very unusual to want to catch an error class. Both Exception and Error are subtypes of the class Throwable.

Here is a list of the predefined exceptions that you are likely to have seen (or see in the near future):

Throwable
  Error
  Exception
    java.io.IOException
    RuntimeException
      ArithmeticException
      IllegalArgumentException
      IllegalStateException
      IndexOutOfBoundsException
        ArrayIndexOutOfBoundsException
        StringIndexOutOfBoundsException
      java.util.NoSuchElementException
        java.util.InputMismatchException
      NullPointerException

Note that some of the names begin with ’java.io’ or ’java.util’; you will need to import those names (the same way we import java.util.Scanner) to use them.

The indentation in the list above indicates subclass relationships; the fact that ArrayIndexOutOfBoundsException is indented under IndexOutOfBoundsException indicates that the former is a subclass of the latter. To put that more simply, when a ArrayIndexOutOfBoundsException is thrown, it will be caught by an exception specifier that is any of ArrayIndexOutOfBoundsException, IndexOutOfBoundsException, RuntimeException or Exception.

What the hierarchy means for us is that we can count on the type RuntimException matching any of these exceptions. If we want to match a more specific subtype, we use that name of its class in the catch clause’s exception specifier. Thus in

    try {
        statements-1
    }
    catch (NullPointerException e) {
        ...
    }

the catch will match only exceptions that are instances of NullPointerException, while ignoring others, such as an ArithmeticException (which would be thrown by, say, a division by zero).

Additional clauses

You are allowed to specify more than one catch clause:

    try {
        statements-1
    }
    catch (exception-specifier) {
        statements-2
    }
    catch (exception-specifier) {
        statements-3
    }

The catch clauses are tested in the order in which they appear, and the handler block for the first match is executed. That means that you should be careful to list you handlers in order from more specific to more general.

Note that the handler blocks are not inside the try block; if a handler block throws an exception, it will not be caught by any of the handlers in the same statement.

Nesting

You can put a try-catch inside another try block. The enclosing statement gets a chance to catch any exceptions that are not caught by the one inside. If no matching catch of an exception is found within the method where it is thrown, then the call to that method will throw the same exception, so that the search for a matching catch continues in the caller. An uncaught exception therefore provides another way to leave a method—or a loop.

finally

There is one more clause that can be placed at the end of a try statement:

    try {
        statements-1
    }
    catch (exception-specifier) {
        statements-2
    }
    catch (exception-specifier) {
        statements-3
    }
    ...
    finally {
        statements-n
    }

The finally clause is special: its statements will be executed after leaving the try block by any means at all–whether execution reaches the end of the block, an exception is thrown (whether or not caught), or even by a return statement. The finally clause is therefore useful for anything that absolutely must happen; it may be a few more weeks before we see a compelling need for that.

Throwing exceptions

For an exception class that has already been defined, we can throw an instance using a throw statement:

    throw new ArithmeticException("Oops.");

There are two things that we need to notice about this. First, we need to throw an instance, so we use new with the exception type’s constructor. Second, unless we dig through the documentation to find otherwise, we will usually provide a String parameter for the constructor, which will be the message to associate with it.

Inside a catch block, we might sometimes want to re-throw the exception that it caught.

    try {
        ... something ....
    }
    catch (ArithmeticException e) {
        ... do something about the exception ...
        throw e;
    }

Useful methods

All predefined exceptions and errors have two methods that we may find useful:

      public String toString();
      public String getMessage();

The first one is useful if we want to print the exception in its usual form; the latter can be used if we want to construct a better message for the user.

Catch or declare

What types of exceptions a method might throw are part of its interface. We specify them by adding a throws clause to the method’s heading, as in

    public static gcd(int m, int n) throws InvalidArgumentException 
        ... body of the method
    

More than one exception type can be listed, separating them by commas.

Every method in Java automatically has the equivalent of the clause

      ... throws RuntimeException, Error ...

The difference between the type Exception and its subtype RuntimeException is that the latter are possible so many places that declaring them would be too much clutter.

Whenever we write a statement that might throw an exception, we must either place that statement in a try block with a matching catch clause, or we must add the exception type (or some supertype) to the method’s throws clause.

The textbook calls this requirement the “catch or declare rule”. When we create our own exception types, we will usually make them subtypes of Exception rather than RuntimeException, so that we will have to declare unhandled exceptions in the method definitions.