Exceptions

In this part we learn about exception handling in Java. Exceptions allow the current flow of control to be interrupted. In Java exceptions are objects that are instances of a class derived from the class Exception. Exceptions enable a controlled way of handling error conditions.

Throwing Exceptions

An exception can be thrown using the throw statement like this: <throw> <exception>;. For instance:

throw new Exception("an error occurred");

If an exception is thrown, unless the code throwing the exception is surounded by a try statement (explained below), control is handed over to the caller of the current method. This process is repeated until either a try-catch block is hit or the exception is rethrown by the main method in which case the program terminates and an error message is printed. This approach allows the programmer to decide where to deal with an exceptional condition. This is quite useful, because the code throwing an exception to indicate an error may not have sufficient information to deal with this error locally.

In [1]:
package lecture;

public class Throwing { 

    public static void main () {
        throw new Exception("Panic!"); // throw an exception from main, this will terminate the program
    }

}
ERROR: java.lang.IllegalStateException: unreported exception java.lang.Exception; must be caught or declared to be thrown
 throw new Exception("Panic!"); }
 ^                             ^   

throws declaration

Why did this code not compile? The reason is that it is only allowed to throw an exception from within a method that lists this exception's class (or a superclass) in its throws declaration which is a list of classnames.

public void myMethod (parameter) throws MyException, MyOtherException {
    ...
}

The above method can throw MyException and MyOtherException.

In [2]:
package lecture;

public class Throwing { 

    // this will now work since we declare "throws Exception"
    public static void main () throws Exception {
        throw new Exception("Panic!"); // throw an exception from main, this will terminate the program
    }

}
Out[2]:
lecture.Throwing
In [3]:
import lecture.Throwing;

Throwing.main();
ERROR: java.lang.Exception: Panic!

Now let's have another example where we throw an exception from within another method that we call from main. Since both the divide method and main do not deal with the exception the following will happen:

  1. by dividing by 0 we cause divide to throw an exception
  2. this exception is not within a try-catch block so control is handed over to the caller of divide which in this case is main.
  3. the call to divide(4,0) is not within a try-catch block, so the program terminates and prints an error message
In [5]:
package lecture;

public class DoubleThrowing { 

    public static int divide (int x, int y) throws Exception {
        if (y == 0) {
            throw new Exception("cannot divide by 0");
        }
        return x/y;
    }
    
    // this will now work since we declare "throws Exception"
    public static void main () throws Exception {
        int a = divide(5,6); 
        int b = divide(4,0); // throws an exception
    }

}
Out[5]:
lecture.DoubleThrowing
In [6]:
import lecture.DoubleThrowing;

DoubleThrowing.main();
ERROR: java.lang.Exception: cannot divide by 0

Try-Catch-Finally

The try-catch statement is used when a piece of code may throw an exception and this exception should be dealt locally. The code the may throw an exception is surounded by try { ... }. The try block is followed by zero of more catch statements that define which exceptions are handled by the try. Any exception type not mentioned here will not be caught but instead be dealt with as explained above. The type of the exception that is throws is compared to each catch's <exception_type> until a type is found that matches (the exception is of a subclass or the same class as <exception_type>. When a match is found, the code within the catch's body is executed binding the exception object to <var_name>.

try {

}
catch (<exception_type_1> <var_name_1>) {
    // deal with <exception_type_1>
}
...
catch (<exception_type_n> <var_name_n>) {
    // deal with <exception_type_n>
}
finally {
    // execute code after handling the exception
};

the code in the finally block is excuted no matter whether an exception is thrown or not.

Let's fix the DoubleThrowing class to run a series of divisions and print en error message instead of terminating.

In [7]:
package lecture;

public class SafeDoubleThrowing { 

    public static int divide (int x, int y) throws Exception {
        if (y == 0) {
            throw new Exception("cannot divide by 0");
        }
        return x/y;
    }
    
    // now we can run multiple division even though some are division by 0
    public static void main () throws Exception {
        int[][] divisions = { {10,6}, {4,0}, {20,4} };
        
        for(int[] div: divisions) {
            try {
                int result = divide(div[0],div[1]); 
                System.out.println(String.format("result of %d/%d is %d", div[0], div[1], result));
            }
            catch (Exception e) {
                System.out.println(e.toString());
            }
        }
    }

}
Out[7]:
lecture.SafeDoubleThrowing
In [8]:
import lecture.SafeDoubleThrowing;

SafeDoubleThrowing.main();
result of 10/6 is 1
java.lang.Exception: cannot divide by 0
result of 20/4 is 5
Out[8]:
null

Exception for dealing with I/O

I/O is a typical example of exception handling since I/O operations are out of the control of our program. That is, our program will have to deal with unforseen error conditions such as trying to read from a file that does not exist. Also I/O objects often wrap operation system resources that are not automatically cleaned up by Java. For instance, when using a File object, you should always call close on this object before the program terminates.

In [43]:
package lecture;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class FileIOWithErrorHandling {
    
    
    public static void main() throws IOException {
        FileInputStream in = null;
        File f;
        byte[] b = new byte[256]; // 256 byte buffer
        
        System.out.println("try to open test.txt");
        f = new File("test.txt");
        try {
            if (f.exists()) {
                System.out.println("File exists, try to read");
                in = new FileInputStream(f);
                for(int available = in.available(); available > 0; available = in.available()) { // read data using buffer b
                    int numread = in.read(b);
                    System.out.printf("\nread %d bytes: ", numread);
                    for(int i = 0; i < numread; i++) // print all bytes read and the a newline
                        System.out.print((char) b[i]);
                    System.out.println();
                }
            }
            else {
                System.out.println("File does not exist!");
            }
        }
        catch (IOException e) {
            System.out.println("error reading text.txt: " + e.toString());
        }
        finally {
            if (in != null) {
                try { 
                    in.close();
                    System.out.println("closed file!"); // look out for this in the output. This statement is excuted even though no exception has been thrown.
                }
                catch (IOException e) {
                    System.out.println("failed closing file input stream:\n" + e.toString());
                }
            }
        }
    }
}
Out[43]:
lecture.FileIOWithErrorHandling
In [44]:
import lecture.FileIOWithErrorHandling;
FileIOWithErrorHandling.main();
try to open test.txt
File exists, try to read

read 256 bytes: This is a file with a long text the is split over multiple lines. We want to
show how our buffer is exausted and how we read this long text 256 bytes at a
time. That is why I have to ramble on and on and on and on and on and on and on
and on and on and on 

read 256 bytes: and on and on and on and on and on and on and on and on and
on and on and on and on and on and on and on and on and on and on and on and on
and on and on and on and on and on and on and on and on and on and on and on and
on and on and on and on and on and 

read 256 bytes: on and on and on and on and on and on and on
and on and on and on and on and on and on and on and on and on and on and on and
on and on and on and on and on and on and on and on and on and on and on and on
and on and on and on and on and on and on and on a

read 256 bytes: nd on and on and on and on and
on and on and on and on and on and on and on and on and on and on and on and on
and on and on and on and on and on and on and on and on and on and on and on and
on and on and on and on and on and on and on and on and on and o

read 256 bytes: n and on and on
and on and on and on and on and on and on and on and on and on and on and on and
on and on and on and on and on and on and on and on and on and on and on and on
and on and on and on and on and on and on and on and on and on and on and on an

read 167 bytes: d
on and on and on and on and on and on and on and on and on and on and on and on
and on and on and on and on and on and on and on and on and on and on and on and
on.

closed file!
Out[44]:
null

User-defined Exception Types

User-defined exception classes are declared just as any other class. They have to extend either Exception or one of its subclasses.

In [4]:
package lecture;

public class MyFirstException extends Exception {
    
    private int number;
    
    public MyFirstException(String message, int number) {
        super(message);
        this.number = number;
    }
    
    public String toString() {
        return super.toString() + "\n" + number;
    } 
}
Out[4]:
lecture.MyFirstException
In [5]:
package lecture;
import lecture.MyFirstException;

public class MyThrowing { 

    public static void main () throws MyFirstException {
        throw new MyFirstException("Panic!", 15);
    }
}
Out[5]:
lecture.MyThrowing
In [6]:
import lecture.MyThrowing;

MyThrowing.main();
ERROR: lecture.MyFirstException: Panic!
15
In [11]:
return 5 / 0; // division by 0 returns an Arithmetic Exception
ERROR: java.lang.ArithmeticException: / by zero