Java 8: Mandatory checked exceptions handling in lambda expressions. Why mandatory, not optional?

非 Y 不嫁゛ 提交于 2019-12-27 11:56:09

问题


I'm playing with the new lambda features in Java 8, and found that the practices offered by Java 8 are really useful. However, I'm wondering is there a good way to make a work-around for the following scenario. Suppose you have an object pool wrapper that requires some kind of a factory to fill the object pool, for example (using java.lang.functions.Factory):

public class JdbcConnectionPool extends ObjectPool<Connection> {

    public ConnectionPool(int maxConnections, String url) {
        super(new Factory<Connection>() {
            @Override
            public Connection make() {
                try {
                    return DriverManager.getConnection(url);
                } catch ( SQLException ex ) {
                    throw new RuntimeException(ex);
                }
            }
        }, maxConnections);
    }

}

After transforming the functional interface into lambda expression, the code above becomes like that:

public class JdbcConnectionPool extends ObjectPool<Connection> {

    public ConnectionPool(int maxConnections, String url) {
        super(() -> {
            try {
                return DriverManager.getConnection(url);
            } catch ( SQLException ex ) {
                throw new RuntimeException(ex);
            }
        }, maxConnections);
    }

}

Not so bad indeed, but the checked exception java.sql.SQLException requires a try/catch block inside the lambda. At my company we use two interfaces for long time:

  • IOut<T> that is an equivalent to java.lang.functions.Factory;
  • and a special interface for the cases that usually require checked exceptions propagation: interface IUnsafeOut<T, E extends Throwable> { T out() throws E; }.

Both IOut<T> and IUnsafeOut<T> are supposed to be removed during migration to Java 8, however there is no exact match for IUnsafeOut<T, E>. If the lambda expressions could deal with checked exceptions like they were unchecked, it could be possible to use simply like the following in the constructor above:

super(() -> DriverManager.getConnection(url), maxConnections);

That looks much cleaner. I see that I can rewrite the ObjectPool super class to accept our IUnsafeOut<T>, but as far as I know, Java 8 is not finished yet, so could be there some changes like:

  • implementing something similar to IUnsafeOut<T, E>? (to be honest, I consider that dirty - the subject must choose what to accept: either Factory or "unsafe factory" that cannot have compatible method signatures)
  • simply ignoring checked exceptions in lambdas, so no need in IUnsafeOut<T, E> surrogates? (why not? e.g. another important change: OpenJDK, that I use, javac now does not require variables and parameters to be declared as final to be captured in an anonymous class [functional interface] or lambda expression)

So the question is generally is: is there a way to bypass checked exceptions in lambdas or is it planned in the future until Java 8 is finally released?


Update 1

Hm-m-m, as far as I understand what we currently have, it seems there is no way at the moment, despite the referenced article is dated from 2010: Brian Goetz explains exception transparency in Java. If nothing changed much in Java 8, this could be considered an answer. Also Brian says that interface ExceptionalCallable<V, E extends Exception> (what I mentioned as IUnsafeOut<T, E extends Throwable> out of our code legacy) is pretty much useless, and I agree with him.

Do I still miss something else?


回答1:


Not sure I really answer your question, but couldn't you simply use something like that?

public final class SupplierUtils {
    private SupplierUtils() {
    }

    public static <T> Supplier<T> wrap(Callable<T> callable) {
        return () -> {
            try {
                return callable.call();
            }
            catch (RuntimeException e) {
                throw e;
            }
            catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }
}

public class JdbcConnectionPool extends ObjectPool<Connection> {

    public JdbcConnectionPool(int maxConnections, String url) {
        super(SupplierUtils.wrap(() -> DriverManager.getConnection(url)), maxConnections);
    }
}



回答2:


In the lambda mailing list this was throughly discussed. As you can see Brian Goetz suggested there that the alternative is to write your own combinator:

Or you could write your own trivial combinator:

static<T> Supplier<T> exceptionWrappingSupplier(Supplier<T> b) {
     return e -> {
         try { b.accept(e); }
         catch (Exception e) { throw new RuntimeException(e); }
     };
}

You can write it once, in less that the time it took to write your original e-mail. And similarly once for each kind of SAM you use.

I'd rather we look at this as "glass 99% full" rather than the alternative. Not all problems require new language features as solutions. (Not to mention that new language features always causes new problems.)

In those days the Consumer interface was called Block.

I think this corresponds with JB Nizet's answer.

Later Brian explains why this was designed this way (the reason of problem)

Yes, you'd have to provide your own exceptional SAMs. But then lambda conversion would work fine with them.

The EG discussed additional language and library support for this problem, and in the end felt that this was a bad cost/benefit tradeoff.

Library-based solutions cause a 2x explosion in SAM types (exceptional vs not), which interact badly with existing combinatorial explosions for primitive specialization.

The available language-based solutions were losers from a complexity/value tradeoff. Though there are some alternative solutions we are going to continue to explore -- though clearly not for 8 and probably not for 9 either.

In the meantime, you have the tools to do what you want. I get that you prefer we provide that last mile for you (and, secondarily, your request is really a thinly-veiled request for "why don't you just give up on checked exceptions already"), but I think the current state lets you get your job done.




回答3:


September 2015:

You can use ET for this. ET is a small Java 8 library for exception conversion/translation.

With ET you can write:

super(() -> et.withReturningTranslation(() -> DriverManager.getConnection(url)), maxConnections);

Multi line version:

super(() -> {
  return et.withReturningTranslation(() -> DriverManager.getConnection(url));
}, maxConnections);

All you need to do before, is creating a new ExceptionTranslator instance:

ExceptionTranslator et = ET.newConfiguration().done();

This instance is thread safe an can be shared by multiple components. You can configure more specific exception conversion rules (e.g. FooCheckedException -> BarRuntimeException) if you like. If no other rules are available, checked exceptions are automatically converted to RuntimeException.

(Disclaimer: I am the author of ET)




回答4:


Have you considered using a RuntimeException (unchecked) wrapper class to smuggle the original exception out of the lambda expression, then casting the wrapped exception back to it's original checked exception?

class WrappedSqlException extends RuntimeException {
    static final long serialVersionUID = 20130808044800000L;
    public WrappedSqlException(SQLException cause) { super(cause); }
    public SQLException getSqlException() { return (SQLException) getCause(); }
}

public ConnectionPool(int maxConnections, String url) throws SQLException {
    try {
        super(() -> {
            try {
                return DriverManager.getConnection(url);
            } catch ( SQLException ex ) {
                throw new WrappedSqlException(ex);
            }
        }, maxConnections);
    } catch (WrappedSqlException wse) {
        throw wse.getSqlException();
    }
}

Creating your own unique class should prevent any likelihood of mistaking another unchecked exception for the one you wrapped inside your lambda, even if the exception is serialized somewhere in the pipeline before you catch and re-throw it.

Hmm... The only thing I see that's a problem here is that you are doing this inside a constructor with a call to super() which, by law, must be the first statement in your constructor. Does try count as a previous statement? I have this working (without the constructor) in my own code.




回答5:


We developed an internal project in my company that helped us with this. We decided to went public two months ago.

This is what we came up with:

@FunctionalInterface
public interface ThrowingFunction<T,R,E extends Throwable> {
R apply(T arg) throws E;

/**
 * @param <T> type
 * @param <E> checked exception
 * @return a function that accepts one argument and returns it as a value.
 */
static <T, E extends Exception> ThrowingFunction<T, T, E> identity() {
    return t -> t;
}

/**
 * @return a Function that returns the result of the given function as an Optional instance.
 * In case of a failure, empty Optional is returned
 */
static <T, R, E extends Exception> Function<T, Optional<R>> lifted(ThrowingFunction<T, R, E> f) {
    Objects.requireNonNull(f);

    return f.lift();
}

static <T, R, E extends Exception> Function<T, R> unchecked(ThrowingFunction<T, R, E> f) {
    Objects.requireNonNull(f);

    return f.uncheck();
}

default <V> ThrowingFunction<V, R, E> compose(final ThrowingFunction<? super V, ? extends T, E> before) {
    Objects.requireNonNull(before);

    return (V v) -> apply(before.apply(v));
}

default <V> ThrowingFunction<T, V, E> andThen(final ThrowingFunction<? super R, ? extends V, E> after) {
    Objects.requireNonNull(after);

    return (T t) -> after.apply(apply(t));
}

/**
 * @return a Function that returns the result as an Optional instance. In case of a failure, empty Optional is
 * returned
 */
default Function<T, Optional<R>> lift() {
    return t -> {
        try {
            return Optional.of(apply(t));
        } catch (Throwable e) {
            return Optional.empty();
        }
    };
}

/**
 * @return a new Function instance which wraps thrown checked exception instance into a RuntimeException
 */
default Function<T, R> uncheck() {
    return t -> {
        try {
            return apply(t);
        } catch (final Throwable e) {
            throw new WrappedException(e);
        }
    };
}

}

https://github.com/TouK/ThrowingFunction/




回答6:


Wrapping the exception in the described way does not work. I tried it and I still get compiler errors, which is actually according to the spec: the lambda expression throws the exception which is incompatible with the target type of the method argument: Callable; call() does not throw it so I can not pass the lambda expression as a Callable.

So basically there is no solution: we are stuck with writing boilerplate. The only thing we can do is voice our opinion that this needs fixing. I think the spec should not just blindly discard a target type based on incompatible thrown exceptions: it should subsequently check whether the thrown incompatible exception is caught or declared as throws in the invoking scope. And for lambda expressions that are not inlined I propose we can mark them as silently throwing checked exception (silent in the sense that the compiler should not check, but the runtime should still catch). let's mark those with => as opposed to -> I know this is not a discussion site, but since this IS the only solution to the question, let yourself be heard and let's change this spec!




回答7:


Paguro provides functional interfaces that wrap checked exceptions. I started working on it a few months after you asked your question, so you were probably part of the inspiration for it!

You'll notice that there are only 4 functional interfaces in Paguro vs. the 43 interfaces included with Java 8. That's because Paguro prefers generics to primitives.

Paguro has single-pass transformations built into its immutable collections (copied from Clojure). These transforms are roughly equivalent to Clojure transducers or Java 8 streams, but they accept functional interfaces that wrap checked exceptions. See: the differences between Paguro and Java 8 streams.




回答8:


You can throw from your lambdas, they just have to be declared "with your own way" (which makes them, unfortunately, not re-usable in standard JDK code, but hey, we does what we cans).

@FunctionalInterface
public interface SupplierIOException {
   MyClass get() throws IOException;
}

Or the more generic-ized version:

public interface ThrowingSupplier<T, E extends Exception> {
  T get() throws E;
}

ref here. There is also a mention of using "sneakyThrow" to not declare the checked exceptions, but then still throw them. It that hurts my head a bit, maybe an option.



来源:https://stackoverflow.com/questions/14039995/java-8-mandatory-checked-exceptions-handling-in-lambda-expressions-why-mandato

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!