Home CPSC 240

Exceptions

 

Overview

When we are writing code that detects and error, what should we do?

In some cases, it makes sense to tell the user of the program that there was an error. For example in assignments you have written, you have had code like this:


// get a positive value
int value;
do {
    System.out.print("Enter a positive number: ");
    value = scanner.nextInt();

    if (value <= 0) {
        System.out.println("Error, please enter a positive number.");
    }
} while(value <= 0);

This code checks for an error condition, and reports any errors to the user of the program.

However, oftentimes the "user" of the code we write is not the person who is running a program, but another programmer who is using something we wrote. Most code is not used directly by end users, but rather used by other programmers as part of something else.

Exceptions provide a way for our code to signal errors to other parts of a program.


 

Exceptions

An exception is a programming construct specifically for handling errors.

There are three parts of an exception:


 

Throwing an Exception

Exceptions are created with the "throw" keyword in Java. For example, the following program throws an exception to avoid a divide by zero.


public class Slope {
    public static double calculateSlope(int x0, int y0, int x1, int y1) {
        // avoid dividing by zero
        if(x1 == x0) {
            throw new ArithmeticException("Vertical lines can not have a slope");
        }

        return (double)(y1 - y0) / (double)(x1 - x0);
    }

    public static void main(String args[]) {
        // ok
        double slope1 = calculateSlope(3, 4, 5, 6);
        System.out.println(slope1);

        // exception!
        double slope2 = calculateSlope(3, 4, 3, 6);
    }
}

 

Catching Exceptions

In the program above, the exception halts the program. If we want the program to recover from the exception, we must catch it.

To catch an exception, we use a try/catch block. The form of this is as follows:


try {

  // code that can cause an exception

} catch(type value) {
  // handle any exception here
}

Whenever an exception is thrown, the program will search for a catch block that catches that type of exception. When it finds one, that code is executed - the code that caused the exception is abandoned.

The goal of the catch block is to try to handle the problem as best as we can. This depends on the situation. Sometimes we may want to halt the program, sometimes we may want to try something different and sometimes we may want to just ignore the problem.

In this example, we handle the problem of a vertical slope by printing a message stating that the line is vertical.


    double x0 = in.nextDouble();
    double x1 = in.nextDouble();
    double y0 = in.nextDouble();
    double y1 = in.nextDouble();

    try {
        double slope = calculateSlope(x0, y0, x1, y1);
        System.out.println("The slope of the line is " + slope);
    } catch(ArithmeticException e) {
        // handle the exceptional case
        System.out.println("Line is vertical!");
    }
  }

The big reason to use exceptions here is that it allows the calculateSlope method to be used in other programs that handle the problem differently. Exceptions decouple detecting a problem from handling it.


 

Exception Types

In the example above, the exception type we threw was ArithmeticException. When Java looks for a suitable catch block, it searches up the stack of method calls until it finds a matching catch.

Java comes with some different types of exceptions built-in that we can use. Another common ones we can use is IllegalArgumentException which can be used when bad values are passed into a method.

We can also create out own types of exceptions, which we can discuss after talking about inheritance.


 

Exception Objects

When we catch an exception, we are also passed the exception object into the catch block. With this, we can get some information out of it. Every exception has a "message" in it which we can print out. For example, if we catch an index out of bounds exception, the message will tell us the length of the array and also what index we tried to use:


for (int i = 0; i <= array.size(); i++) {
    try {
        array[i] = 5;
    } catch (Exception e) {
        System.out.println(e.getMessage());
    }
}

This will produce a message like the following:

Index 100 out of bounds for length 100

We can also print the stack trace of an exception, which is where we are in the method calls of a program at the point in which the exception occurred.

For example, you in this program the exception occurs in the method fillArray which is called from main. The exception is caught in main, and the stack trace shows us what happened:


public static ArrayList<Integer> fillArray() {
    ArrayList<Integer> list = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        list.add(i * 10);
    }
    list.set(100, 0);

    return list;
}

public static void main(String[] args) {
    try {
        ArrayList<Integer> list = fillArray();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

When we run this the stack trace is printed:

java.lang.IndexOutOfBoundsException: Index 100 out of bounds for length 100
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302)
	at java.base/java.util.Objects.checkIndex(Objects.java:359)
	at java.base/java.util.ArrayList.set(ArrayList.java:441)
	at Main.fillArray(Main.java:10)
	at Main.main(Main.java:17)

Generally, printing out the messages and stack traces of exceptions you caught are very handy for debugging, but usually don't make as much sense in finished programs.

Copyright © 2024 Ian Finlayson | Licensed under a Creative Commons BY-NC-SA 4.0 License.