How can I re-throw an exception in a lambda block

2019-02-13 20:51发布

问题:

With following code,

void key(Key) throws SomeCheckedException {
}

void supplier(Supplier<Key> s) throws SomeCheckedException {
    ofNullable(s).ifPresent(s -> {                   //    |
        try {                                        //    |
            key(s.get());                            //    |
        } catch (final SomeCheckedException sce) {   //    |
            // sce is coming from key() method       //    |
            // How can I throw sce for outer method? //  --/
        }
    });
}

How can I throw sce as if the method(supplier) method is throwing it?

Please note that above code is just an example. I need the key(s.get()) to be inside a lambda expression.

void supplier(Supplier<Key> s) throws SomeCheckException {
    key(s.get());
}

回答1:

If you want to process checked exceptions in a safe manner you need a helper method which provides the facility of wrapping the exception into a sub-type of RuntimeException. Here is such a helper function which uses Generic’s type safety to ensure that only declared exceptions will be re-thrown (unless you use an unsafe operation):

public static <E extends Throwable> void attempt(
    Consumer<Function<E,RuntimeException>> action) throws E {

    final class CarryException extends RuntimeException {
        final E carried;
        CarryException(E cause) {
            super(cause);
            carried=cause;
        }
    }

    try { action.accept( CarryException::new ); }
    catch(CarryException ex) { throw ex.carried; }
}

It supports an arbitrary action which will receive a function which does the temporary wrapping of the checked exception type to the RuntimeException. This wrapping will be transparent, the method attempt will either complete normally or throw the original checked exception E (or an unrelated unchecked exception if one occurs).

So you can use it like this:

public static void processIterm(Supplier<Key> s)
    throws SomeCheckedException  {

    attempt( (Function<SomeCheckedException, RuntimeException> thrower) ->
        Optional.ofNullable(s).ifPresent(nonNull -> {
            try { key(nonNull.get()); } // assuming key may throw SomeCheckedException
            catch(SomeCheckedException  e) { throw thrower.apply(e); }
        }));
}

Due to the nested operations the compiler is unable to infer the exception type automatically. The code above use an explicit declaration of the thrower parameter type. Alternatively you can use a type invocation of the helper method like

ContainingClass.<SomeCheckedException>attempt( thrower ->
    Optional.ofNullable(s).ifPresent(nonNull -> {
        try { key(nonNull.get()); }
        catch(SomeCheckedException  e) { throw thrower.apply(e); }
    }));


回答2:

You can't. Supplier#get() does not declare to throw any (checked) exceptions. Remember that a lambda expression simply creates an instance, it doesn't actually invoke the target functional interface method.

If you want to, you can wrap the checked exception in an unchecked exception and throw that.