Click here to Skip to main content
15,886,258 members
Articles / Programming Languages / Java

Sneaky Exceptions

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
23 Feb 2015CPOL2 min read 5.6K   2  
Sneaky Exceptions

Streams and lambdas are powerful constructs to facilitate functional style in Java 8. There is a specific limitation related to checked exception handling one might bump into 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.

Java
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, its apply method we define as a lambda expression doesn't declare any checked exceptions:

Java
@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 lambda expression. This sacrifices quite a bit of brevity:

Java
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 method 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 containing checked exception throws and letting those exceptions propagate from our stream operation.

The first step is to define an alternative Function that defines checked exceptions.

Java
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 valid Function.

Java
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.

Java
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:

Java
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 in handy despite its controversies. Similar variants can be created for other commonly used 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, its 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).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Norway Norway
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --