Sunday, August 25, 2013

Providing failure policy with generics and annotations

Java generics with annotations provides a simple technique for specifying failure policies in an API. The exception in a throws clause may be a generic parameter. The @Nullable and @Nonnull annotations express intent.

In the example, Base takes an Exception as a generic parameter. Three implementations demonstrate the choices:

  1. ReturnsNull is for "finder" type APIs, where a null return is in good taste.
  2. ThrowsUnchecked is when failure is fatal, and should be handled higher in the stack.
  3. ThrowsChecked is when failure is transient, and can be handled by the caller (e.g., network failures).

Because the exception is defined at a class level, this technique is best for SAM-style APIs or when a common exception is shared by a few calls.

static void main(final String... args) {
    new ReturnsNull().returnSomething();
    System.out.println("Returned null");
    try {
        new ThrowsUnchecked().returnSomething();
    } catch (final RuntimeException ignored) {
        System.out.println("Threw unchecked");
    }
    try {
        new ThrowsChecked().returnSomething();
    } catch (final Exception ignored) {
        System.out.println("Threw checked");
    }
}

interface Base<E extends Exception> {
    Object returnSomething() throws E;
}

final class ReturnsNull implements Base<RuntimeException> {
    @Nullable @Override
    public String returnSomething() {
        return null;
    }
}

final class ThrowsUnchecked implements Base<RuntimeException> {
    @Nonnull @Override
    public String returnSomething() {
        throw new RuntimeException();
    }
}

final class ThrowsChecked implements Base<Exception> {
    @Nonnull @Override
    public String returnSomething() throws Exception {
        throw new Exception();
    }
}

No comments: