Handling Exceptions

an introduction to exception handling in Java

You should download this code and follow along!

Consider the following Fraction class (called BadFraction in the code above):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
    public class Fraction {
        
        private int num;
        private int den;
    
        public static int gcd(int n, int m) {
            if (m == 0) return n;
            return gcd(m, n % m);
        }
    
        Fraction () {
            num = 0;
            den = 1;
        }
    
        Fraction (int num, int den) {        
            this.num = num;
            this.den = den;
            reduce();
        }
    
        public void reduce () {
            int gcd = gcd(num, den);
    
            if (gcd != 0) {
                num /= gcd;
                den /= gcd;
            }
        }
    
        public int getNum () {return num;}
        public int getDen () {return den;}
    
        public Fraction plus (Fraction f) {
            return new Fraction(num * f.den + den * f.num, den * f.den);
        }
    
        public Fraction minus (Fraction f) {
            return this.plus(f.times(-1));
        }
    
        public Fraction reciprocal () {
            return new Fraction(den, num);
        }
    
        public Fraction times (Fraction f) {
            return new Fraction(num * f.num, den * f.den);
        }
    
        public Fraction times (int n) {
            return new Fraction(num * n, den);
        }
    
        public Fraction dividedBy (Fraction f) {
            return this.times(f.reciprocal());
        }
    
        public boolean equals (Fraction f) {
            return (num * f.den == f.num * den);
        }
    
        public String toString () {
            return (num + " / " + den);
        }
    }

To test our Fraction class, we can make some Fractions:

1
2
3
    Fraction a = new Fraction(1, 2);
    Fraction b = new Fraction(1, 3);
    Fraction c = new Fraction(1, 6);

We can do some arithmetic and get the expected results:

1
2
3
4
    System.out.println("a + b = " + a.plus(b).toString());
    System.out.println("a - b = " + a.minus(b).toString());
    System.out.println("a + b - c = " + a.plus(b).minus(c).toString());
    System.out.println("c / (a + b) = " + c.dividedBy(a.plus(b)));

produces the following output

1
2
3
4
    a + b = 5 / 6
    a - b = 1 / 6
    a + b - c = 2 / 3
    c / (a + b) = 1 / 5

as we’d expect. But something strange happens when we run the following code:

1
2
3
4
5
    Fraction oops = a.minus(b).minus(c).dividedBy(a.minus(b).minus(c));
    if (oops.equals(a)) {
      System.out.println("Huh... (a - b - c) / (a - b - c) == a");
      System.out.println("That is strange, because a = " + a.toString());
    }

The resulting output is:

1
2
    Huh... (a - b - c) / (a - b - c) == a
    That is strange, because a = 1 / 2

Why is this so strange? Well, because

\[\frac{a - b - c}{a - b - c} = \frac{1}{2} \Rightarrow 2 (a - b - c) = (a - b - c) \Rightarrow 2 = 1.\]

So it seems our program implies that \(2 = 1\)?!?! Well, no. This problem is revealed when we output the value of the fraction \((a - b - c) / (a - b - c)\):

1
    System.out.println("(a - b - c) / (a - b - c) = " + oops.toString());

prints

1
    (a - b - c) / (a - b - c) = 0 / 0

The real issue is that we divided by zero, and our program just lets us. In what follows, we will show how to use exception handling to fix this problem.

Exceptions

Exceptions are a way that we can communicate that our program encountered some sort of error or unexpected input. You’ve probably encountered exceptions in your code resulting from mistakes in your code. For example if you have a program with

1
2
3
    int a = 1;
    int b = 0; 
    int c = a / b;

your program will compile, but running it will result in:

1
2
    Exception in thread "main" java.lang.ArithmeticException: divide by zero
            at DivideByZero.main(DivideByZero.java:6)

or something similar. Here, we will describe how to make and use exceptions so that we can deal with these errors in a graceful manner without crashing our programs.

Defining new exceptions

Java has many built-in exceptions (see the Java API documentation—note that many of the listed direct subclasses have subclasses themselves!). If none of the exceptions listed there seem appropriate we can define a new exception by defining a subclass of the Exception class. Typically, all we need to do are to define two constructors for our exception that take, respectively, no arguments and a String as an argument.

Here is the complete code for a DivisionByZeroException that we will use for our Fraction class:

1
2
3
4
5
6
7
8
9
    public class DivisionByZeroException extends Exception {
      public DivisionByZeroException () {
        super();
      }
    
      public DivisionByZeroException (String message) {
        super(message);
      }
    }

That’s all we have to do to make a new exception class!

Throwing exceptions

When we encounter the exception that our code should deal with, we throw the exception, using the keyword throw. There are a few places in Fraction where a division by zero error could occur. The first place is in the constructor for Fraction itself. There is currently nothing stopping a programmer from writing new Fraction(1, 0) and carrying on.

To avoid this scenario, we throw a division by zero exception by adding the following lines to the Fraction constructor:

1
2
3
    if (den == 0) {
      throw new DivisionByZeroException();
    }

Whenever a method throws an exception, we must add the exception(s) that could be thrown to the method’s declaration. We do so by adding throws … to the method declaration after the parameter list, where is the list of exceptions the method could throw. For example, our new constructor becomes:

1
2
3
4
5
6
7
8
9
    Fraction (int num, int den) throws DivisionByZeroException {
      if (den == 0) {
        throw new DivisionByZeroException();
      }
            
      this.num = num;
      this.den = den;
      reduce();
    }

To make our exception more informative, we can add a message saying what happened by passing a String to the exception’s constructor, for example

1
    throw new DivisionByZeroException("Tried to create Fraction with numerator " + num + " and denominator " + den);

Unfortunately, our work isn’t done yet. If we compile the new Fraction class, we’ll get errors such as:

1
2
    Fraction.java:30: error: unreported exception DivisionByZeroException; must be caught or declared to be thrown
            return new Fraction(num * f.den + den * f.num, den * f.den);

Line 30 is in our plus method. The problem is that the method calls the constructor for Fraction, which could throw an exception. Thus, plus needs to either “catch” the exception (and figure out how to handle it), or throw the exception back to whatever method called plus in the first place. If we don’t know how to handle the exception within plus, we should opt for the latter option. To this end, we can just change the declaration of plus to indicate that it can throw a DivisionByZeroException as well:

1
2
3
    public Fraction plus throws DivisionByZeroException (Fraction f) {
      return new Fraction(num * f.den + den * f.num, den * f.den);
    }

Then we can do the same with all methods that call the Fraction constructor and plus so that the Fraction.java compiles.

Now let’s try to test the Fraction class with the following FractionTester program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    public class FractionTester {
      public static void main (String[] args) throws DivisionByZeroException {
        System.out.println("Making some fractions...");
        Fraction a = new Fraction(1, 2);
        Fraction b = new Fraction(1, 3);
        Fraction c = new Fraction(1, 6);
        System.out.println("a = " + a.toString() +
                           ", b = " + b.toString() +
                           ", c = " + c.toString());
    
        System.out.println("Doing some arithmetic...");
    
        System.out.println("a + b = " + a.plus(b).toString());
        System.out.println("a - b = " + a.minus(b).toString());
        System.out.println("a + b - c = " + a.plus(b).minus(c).toString());
        System.out.println("c / (a + b) = " + c.dividedBy(a.plus(b)));
    
        System.out.println("Computing (a - b - c) / (a - b - c)...");
        Fraction oops = a.minus(b).minus(c).dividedBy(a.minus(b).minus(c));
    
        if (oops.equals(a)) {
          System.out.println("Huh... (a - b - c) / (a - b - c) == a");
          System.out.println("That is strange, because a = " + a.toString());
        }
    
        System.out.println("(a - b - c) / (a - b - c) = " + oops.toString());
      }
    }

Unfortunately, now we can’t compile FractionTester because we get a bunch of errors about unreported exceptions:

1
2
    FractionTester.java:4: error: unreported exception DivisionByZeroException; must be caught or declared to be thrown
            Fraction a = new Fraction(1, 2);

We can fix all of these errors by declaring that our main method throws DivisionByZeroExceptions. It is bad form to have a main method throw exceptions (the whole point of having the exceptions is so that we can handle them), but here is what happens:

1
2
3
4
5
6
7
8
9
10
11
12
13
    Making some fractions...
    a = 1 / 2, b = 1 / 3, c = 1 / 6
    Doing some arithmetic...
    a + b = 5 / 6
    a - b = 1 / 6
    a + b - c = 2 / 3
    c / (a + b) = 1 / 5
    Computing (a - b - c) / (a - b - c)...
    Exception in thread "main" DivisionByZeroException: Tried to create Fraction with numerator 1 and denominator 0
            at Fraction.<init>(Fraction.java:14)
            at Fraction.reciprocal(Fraction.java:43)
            at Fraction.dividedBy(Fraction.java:55)
            at FractionTester.main(FractionTester.java:19)

This is progress in the sense that the program at least alerts us that there is a problem with division by zero (unlike before). But this is still bad in the sense that our program is interrupted before we can try to fix the issue.

Catching exceptions

In the programs above, exceptions are thrown, but we never attempt to fix or isolate the problem. As a result, when an exception is encountered, the program terminates (albeit with a helpful message indicating where the problem occurred).

If we know how to handle a problem indicated by a thrown exception, we can “catch” the exception by using try-catch blocks:

1
2
3
4
5
6
    try {
      // some code that could throw SomeException
    }
    catch (SomeException e) {
      // code to run if SomeException is thrown
    }

When the code in the try block throws SomeException, execution of the try block is stopped, and the code in the catch block is executed. Since the code in the catch block deals with the exception, the method containing the try-catch blocks doesn’t need to throw SomeException itself.

Note. We can handle multiple exceptions by having multiple catch blocks:

1
2
3
4
5
6
7
8
9
    try {
      // some code that could throw SomeException or AnotherException
    }
    catch (SomeException e) {
      // code to run if SomeException is thrown
    }
    catch (AnotherException e) {
      // code to run if AnotherException is thrown
    }

Now we can catch the exceptions thrown by Fraction in our FractionTester program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    public class FractionTester {
        public static void main (String[] args) {
            System.out.println("Making some fractions...");
    
            Fraction a = new Fraction();
            Fraction b = new Fraction();
            Fraction c = new Fraction();
    
            try {
                a = new Fraction(1, 2);
                b = new Fraction(1, 3);
                c = new Fraction(1, 6);
    
                System.out.println("a = " + a.toString() +
                                   ", b = " + b.toString() +
                                   ", c = " + c.toString());
            }
            catch (DivisionByZeroException e){
                // Since I defined the Fractions above not to be zero, I'm
                // not even going to write any code here. I *know* a
                // DivisionByZeroException won't occur
            }
            
            System.out.println("Doing some basic arithmetic...");
    
            try {
    
                System.out.println("a + b = " + a.plus(b).toString());
                System.out.println("a - b = " + a.minus(b).toString());
                System.out.println("a + b - c = " + a.plus(b).minus(c).toString());
                System.out.println("c / (a + b) = " + c.dividedBy(a.plus(b)));
                System.out.println("1 / (a - b - c) = " + a.minus(b).minus(c).reciprocal());
            }                
            catch (DivisionByZeroException e) {
                System.out.println("Oops. We divided by zero! " + e);
            }
        }
    }

Notice that we had to declare Fractions a, b, and c outside of the first try block, where we called their zero-argument constructors (which do not throw exceptions).

Running the program gives the following output:

1
2
3
4
5
6
7
8
    Making some fractions...
    a = 1 / 2, b = 1 / 3, c = 1 / 6
    Doing some basic arithmetic...
    a + b = 5 / 6
    a - b = 1 / 6
    a + b - c = 2 / 3
    c / (a + b) = 1 / 5
    Oops. We divided by zero! DivisionByZeroException: Tried to create Fraction with numerator 1 and denominator 0

Things are looking good, but our program can be improved. First of all, since the Fraction constructor throws an exception, we can assume that any Fraction instance does not have a zero denominator after construction. Since den is private and there is no method to set den directly, we can safely assume that adding, subtracting, and multiplying existing Fraction instances will not create Fractions with zero denominators. Thus we can catch DivisionByZeroExceptions directly in these methods. For example:

1
2
3
4
5
6
7
8
    public Fraction plus (Fraction f) {
        try {
          return new Fraction(num * f.den + den * f.num, den * f.den);
        }
        catch (DivisionByZeroException e) {
          return null;
        }
    }

Again, we can be certain that the exception we’re “catching” never occurs, so we don’t need to put any code here. We can modify minus and times similarly. The methods reciprocal and dividedBy, however, might create zero denominators (if they are called with an argument whose value is 0), so they should still throw exceptions.

Once we’ve made these modifications to Fraction, we don’t need to call plus, minus, or times in FractionTester inside a try block. Now we can separate the “dangerous” statements into their own try-catch blocks so that we can make more specific behavior:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    try {
    
        System.out.println("c / (a + b) = " + c.dividedBy(a.plus(b)));
    }
    
    catch (DivisionByZeroException e) {
    
        System.out.println("Oops. You tried to divide " + c.toString() + " by " + a.plus(b).toString() +  ". " + e);
    }
    
    try {
        System.out.println("1 / (a - b - c) = " + a.minus(b).minus(c).reciprocal());
    }
    catch (DivisionByZeroException e) {
        System.out.println("Oops. You tried to take the reciprocal of " + a.minus(b).minus(c).toString() +  ". " + e);
    }

The resulting modification outputs:

1
2
3
4
5
6
7
8
    Making some fractions...
    a = 1 / 2, b = 1 / 3, c = 1 / 6
    Doing some basic arithmetic...
    a + b = 5 / 6
    a - b = 1 / 6
    a + b - c = 2 / 3
    c / (a + b) = 1 / 5
    Oops. You tried to take the reciprocal of 0 / 1 DivisionByZeroException: Tried to create Fraction with numerator 1 and denominator 0

Finally, to see that this exception handling actually buys us something, see what happens if we move the first few arithmetic operations to after the try-catch blocks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    System.out.println("Doing some basic arithmetic...");
    System.out.println("a + b = " + a.plus(b).toString());
    try {
        System.out.println("c / (a + b) = " + c.dividedBy(a.plus(b)));
    }
    catch (DivisionByZeroException e) {   
        System.out.println("Oops. You tried to divide " + c.toString() + " by " + a.plus(b).toString() +  ". " + e);
    }
    
    try {
        System.out.println("1 / (a - b - c) = " + a.minus(b).minus(c).reciprocal());
    }
    catch (DivisionByZeroException e) {
        System.out.println("Oops. You tried to take the reciprocal of " + a.minus(b).minus(c).toString() +  ". " + e);
    }
    System.out.println("a - b = " + a.minus(b).toString());
    System.out.println("a + b - c = " + a.plus(b).minus(c).toString());

Now this code gives the following output:

1
2
3
4
5
6
7
8
    Making some fractions...
    a = 1 / 2, b = 1 / 3, c = 1 / 6
    Doing some basic arithmetic...
    a + b = 5 / 6
    c / (a + b) = 1 / 5
    Oops. You tried to take the reciprocal of 0 / 1. DivisionByZeroException: Tried to create Fraction with numerator 1 and denominator 0
    a - b = 1 / 6
    a + b - c = 2 / 3

This is progress! We did some arithmetic that previously either crashed our program, or (worse!) gave a subtle error without us having any clue why. Now, we can detect mistakes in the program’s input/execution and deal with them in such a way that the program continues to function despite the exception.