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.
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.
package lecture;
public class Throwing {
public static void main () {
throw new Exception("Panic!"); // throw an exception from main, this will terminate the program
}
}
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
.
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
}
}
import lecture.Throwing;
Throwing.main();
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:
divide
to throw an exceptiondivide
which in this case is main
.divide(4,0)
is not within a try-catch block, so the program terminates and prints an error messagepackage 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
}
}
import lecture.DoubleThrowing;
DoubleThrowing.main();
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.
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());
}
}
}
}
import lecture.SafeDoubleThrowing;
SafeDoubleThrowing.main();
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.
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());
}
}
}
}
}
import lecture.FileIOWithErrorHandling;
FileIOWithErrorHandling.main();
User-defined exception classes are declared just as any other class. They have to extend
either Exception
or one of its subclasses.
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;
}
}
package lecture;
import lecture.MyFirstException;
public class MyThrowing {
public static void main () throws MyFirstException {
throw new MyFirstException("Panic!", 15);
}
}
import lecture.MyThrowing;
MyThrowing.main();
return 5 / 0; // division by 0 returns an Arithmetic Exception