Monday, February 23, 2015

Sneaky exceptions

Streams and lambdas are powerfool constructs to facilitate functional style in Java 8. There is a specific inconvenience related to checked exception handling though. I'll illustrate the issue through a simple example: converting a collection of Strings - containing urls - to a collection of URL objects using the Stream API.
List<String> urlStrings = Arrays.asList(
    "http://google.com",
    "http://microsoft.com",
    "http://amazon.com"
);

List<URL> urls = urlStrings.stream().map(URL::new).collect(Collectors.toList());
This would look nice but it won't compile: the constructor of the URL throws a checked exception: MalformedURLException. The map method expects a Function, it's apply method implemented by the lambda expression doesn't declare any checked exceptions:
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
Placing the stream operation into a try block or declaring the exception in throws of the method containing it wouldn't help anything - the Function / lambda expression itself won't compile. The only viable option is to handle the exception within the Function. This sacrifices quite a bit of brevity:
List<URL> urls = urlStrings
    .stream()
    .map(url -> {
        try {
            return new URL(url);
        } catch (MalformedURLException e) {
            throw new SomeRuntimeException(e);
        }
    }).collect(Collectors.toList());
Compact code is one thing, the bigger issue is the original exception can't be directly propagated - to be handled outside the stream for instance. There is a pattern that may come handy in such situations: the sneaky throw. The intent is to throw the original checked exception as is, but hide it from the method's signature - effectively imitating characteristics of runtime exceptions. In our case this would be useful for defining Functions that may throw checked exceptions that propagate from the stream operation.

The first step is to define an alternative Function that defines checked exceptions.
public class Sneaky {
    @FunctionalInterface
    public static interface SneakyFunction<T, R> {
        R apply(T t) throws Exception;
    }
    ...
}
This allows us to define something similar to Functions. It won't be useful in itself, the Stream.map() expects a Function implementation. We need a way to transform our SneakyFunction into a Function.
public class Sneaky {
    @FunctionalInterface
    public static interface SneakyFunction<T, R> {
        R apply(T t) throws Exception;
    }

    public static <T, R> Function<T, R> function(SneakyFunction<T, R> function) {
    ...
 }
}
Now comes a bit of type erasure trick to hide the checked exception from the method signature and make it behave like a runtime exception.
public class Sneaky {
    @FunctionalInterface
    public static interface SneakyFunction<T, R> {
        R apply(T t) throws Exception;
    }

    public static <T, R> Function<T, R> function(SneakyFunction<T, R> function) {
        return o -> {
            try {
                return function.apply(o);
            } catch (Exception e) {
                Sneaky.<RuntimeException>sneakyException(e);
                return null;
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static <T extends Throwable> T sneakyException(Throwable t) throws T {
        throw (T) t;
    }
}
The usage pattern in streams operations will be defining SneakyFunction instead of Function - allowing checked exception throws - then transform it to a Function:
List<URL> urls = urlStrings.stream().map(Sneaky.function(URL::new)).collect(Collectors.toList());
This code compiles and runs fine, will throw MalformedURLException when needed and it's possible to propagate the exception easily. There are simpler and "safer" solutions for this specific problem of course, that's not the point. The need for propagating checked exceptions from Streams is not uncommon, and this pattern can come handy despite it's controversies. Similar variants can be created for other commonly used functional or pseudo-functional interfaces, like the Consumer. Now about the risks: the compiler will have less chance to force explicit handling of the checked exception in some scenarios. As with pretty much everything, it's use needs some judgment. Sneaky throws are used in quite a few libraries, and there are some that support sneaky throws, like Project Lombok or Google Guava (in a more cautious way).

1 comment:

  1. Thanks for sharing this informative information.You may also refer.....Exception handling in java An exception or exceptional event is a problem that arises during the execution of a program..
    http://www.s4techno.com/blog/2016/07/12/exception-handling/

    ReplyDelete