Lecture 5 Handout

Java Exceptions

Learning Outcomes

At the end of this lecture, you’ll be able to:

Lecture Plan

In this lecture, we'll cover the following lessons:

  1. Java Programs with Exceptions: A robust method
  2. Java Programs with Exceptions: Unit Testing
  3. Java Programs with Exceptions: Throw an Exception!
  4. Java Programs with Exceptions: Exercise
  5. Java Programs with Exceptions: Custom Exceptions
  6. Java Interlude: Exception Hierarchies
  7. Java Interlude: Two Types of Exceptions
  8. Exercise: LengthException

Lessons marked with ⚡ contain exercise/activity.

Downloads

Java Programs with Exceptions: A robust method

Consider the specification of the IndexedList.get method:

/**
 * Retrieve the value stored at the given index.
 *
 * @param index representing a position in this list.
 *              Pre: 0 <= index < length
 * @return value at the given index.
 */
T get(int index);

A client of get (any method that calls it) is responsible to ensure the pre-condition is met. The get method can check the validity of its input too. (It is not always the case that a method will be able to check and ensure its pre-conditions are met.)

A robust method is one that handles bad (invalid or absurd) inputs reasonably.

Java’s exception handling mechanism supports the construction of robust methods.

Let’s update the specification/declaration of IndexedList.get to make it robust.

/**
 * Retrieve the value stored at the given index.
 *
 * @param index representing a position in this list.
 * @return value at the given index.
 * @throws IndexOutOfBoundsException when index < 0 or index >= length.
 */
T get(int index) throws IndexOutOfBoundsException;

Notice we have removed the pre-condition. Instead, the method throws IndexOutOfBoundsException when `index is out of range.

Resources

Here are some resources on robust coding:

Defensive programming is a closely related concept.

Java Programs with Exceptions: Unit Testing

Let’s update IndexedListTest to include tests to check the IndexOutOfBoundsException is thrown when it is expected.

@Test
@DisplayName("get() throws exception if index is below the valid range.")
void testGetWithIndexBelowRangeThrowsException() {
  try {
    numbers.get(-1);
    fail("IndexOutOfBoundsException was not thrown for index < 0");
  } catch (IndexOutOfBoundsException ex) {
    return;
  }
}

@Test
@DisplayName("get() throws exception if index is above the valid range.")
void testGetWithIndexAboveRangeThrowsException() {
  try {
    numbers.get(size + 1);
    fail("IndexOutOfBoundsException was not thrown for index > length");
  } catch (IndexOutOfBoundsException ex) {
    return;
  }
}

Notice the approach to test that an exception is thrown when it is expected to be thrown:

try {
  // call a method with an invalid input
  obj.someMethodThatThrowsException(badInput);
  // we expect the execution stops 
  //   and goes to the catch block as a result of an exception
  // if we get passed this point, exception was not thrown
  //   we must deliberately signal the test has failed!
  fail("The expected exception was not thrown!");
} catch (ExpectedException ex) {
  // if we are here, the expected exception is thrown;
  //   there is nothing to do, we can "return" 
  //   to indicate the test has successfully passed
  return;
}

The fail method is part of JUnit’s Assertions library

import static org.junit.jupiter.api.Assertions.fail;

Run the tests and see them fail!

Resources

If you need a refresher on Java’s Exception, please visit Exceptions on Oracle’s [online] Java Tutorial. Alternatively, you can read Baeldung article on Java Exceptions.

JUnit 5 includes assertThrows() method that provides a more elegant approach to check an exception is thrown. We will not use this alternative approach because it involves the use of Java’s Lambda expression:

assertThrows(IndexOutOfBoundsException.class, () -> {
  numbers.get(-1);
});

This post on howtodoinjava.com provides a more detailed example on using assertThrows().

Java Programs with Exceptions: Throw an Exception!

Let’s update ArrayIndexedList.get to throw the IndexOutOfBoundsException:

@Override
public T get(int index) throws IndexOutOfBoundsException {
  if (index >= 0 && index < length()) {
    return data[index];
  } else {
    throw new IndexOutOfBoundsException();
  }
}

Make note of the syntax; in particular, be careful with throw v.s. throws keywords.

Run IndexedListTest and see that all tests pass.

Java Programs with Exceptions: Exercise

Exercise:

Solution

Update IndexedList.put:

/**
 * Change the value at the given index.
 *
 * @param index representing a position in this list.
 * @param value to be written at the given index.
 *              Post: this.get(index) == value
 * @throws IndexOutOfBoundsException when index < 0 or index >= length.
 */
void put(int index, T value) throws IndexOutOfBoundsException;

Add the following unit tests to IndexedListTest:

@Test
@DisplayName("put() throws exception if index is below the valid range.")
void testPutWithIndexBelowRangeThrowsException() {
  try {
    numbers.put(-1, 10);
    fail("IndexOutOfBoundsException was not thrown for index < 0");
  } catch (IndexOutOfBoundsException ex) {
    return;
  }
}

@Test
@DisplayName("put() throws exception if index is above the valid range.")
void testPutWithIndexAboveRangeThrowsException() {
  try {
    numbers.put(size + 1, 10);
    fail("IndexOutOfBoundsException was not thrown for index > length");
  } catch (IndexOutOfBoundsException ex) {
    return;
  }
}

Update ArrayIndexedList.put:

@Override
public void put(int index, T value) throws IndexOutOfBoundsException {
  if (index < 0 || index > length()) {
    throw new IndexOutOfBoundsException();
  }

  data[index] = value;
}

Aside: IndexOutOfBoundsException is a built-in Java Exception which will be thrown e.g. when an array is indexed with a value out of index range. So, the statement data[index] = value; will throw IndexOutOfBoundsException when index is invalid; we didn’t need to change the implementation of ArrayIndexedList.put. I prefer the updated implementation as it provides more clarity to the behavior of the method.

Java Programs with Exceptions: Custom Exceptions

Java comes with many built-in exceptions (see a list of common exceptions and their description here) such as the IndexOutOfBoundsException. These exceptions cover most situations that are bound to happen in programs where there is a need to throw an exception. However, there will be times we need to supplement these built-in exceptions with our own.

Creating custom exceptions can be as simple as extending an existing built-in exception.

To learn the process, we make our own version of IndexOutOfBoundsException. We call ours simply IndexException.

All built-in exceptions have a default constructor as well as an overloaded constructor that takes a String (message) as a parameter. It is considered good practice to provide a similar overloaded constructor for your custom exceptions.

/**
 * Exception for invalid index. Data structures using (integer) indices
 * throw IndexException if a given index is out of range.
 */
public class IndexException extends RuntimeException {

  /**
   * Constructs a new IndexException.
   */
  public IndexException() {
  }

  /**
   * Constructs a new IndexException with the specified detail message.
   *
   * @param message the detail message. The detail message is saved for
   *                later retrieval by the getMessage() method.
   */
  public IndexException(String message) {
    super(message);
  }
}
Resource

Create a Custom Exception in Java by Baeldung is a good read.

Java Interlude: Exception Hierarchies

The Throwable class is the superclass of all errors and exceptions in the Java language. Only objects that are instances of this class (or one of its subclasses) are thrown by the Java Virtual Machine or can be thrown by the Java throw statement.

Exceptions are regular classes, and as such, one exception can subclass another.

class BankingException extends Throwable {...}
class InsufficientFundsException extends BankingException {...}
class NegativeAmountException extends BankingException {...}

If an exception is declared to be caught, any of the sub-classes of that exception will also be caught by that same catch statement.

try {
  // Some code that might throw BankingException exception
  // or its subclasses
} catch (BankingException e) {
  // deal with the exception
}

When you chain catch blocks, you must deal with more specific exceptions first.

try {
  // Some code that might throw BankingException exception
  // or its subclasses
} catch (InsufficientFundsException e) {
  // deal with InsufficientFundsException
} catch (BankingException e) {
  // deal with the exception
}

Java Interlude: Two Types of Exceptions

There are two kinds of exceptions in Java: checked exceptions and unchecked exceptions. In this lesson, I’ll provide some examples to understand the distinction.

Checked Exceptions

Consider this code:

public void doSomething() {
    doThisFirst();
    // more code here!
}

If doThisFirst method throws a checked exception, the compiler will not let you simply call it! You must handle the exception by either of these techniques:

  1. Using try/catch block
public void doSomething()  {
  try { 
    doThisFirst();
  } catch (SomeCheckedException e) {
    // Deal with the exception.
  }
  // more code here!
}
  1. Allow the doSomething method to throw the exception raised by doThisFirst
public void doSomething() throws SomeCheckedException {
  doThisFirst();
  // more code here!
}

A method that throws a checked exception must include a throws clause in its declaration.

Java verifies checked exceptions at compile-time and forces you to provide a handling mechanism for it.

Unchecked Exceptions

All Java exceptions are checked unless they subtype the Error class or the RuntimeException class.

The compiler does not force you to handle unchecked exceptions. For instance, the compiler will let you simply call the doThisFirst() if it (may potentially) throws an unchecked exception:

public void doSomething() {
    doThisFirst();
    // more code here!
}

A method that throws an unchecked exception does not need to include a throws clause in its declaration.

Why two types of Exceptions?

The distinction between checked v.s. unchecked exception is specific to Java. In C++ for instance, all exceptions are unchecked, so it is up to the programmer to handle them or not.

Read Unchecked Exceptions — The Controversy on Oracle’s website for more information. In a nutshell, Java believes it must force programmers to handle exceptions. On the other hand, it makes allowance to declare errors that could happen, but rarely do, as unchecked exceptions. So, the programmer is not frustrated to e.g. have to write try/catch every time they use division in a program even though division by zero causes ArithmeticException.

Resources

Checked and Unchecked Exceptions in Java by Baeldung is a good read.

Exercise: LengthException

Exercise The constructor of ArrayIndexedList should throw an unchecked LengthException when the value provided for size is $\leq 0$. Take all the necessary steps to make this happen (don’t forget writing tests).

Solution

Please refer to the posted solution.