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
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.
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.
There are parts of Java’s machinery for exceptions that require understanding some details that are beyond the scope of this course, but we can do many useful things without needing to know too much more.
Our textbook covers exceptions in chapter 9. You should be able to understand most of that chapter except the section about “Defining Exception Classes” and some of the sentences that talk about “subclasses.” It should be enough to understand that “subclass” is another way of talking about subtypes, as when a class implements an interface.
The first new mechanism that we need to understand is the try statement and its clauses. Its most basic form looks like this:
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:
An exception (the thing that is thrown) is a Java object that is an instance of a class—a class that is a subtype of the predefined interface Throwable.
The exception specifier 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.
What the hierarchy means for us is that we can count on the type Exception matching any exception that occurs. 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
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).
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.
The exceptions we have seen, as for an index out of bounds, are subtypes of RuntimeException, itself a subtype of Exception. Here is a listing of a few of predefined exception types that we are likely to have seen or to see in the near future.
Note that some of the names begin with ’java.util’ or ’java.io’; 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, an ArrayIndexOutOfBoundsException will match as any of ArrayIndexOutOfBoundsException, IndexOutOfBoundsException, RuntimeException, or Exception.
You are allowed to specify more than one catch clause:
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 this statement.
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. Throwing an exception therefore provides another way to leave a method—or a loop.
There is one more clause that can be placed at the end of a try statement:
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.
For an exception class that has already been defined, we can throw an instance using a throw statement:
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.
In the list of predefined exceptions, we noted that there are subtype relationships, and that we can use those relationships so that a catch clause matches a particular set of exceptions.
In the code above, the clause matches a particular exception if the exception is an instance of the specified type—in this case, if it is of type SomeExceptionType or one of its subtypes.
We have previously subtypes by either having a class implement an interface
or by having an interface extend another interface
In either case, the subtype must provide all of the methods from the supertype, but it can add more methods and instance variables as needed. These relationships are not very complicated, because the supertype is always an interface, which leaves it to the subtype class to specify instance variables and to provide implementations of the methods.
Java also allows a class to extend another class
This can be more complicated, because it allows the subtype to inherit definitions of instance variables and implementations of methods from the supertype. This can be very useful, because it lets you avoid writing indentical or nearly-identical methods for related classes. Learning to use this capability well goes beyond what we can cover in this course, and misusing can make for some really ugly programs.1 So we will take advantage of this feature only to define our own exception types—without needing to understand most of the machinery they require.
To hook our own exception type into the Java machinery, we define it as extending some existing exception type. To fit in, we should define at least two constructors, one of which takes no parameters, the other of which takes a single String as a message. That will generally look something like this:
The calls to super() invoke the constructor of the supertype. Most of the time, we’ll choose to extend the class Exception.
We can add other constructors or other methods, and we can even add instance variables. But we probably won’t need that during this semester.
All predefined exceptions and errors have two methods that we may find useful:
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.
Defining our exceptions as described above provides them with these methods–without us having to write them.
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
More than one exception type can be listed, separating them by commas.
Every method in Java automatically has the equivalent of the clause
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.
1The power and the potential for a mess grow even more when you use multiple inheritance, in which a class extends more than one subclass.