Exceptions

“An exception is an abnormal condition that arises in a code sequence at run time.  In other words, an exception is a run-time error.”

A Java Exception is an object that is created dynamically at runtime and which describes some exceptional (not necessarily erroneous) condition that isn’t handled immediately when the exceptional condition occurs.

An Exception is an object that holds a message.

Exceptions are not used for all exceptional conditions.  For example, if you have an application that asks the user for an integer, say between 1 and 10, and the user enters 99, you’ll probably validate the input (check it’s value) and perhaps ask for another value.

On the other hand, suppose you wrote an application that asks the user for a file name, then calls a library method to attempt to open the file for.   What if when run, a user enters the name of a file that doesn’t exist?  The library method that tries to open the file can’t open a non-existent file for reading.  But the library method wasn’t written by you and doesn’t know how you want to handle that sort of error condition.

  • Do you want to ask the user to reenter the filename?
  • Do you want to pop up a file browser?
  • Do you want to open some predefined default file?
  • Do you want to skip the operation that uses the file?
  • Do you want to terminate the program?

These are all sensible choices in different scenarios.  To handle this uncertainty, the library method might create an exception object and throw it back to the code that called the library method so that the calling code can deal with the exceptional condition.

Contents

A Typical Situation

Suppose a method foo needs to call a method bar.  Also, suppose that bar inspects the values of the arguments that foo passes to bar.  If bar wants foo to handle the situation when bar receives invalid arguments, then bar can throw an exception and foo can catch it.

  1. In bar, when bar receives an invalid argument, bar creates an Exception object and throws it to foo.
  2. In foo, when foo wants to call bar, it calls bar in a try-catch block and if an Exception object is thrown from bar back to foo, the Exception object is caught in foo’s catch block.
void bar(...) throws ExceptionType {
    ...
    if (exceptional condition) {
        throw new ExceptionType();
    ...
}
void foo() {
    ...
    try {
        bar(...);
    } catch (ExceptionType e) {
        // handle exceptional condition
    }
    ...
}

The Throws Clause

Methods that are capable of throwing an exception must declare that they are capable of doing so, by including a throws clause after the method’s parameter list and include the exception type.

Finally Block

If in the calling method we have code that must be executed after the try block (no matter what), but whether or not an exception occurred, we can put that code in a finally block after the catch block.

try {
    ...
} catch (ExceptionType e) {
    ...
} finally {
    ...
}

The finally block will execute even if …

  • There is a return statement in the try block
  • An exception is thrown in the try block

Uncaught Exceptions

All exceptions must be handled immediately after they are thrown.  If an exception is not caught in a catch clause then it is caught by the runtime exception handler.  The runtime exception handler will display a string that describes the exception, display a stack trace, and then terminate the program.

Multiple Catch Clauses

If multiple exceptions can be caught in a try-catch block, you can catch each one in a separate catch blocks listed one after another.  If an exception is thrown, only one catch block will be executed (the first one having the appropriate exception type).  After the catch block is executed, the code following the try-catch blocks will be executed.  Since only one catch block is executed, you should list your catch block from most specific exception to the most general.

try {
    ...
} catch (ExceptionType1 e) {
    ...
} catch (ExceptionType2 e) {
    ...
}

If the exception handling code is the same for multiple exception types, we can also specify that a single catch block can catch multiple types of exceptions.  To do so, we list each of the exception types in a single catch clause separated by logical OR symbols (|).

try {
    ...
} catch (ExceptionType1 | ExceptionType2 e) {
   ...
}

Rethrowing Exceptions

A method that catches an exception can throw the same exception object (throw e).  If it does, the exception goes up the stack until it is caught (potentially by the runtime exception handler).

try {
    ...
} catch (ExceptionType e) {
    throw e;
}

Rethrowing From a Subclass Constructor

If a superclass’ constructor throws an exception, the subclass needn’t explicitly catch and rethrow the exception. Instead, the exception is automatically caught and rethrown by the subclass.

Recall that when a subclass calls the superclass’ constructor, it uses the super keyword.  Since the subclass needn’t catch and rethrow any exception from the superclass’ constructor, the use of super should not be in a try-catch block.

// Subclass MyClass constructor
public MyClass() throws ExceptionType {
    super();
    ...
}

Custom Exception Types

Exception objects are class objects and as such are defined in some Java class file.  All user-defined exceptions have types that extend the Exception class.

The simplest exception class has no fields or methods, only a constructor.

public class SimpleException extends Exception {
    public SimpleException() {}
}

Below is a test program that demonstrates its use.

public class Driver {

    public static void main(String[] args) {
        try {
            foo();
        } catch (SimpleException e) {
            System.out.println("Simple Exception caught!");
        }
    }

    private static void foo() throws SimpleException {
        // suppose exceptional condition occurred
        throw new SimpleException();
    }
}

When creating an exception object, we may want to store data in it, so the catching method can understand why the exception was thrown.   Below is an exception class that stores a String that is passed to the constructor.  The class also overrides toString to return the string that is stored.

public class MessageException extends Exception {
    private String message;

    public MessageException(String m) {
        message = m;
    }

    @Override
    public String toString() {
        return message;
    }
}

When the exception is created, we pass a String to the constructor indicating the error that occurred and when the exception is caught, we print the the String.

public class Driver {

    public static void main(String[] args) {
        try {
            foo();
        } catch (MessageException e) {
            System.out.println(e.toString());
       }
    }

    private static void foo() throws MessageException {
        // suppose exceptional condition occurred
        throw new MessageException("describe error here");
    }
}

Example

Suppose a text messaging application that you are working on needs a class named User to store a user’s username (String) and password (String). The constructor for the User class should have two parameters, the first for the username and the second for the password.  The constructor should throw an IllFormedUserException object if either the username or the password that are passed to the constructor are null.  The User class should also have accessor (getter) methods.

  1. Write an IllFormedUserException class with no fields or methods; only a constructor.
  2. Write a User class as defined above.
  3. Write a driver that shows:
    1. The User constructor does not throw an IllFormedUserException when non-null Strings are passed to it.
    2. The constructor throws an IllFormedUserException when a null string is passed as the first argument to the User constructor.
    3. The constructor throws an IllFormedUserException when a null string is passed as the second argument to the User constructor.
public class IllFormedUserException extends Exception {     
    private String message;  
   
    public IllFormedUserException(String m) {         
        message = m;     
    }     

    @Override  
    public String toString() {         
        return message;     
    } 
}
public class User {
    private String username;
    private String password;

    public User(String u, String p) throws IllFormedUserException {
        if (u == null)
             throw new IllFormedUserException("null username");

        if (p == null) 
            throw new IllFormedUserException("null password");

        username = u;
        password = p;
    }

    public String getUsername() { return username; }
    public String getPassword() { return password; }
}
public class Driver {

    public static void main(String[] args) {
        User user;

        try {
            user = new User("Bob", "ABC");
        } catch (IllFormedUserException e) {
            System.out.println(e.toString());
       }

        try {
            user = new User("Bob", null);
        } catch (IllFormedUserException e) {
            System.out.println(e.toString());
       }

        try {
            user = new User(null, "ABC");
        } catch (IllFormedUserException e) {
            System.out.println(e.toString());
       }
    }
}

Variables Declarations and Try-Catch Blocks

We must be aware that try-catch blocks are blocks of code that define the scope for the variables declared inside of them.  Therefore if you declare a reference variable inside a try-catch block, it can only be used inside the try-catch block and ceases to exist after the try-catch block is executed.

If you use a try-catch block to catch exceptions thrown by constructors and you intend on using the newly created objects outside of the try-catch block, then the variable that holds the reference to the object must be declared before the try-catch block.

User user1;
User user2;

try {
    user1 = new User("Alice", "ABC");
    user2 = new User("Bob", "DEF");
} catch (IllFormedUserException e) {
    System.out.println(e.toString());
    return;
}    

User[] users = {user1, user2};
...

 

© 2017 – 2021, Eric. All rights reserved.