Sunday, 15 June 2014

Java: lambdas and easy checked exception handling with KλudJe

This post describes an approach to handling checked exceptions using Java 8 lambdas without adding try/catch blocks everywhere.

Lambdas and checked exceptions

Consider this contrived example:

import java.io.IOException;
import java.io.UncheckedIOException;

public class WrapAndUnwrap {
  private void runit(Runnable runner) {
    runner.run();
  }

  private void throwit() throws IOException {
    throw new IOException("expected");
  }

  /**
   * @throws IOException when an error occurs
   */
  public void combine() throws IOException {
    try {
      runit(() -> {
        try {
          throwit();
        } catch (IOException e) {
          throw new UncheckedIOException(e);
        }
      });
    } catch (UncheckedIOException e) {
      throw e.getCause();
    }
  }
}

In order to have runit invoke the throwit method and meet the error handling contract for combine the code must wrap and unwrap the IOException.

Using KλudJe to handle checked exceptions

Here a cleaner way to implement the logic:

import uk.kludje.fn.lang.URunnable;
import java.io.IOException;

public class TransparentThrow {
  private void runit(Runnable runner) {
    runner.run();
  }

  private void throwit() throws IOException {
    throw new IOException("expected");
  }

  public void combine() throws IOException {
    runit((URunnable) this::throwit);
  }
}

This code uses the KλudJe library which can be downloaded from the Central Repository. KλudJe contains mirror types for many of the standard API functional interfaces.

URunnable

The URunnable type takes advantage of two things:

  • "Sneaky throws"
  • Default methods

Checked exceptions only matter to the Java compiler. Other JVM languages happily throw and consume them in the manner of runtime exceptions. It is possible to use some generics gymnastics to get the Java compiler to do this too - the technique is described here.

URunnable declares a new abstract method for lambdas to implement and it re-throws any exceptions using a default implementation of run.

@java.lang.FunctionalInterface
public interface URunnable extends java.lang.Runnable {

  default void run() {
    try {
      $run();
    } catch (Throwable throwable) {
      uk.kludje.Exceptions.throwChecked(throwable);
      throw new AssertionError(); //unreachable code
    }
  }

  void $run() throws Throwable;
}

No comments:

Post a Comment

All comments are moderated