Home CPSC 330

Exceptions & Assertions

Handling Errors

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();
  } 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.

One way of handling this is return values. This has some disadvantages:


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


// Slope.java

class Line {
  public Line(int x0, int y0, int x1, int y1) {
    this.x0 = x0;
    this.y0 = y0;
    this.x1 = x1;
    this.y1 = y1;
  }

  public double slope() {
    // avoid dividing by zero
    if(x1 == x0) {
      throw new ArithmeticException();
    }

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

  private int x0, y0, x1, y1;
}

public class Slope {
  public static void main(String args[]) {
    // OK
    Line a = new Line(3, 4, 5, 6);
    System.out.printf("Slope = %f.\n", a.slope());

    // Exception!
    Line b = new Line(3, 4, 3, 6);
    System.out.printf("Slope = %f.\n", b.slope());
  }
}

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.

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


import java.util.Scanner;

class Line {
  public Line(int x0, int y0, int x1, int y1) {
    this.x0 = x0;
    this.y0 = y0;
    this.x1 = x1;
    this.y1 = y1;
  }

  public double slope() {
    // avoid dividing by zero
    if(x1 == x0) {
      throw new ArithmeticException();
    }

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

  private int x0, y0, x1, y1;
}


public class Slope2{
  public static void main(String args[]) {
    Scanner in = new Scanner(System.in);

    System.out.println("Enter four values (x0, y0, x1, y1)");
    Line line = new Line(in.nextInt(), in.nextInt(), in.nextInt(), in.nextInt());
    
    // try to calculate the slope
    try {
      System.out.printf("Slope = %f.\n", line.slope());
    } catch(ArithmeticException e) {
        // handle the exceptional case
        System.out.println("Line is vertical!");
    }
  }
}

Why not have the Line class print that message inside of the slope method?


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 function calls until it finds a matching catch.

If there is a catch that handles a base class, it will match any derived class. Java has a class hierarchy of errors:

This allows programmers to catch a generic exception like "RuntimeException" or "IOException" and catch any derived class.

We can also make new exceptions for errors we may create to integrate with existing errors.


Input Validation

Many parts of the Java library use exceptions to communicate errors. For example, the Scanner class throws InputMismatchException when the input doesn't match what's expected.

If we want to validate input, we can catch and deal with this exception:


import java.util.*;

public class InputValid {
  public static void main(String args[]) {
    // create a Scanner object
    Scanner in = new Scanner(System.in);
    
    // prompt for input
    System.out.println("Enter a number: ");

    // read in an int
    int number = 0;
    boolean done = false;
    while(!done) {
      // try to get the number
      try {
        number = in.nextInt();
        done = true;
        // catch bad input
      } catch(InputMismatchException e) {
        // scold them and clear the garbage they entered
        System.out.println("That is not a number!");
        in.next();
      }
    }

    // now we are sure it's an int
    System.out.printf("You entered %d.\n", number);
  }
}

finally

Throwing exceptions can lead to problems where resources are not cleaned up:


public static void main(String[] args) {
  try {
    // open a file
    BufferedReader in = new BufferedReader(new FileReader(filename));

    // read from the file (can create an exception)
    String input = in.readLine();

    // close the file
    in.close();

  } catch(IOException e) {
    // when we get here, the file may not have been closed
    in.close();
  }
}

Rather than have to repeat this code, we can use a "finally" block in Java which will always execute regardless of whether or not an exception occurred:


public static void main(String[] args) {
  try {
    // open a file
    BufferedReader in = new BufferedReader(new FileReader(filename));

    // read from the file (can create an exception)
    String input = in.readLine();

    // don't close the file here
  } catch(IOException e) {
    // handle the error
  } finally {
    // this will always execute
    in.close();
  }
}

The finally block allows reduction in redundant code.


Assertions

Another technique for delaing with errors is to use an assertion. An assertion basically says that something has to be true. If it's not true, then the assertion will end the program and print out an error message. This is done using the assert statement:

// tests the condition
// if true, does nothing
// if false halts the program
assert condition;

This example shows how assertions work. Unfortunately, Java disables assertions by default. To check them, pass the -ea flag to the java command.

Unlike the above example, assertions should not be used for anything that may happen in a working program, but for bugs:


When to use Exceptions

Exceptions are best used to communicate errors and other unexpected situations between different pieces of code.

They should not be used for bugs in your program, but for things that are out of your control.


When to use Assertions

Assertions are best used when the case they test absolutely should never be true. A working program should never produce assertion errors when being used by a user.

They are very good for enforcing your assumptions during development.

Copyright © 2018 Ian Finlayson | Licensed under a Creative Commons Attribution 4.0 International License.