KotlinCS 124 LogoJava
PrevIndexNext
Kotlin
Java
  • Implementing a Map : 04/26/2024

  • Streams : 04/25/2024

  • Generics : 04/24/2024

  • Hashing : 04/23/2024

  • Binary Search : 04/22/2024

  • MP3: Course Ratings : 04/19/2024

  • Quicksort : 04/18/2024

  • Merge Sort : 04/17/2024

  • Sorting Algorithms : 04/16/2024

  • MP Debugging Part 1 : 04/15/2024

  • MP2: Course Activity : 04/12/2024

  • Practice with Recursion : 04/11/2024

  • MP Debugging Part 0 : 04/10/2024

  • MP2: API Client : 04/09/2024

  • MP2: API Server : 04/08/2024

  • Trees and Recursion : 04/05/2024

  • Trees : 04/04/2024

  • Recursion : 04/03/2024

  • MP1: Filtering and Search : 04/02/2024

  • MP1: Loading and Sorting : 04/01/2024

  • Lists Review and Performance : 03/29/2024

  • Linked Lists : 03/28/2024

  • Algorithms and Lists : 03/27/2024

  • Continuing MP0 : 03/26/2024

  • Getting Started with MP0 : 03/25/2024

  • Lambda Expressions : 03/22/2024

  • Anonymous Classes : 03/21/2024

  • Practice with Interfaces : 03/20/2024

  • Implementing Interfaces : 03/19/2024

  • Using Interfaces : 03/18/2024

  • Working with Exceptions : 03/08/2024

  • Throwing Exceptions : 03/07/2024

  • Catching Exceptions : 03/06/2024

  • References and Polymorphism : 03/05/2024

  • References : 03/04/2024

  • Data Modeling 2 : 03/01/2024

  • Equality and Object Copying : 02/29/2024

  • Polymorphism : 02/28/2024

  • Inheritance : 02/27/2024

  • Data Modeling 1 : 02/26/2024

  • Static : 02/23/2024

  • Encapsulation : 02/22/2024

  • Constructors : 02/21/2024

  • Objects, Continued : 02/20/2024

  • Introduction to Objects : 02/19/2024

  • Compilation and Type Inference : 02/16/2024

  • Practice with Collections : 02/15/2024

  • Maps and Sets : 02/14/2024

  • Lists and Type Parameters : 02/13/2024

  • Imports and Libraries : 02/12/2024

  • Multidimensional Arrays : 02/09/2024

  • Practice with Strings : 02/08/2024

  • null : 02/07/2024

  • Algorithms and Strings : 02/06/2024

  • Strings : 02/05/2024

  • Functions and Algorithms : 02/02/2024

  • Practice with Functions : 02/01/2024

  • More About Functions : 01/31/2024

  • Errors and Debugging : 01/30/2024

  • Functions : 01/29/2024

  • Practice with Loops and Algorithms : 01/26/2024

  • Algorithms : 01/25/2024

  • Loops : 01/24/2024

  • Arrays : 01/23/2024

  • Compound Conditionals : 01/22/2024

  • Conditional Expressions and Statements : 01/19/2024

  • Operations on Variables : 01/18/2024

  • Variables and Types : 01/17/2024

  • Welcome to CS 124 : 01/16/2024

Working with Exceptions

public class MyException extends Exception { }
boolean youAreAlone = false;
if (youAreAlone) {
throw new MyException("You are not alone!");
}

In this lesson we’ll focus on how to use Java’s exception handling mechanisms—both try-catch and throw. We’ll also introduce a few new wrinkles to round out our understanding of this particular system. Our focus is on real-word patterns for working with and handling errors.

Warm Up Debugging Challenge
Warm Up Debugging Challenge

But! Let’s warm up with another graded debugging challenge!

Re-throw and finally
Re-throw and finally

Before we go on, let’s look at a few further wrinkles in Java exception handling that we haven’t touched on yet.

Re-throw
Re-throw

First, you may have wondered: what happens if you throw an exception inside a catch block? Let’s try it:

try {
throw new Exception("Try");
} catch (Exception e) {
throw new Exception("Catch");
}

You’ll notice that the second exception is thrown out of the catch block. Of course, we can rewrap the entire try-catch in another try-catch:

try {
try {
throw new Exception("Try");
} catch (Exception e) {
throw new Exception("Catch");
}
} catch (Exception e) {
System.out.println("Caught it! " + e);
}

Note that exceptions thrown inside a catch block cannot be caught by the same try-catch in which they were thrown. So this doesn’t work:

try {
throw new IllegalStateException("Try");
} catch (IllegalStateException e) {
throw new IllegalArgumentException("Nope");
} catch (IllegalArgumentException e) {
System.out.println("Won't get here");
}

finally
finally

try-catch blocks can include an additional component: a finally block. The finally block is always executed, regardless of whether an exception was thrown or not. Let’s see how that works:

import java.util.Random;
Random random = new Random();
try {
if (random.nextBoolean()) {
throw new Exception();
}
System.out.println("Try");
} catch (Exception e) {
System.out.println("Catch");
} finally {
System.out.println("Finally");
}
System.out.println("Done");

If you run this a few times, you’ll notice that regardless of whether we complete the try block successfully or enter the catch, the finally block is always executed.

One of the cool things about finally is that it is always executed. Even if the try includes a return!

int example() {
try {
return 0;
} finally {
System.out.println("Done");
}
}
System.out.println(example());

This feature of finally makes it useful when a method needs to do some kind of cleanup before exiting. We haven’t run into this scenario yet, but you will sometimes. And when you do, finally will be there for you!

Practice: PingPonger Class

Created By: Geoffrey Challen
/ Version: 2020.11.0

Create a public class PingPonger. A PingPonger is in one of two states: ping or pong. You should provide a constructor accepting a String argument that sets the initial state. If the passed String is "ping", the initial state is ping. If the passed String is "pong", the initial state is pong. If the passed String is not ping or pong, you should throw an IllegalArgumentException.

Once the PingPonger has been created, the user should call pong if the state is ping and ping if the state is pong. ping takes no arguments and returns true. pong takes no arguments and returns false. If ping or pong is called out of order, you should throw an IllegalStateException.

Here's an example:

Exception Handling Patterns
Exception Handling Patterns

Next let’s look at a few common exception handling patterns.

Why Not assert?
Why Not assert?

Previously we’ve presented how to handle bad inputs using assert:

void doIt(String it) {
assert it != null : "That's not it!";
System.out.println(it);
}
doIt("Good!");
doIt(null);

But here’s the dirty truth about Java assertions: they are not on by default! If you examine the Java command line options you’ll see that you need to pass a special flag to java to turn assertions on. Let’s look at how that works in practice in a brief screencast:

assert is enabled in all of our playgrounds. However, in other Java environments—such as on Android—it may be hard or impossible to enable assertions. While they can be useful during testing, the right thing to do in most cases is to throw an Exception rather than rely on assert.

Input Validation
Input Validation

However, we can replace many of the places where we had previously used assert with commonly available non-checked Java exceptions. When checking parameters to a method, IllegalArgumentException is usually what you want to use:

void doIt(String it) {
if (it == null) {
throw new IllegalArgumentException("That's not it!");
}
}
doIt("Good!");
doIt(null);

Note that because IllegalArgumentException is unchecked, you don’t need to use throws to declare that the method throws it. And the compiler will not require that you catch it. This is good, since usually an IllegalArgumentException indicates that the inputs to a method contradict the documentation.

State Validation
State Validation

When designing our Java classes, there are times when we want to enforce patterns of usage that span multiple methods. To make that more concrete, let’s look at an example together:

// State Validation Example

Batching Failures
Batching Failures

Sometimes our code needs to take a series of steps to complete some action. If any of those steps fail, the entire operation fails. This can be a good place to use a try-catch to avoid having to do a lot of error checking after each step. Let’s look at an example of this:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
ObjectMapper mapper = new ObjectMapper();
String json = """
{
"you": {
"are": {
"not": {
"alone": true
}
}
}
}
""";
JsonNode node = mapper.readTree(json);

Rethrowing Exceptions
Rethrowing Exceptions

Sometimes when an error occurs we just want to log that it happened, but then let it continue to propagate. We can do this by rethrowing the error out of the catch block:

try {
throw new Exception("Had a problem");
} catch (Exception e) {
System.out.println("Had a problem: " + e);
throw e;
}

You can also use this technique to convert a checked exception to an unchecked exception:

try {
throw new Exception("Had a problem");
} catch (Exception e) {
System.out.println("Had a problem: " + e);
throw new IllegalStateException(e);
}

Why would we do this? In some cases, even if the compiler will insist that we “handle” a checked exception, there really isn’t anything reasonable to do other than crash. In that case, converting the checked exception to an unchecked RuntimeException (like IllegalStateException above) simplifies the error handling. Methods that call our method now don’t themselves need to handle the exception, because it is unchecked.

Designing Your Own Exceptions
Designing Your Own Exceptions

Once you start writing your own libraries and sharing code with others, it can be helpful to design your own custom exceptions. One reason to do this is that it allows users of your code to better understand what went wrong. And to do more accurate error handling by establishing multiple catch blocks that deal with different kinds of exceptions.

Happily, extending Exception could not be easier:

public class MyNewException extends Exception {}

Note that this creates a checked exception. Usually that’s what you want, since there is usually a reasonable unchecked exception already available for you. But you can create new unchecked exceptions similarly by extending RuntimeException.

Practice: Pentagon Shape

Created By: Geoffrey Challen
/ Version: 2021.10.0

Create and complete the implementation of the Pentagon class. Your class should be public, inherit from the Shape class, and provide the following methods:

  1. Constructor that takes a double parameter. Creates a new Pentagon with the passed side length. If the passed length is less than or equal to 0, throw an IllegalArgumentException. You should call the Shape constructor and pass it the String "pentagon" to identify the type of this shape.
  2. Public instance method area that takes no arguments and returns a double. Return the area of this shape, given only the side length, which you can find here: https://www.cuemath.com/measurement/area-of-pentagon/. You will need to use Math.sqrt to solve this problem.
  3. Override public boolean equals(Object other). Return true if other is a Pentagon with the same side length, and false otherwise. Note that other may be null or not a Pentagon.

Finally, note that your class should not expose any of its internal state publicly.

Homework: Result Might Throw

Created By: Geoffrey Challen
/ Version: 2022.10.0

When we call a method that might throw an exception, sometimes the right thing to do is to catch it, and other times we should let it be thrown out of our method.

But sometimes we want to catch that exception and save it for later, to be dealt with by another part of our program. This is particularly useful when the code that is using the result of the method call is not the caller of the method, a situation that we'll encounter once we begin Android development.

To help handle this situation, create a public class called ResultMightThrow. It stores the result of calling a method that might throw: either an Object result if the method succeeded, or an Exception exception if it threw. ResultMightThrow provides two constructors: one accepting a single Object, the other a single Exception. The Object may be null, but you should reject null exceptions by throwing an IllegalArgumentException.

You should provide a getter for the result using our usual naming convention. However, if the ResultMightThrow actually stores an exception, you should throw that exception instead when getResult is called. getResult must be declared to throw an Exception.

You should also provide a getter for the exception, again following our usual naming conventions.

When complete, here is how your class should work:

Note that the multiple constructors used in this problem illustrates the intersection between Java's method overloading and polymorphism. When choosing between the two constructors that both accept a single parameter, Java will choose the one with the type most closely matching the passed value. So even though every Java Exception is also an Object, calling the ResultMightThrow constructor with an Exception will enter the constructor accepting an Exception as a parameter.

More Practice

Need more practice? Head over to the practice page.