简体   繁体   English

"如何从 Java 8 流中抛出 CHECKED 异常?"

[英]How can I throw CHECKED exceptions from inside Java 8 streams?

How can I throw CHECKED exceptions from inside Java 8 streams/lambdas?如何从 Java 8 流/lambda 中抛出 CHECKED 异常?

In other words, I want to make code like this compile:换句话说,我想让这样的代码编译:

public List<Class> getClasses() throws ClassNotFoundException {     

    List<Class> classes = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
              .map(className -> Class.forName(className))
              .collect(Collectors.toList());                  
    return classes;
    }

This code does not compile, since the Class.forName() method above throws ClassNotFoundException , which is checked.这段代码无法编译,因为上面的Class.forName()方法会抛出ClassNotFoundException ,并对其进行检查。

Please note I do NOT want to wrap the checked exception inside a runtime exception and throw the wrapped unchecked exception instead.请注意,我不想将检查的异常包装在运行时异常中,而是抛出包装的未经检查的异常。 I want to throw the checked exception itself , and without adding ugly try / catches to the stream.我想抛出检查的异常本身,而不是向流中添加丑陋的try / catches

The simple answer to your question is: You can't, at least not directly.你的问题的简单答案是:你不能,至少不能直接。 And it's not your fault.这不是你的错。 Oracle messed it up.甲骨文搞砸了。 They cling on the concept of checked exceptions, but inconsistently forgot to take care of checked exceptions when designing the functional interfaces, streams, lambda etc. That's all grist to the mill of experts like Robert C. Martin who call checked exceptions a failed experiment.他们坚持受检异常的概念,但在设计函数式接口、流、lambda 等时始终没有考虑到受检异常。这对像 Robert C. Martin 这样的专家来说很重要,他们将受检异常称为失败的实验。

In my opinion, this is a huge bug in the API and a minor bug in the language specification .在我看来,这是API中的一个巨大错误语言规范中的一个小错误。

The bug in the API is that it provides no facility for forwarding checked exceptions where this actually would make an awful lot of sense for functional programming. API 中的错误在于它没有提供转发已检查异常的工具,而这实际上对函数式编程非常有意义。 As I will demonstrate below, such a facility would've been easily possible.正如我将在下面演示的那样,这样的设施很容易实现。

The bug in the language specification is that it does not allow a type parameter to infer a list of types instead of a single type as long as the type parameter is only used in situations where a list of types is permissable ( throws clause).语言规范中的错误在于它不允许类型参数推断类型列表而不是单个类型,只要类型参数仅用于允许类型列表的情况( throws子句)。

Our expectation as Java programmers is that the following code should compile:我们作为 Java 程序员的期望是以下代码应该编译:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CheckedStream {
    // List variant to demonstrate what we actually had before refactoring.
    public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
        final List<Class> classes = new ArrayList<>();
        for (final String name : names)
            classes.add(Class.forName(name));
        return classes;
    }

    // The Stream function which we want to compile.
    public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
        return names.map(Class::forName);
    }
}

However, it gives:但是,它给出了:

cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java 
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
        return names.map(Class::forName);
                         ^
1 error

The way in which the functional interfaces are defined currently prevents the Compiler from forwarding the exception - there is no declaration which would tell Stream.map() that if Function.apply() throws E , Stream.map() throws E as well.函数接口的定义方式当前阻止编译器转发异常 - 没有声明会告诉Stream.map()如果Function.apply() throws EStream.map() throws E

What's missing is a declaration of a type parameter for passing through checked exceptions.缺少的是用于传递已检查异常的类型参数的声明。 The following code shows how such a pass-through type parameter actually could have been declared with the current syntax.以下代码显示了如何使用当前语法实际声明此类传递类型参数。 Except for the special case in the marked line, which is a limit discussed below, this code compiles and behaves as expected.除了标记行中的特殊情况,这是下面讨论的限制,此代码编译并按预期运行。

import java.io.IOException;
interface Function<T, R, E extends Throwable> {
    // Declare you throw E, whatever that is.
    R apply(T t) throws E;
}   

interface Stream<T> {
    // Pass through E, whatever mapper defined for E.
    <R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}   

class Main {
    public static void main(final String... args) throws ClassNotFoundException {
        final Stream<String> s = null;

        // Works: E is ClassNotFoundException.
        s.map(Class::forName);

        // Works: E is RuntimeException (probably).
        s.map(Main::convertClass);

        // Works: E is ClassNotFoundException.
        s.map(Main::throwSome);

        // Doesn't work: E is Exception.
        s.map(Main::throwSomeMore);  // error: unreported exception Exception; must be caught or declared to be thrown
    }   

    public static Class convertClass(final String s) {
        return Main.class;
    }   

    static class FooException extends ClassNotFoundException {}

    static class BarException extends ClassNotFoundException {}

    public static Class throwSome(final String s) throws FooException, BarException {
        throw new FooException();
    }   

    public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException  {
        throw new FooException();
    }   
}   

In the case of throwSomeMore we would like to see IOException being missed, but it actually misses Exception .throwSomeMore的情况下,我们希望看到IOException被错过,但它实际上错过了Exception

This is not perfect because type inference seems to be looking for a single type, even in the case of exceptions.这并不完美,因为类型推断似乎是在寻找单一类型,即使在出现异常的情况下也是如此。 Because the type inference needs a single type, E needs to resolve to a common super of ClassNotFoundException and IOException , which is Exception .因为类型推断需要单一类型,所以E需要解析为ClassNotFoundExceptionIOException的共同super类,即Exception

A tweak to the definition of type inference is needed so that the compiler would look for multiple types if the type parameter is used where a list of types is permissible ( throws clause).需要对类型推断的定义进行调整,以便在允许类型列表( throws子句)的情况下使用类型参数时,编译器将查找多种类型。 Then the exception type reported by the compiler would be as specific as the original throws declaration of the checked exceptions of the referenced method, not a single catch-all super type.那么编译器报告的异常类型将与被引用方法的已检查异常的原始throws声明一样具体,而不是单个包罗万象的超类型。

The bad news is that this means that Oracle messed it up.坏消息是,这意味着 Oracle 搞砸了。 Certainly they won't break user-land code, but introducing exception type parameters to the existing functional interfaces would break compilation of all user-land code that uses these interfaces explicitly.当然,它们不会破坏用户端代码,但向现有功能接口引入异常类型参数会破坏显式使用这些接口的所有用户端代码的编译。 They'll have to invent some new syntax sugar to fix this.他们将不得不发明一些新的语法糖来解决这个问题。

The even worse news is that this topic was already discussed by Brian Goetz in 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (new link: http://mail.openjdk.java.net/pipermail/lambda-dev/2010-June/001484.html ) but I'm informed that this investigation ultimately did not pan out, and that there is no current work at Oracle that I know of to mitigate the interactions between checked exceptions and lambdas.更糟糕的消息是,Brian Goetz 在 2010 年已经讨论过这个话题https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (新链接: http : //mail.openjdk.java.net/pipermail/lambda -dev/2010-June/001484.html ),但我被告知这项调查最终没有成功,而且我知道 Oracle 当前没有工作来减轻已检查异常和 lambda 之间的交互。

This LambdaExceptionUtil helper class lets you use any checked exceptions in Java streams, like this:这个LambdaExceptionUtil帮助器类允许您在 Java 流中使用任何已检查的异常,如下所示:

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
      .map(rethrowFunction(Class::forName))
      .collect(Collectors.toList());

Note Class::forName throws ClassNotFoundException , which is checked .注意Class::forName抛出ClassNotFoundException ,它被检查了 The stream itself also throws ClassNotFoundException , and NOT some wrapping unchecked exception.流本身也会抛出ClassNotFoundException ,而不是一些包装的未经检查的异常。

public final class LambdaExceptionUtil {

@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
    void accept(T t) throws E;
    }

@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
    void accept(T t, U u) throws E;
    }

@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
    R apply(T t) throws E;
    }

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

@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
    void run() throws E;
    }

/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
    return t -> {
        try { consumer.accept(t); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
    return (t, u) -> {
        try { biConsumer.accept(t, u); }
        catch (Exception exception) { throwAsUnchecked(exception); }
        };
    }

/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
    return t -> {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
    return () -> {
        try { return function.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        };
    }

/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
    {
    try { t.run(); }
    catch (Exception exception) { throwAsUnchecked(exception); }
    }

/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
    {
    try { return supplier.get(); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
    try { return function.apply(t); }
    catch (Exception exception) { throwAsUnchecked(exception); return null; }
    }

@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }

}

Many other examples on how to use it (after statically importing LambdaExceptionUtil ):关于如何使用它的许多其他示例(在静态导入LambdaExceptionUtil ):

@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));

    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .forEach(rethrowConsumer(System.out::println));
    }

@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
    List<Class> classes1
          = Stream.of("Object", "Integer", "String")
                  .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                  .collect(Collectors.toList());

    List<Class> classes2
          = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                  .map(rethrowFunction(Class::forName))
                  .collect(Collectors.toList());
    }

@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
    Collector.of(
          rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
          StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
    }

@Test    
public void test_uncheck_exception_thrown_by_method() {
    Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));

    Class clazz2 = uncheck(Class::forName, "java.lang.String");
    }

@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
    Class clazz3 = uncheck(Class::forName, "INVALID");
    }    

NOTE 1: The rethrow methods of the LambdaExceptionUtil class above may be used without fear, and are OK to use in any situation .注1: rethrow的方法LambdaExceptionUtil级以上可以毫不畏惧地被使用,并确定在任何情况下使用 A big thanks to user @PaoloC who helped solve the last problem: Now the compiler will ask you to add throw clauses and everything's as if you could throw checked exceptions natively on Java 8 streams.非常感谢帮助解决最后一个问题的用户 @PaoloC:现在编译器会要求您添加 throw 子句,一切就好像您可以在 Java 8 流上本地抛出已检查的异常一样。


NOTE 2: The uncheck methods of the LambdaExceptionUtil class above are bonus methods, and may be safely removed them from the class if you don't want to use them.注意 2:上面LambdaExceptionUtil类的uncheck方法是额外的方法,如果您不想使用它们,可以安全地将它们从类中删除。 If you do used them, do it with care, and not before understanding the following use cases, advantages/disadvantages and limitations:如果您确实使用过它们,请谨慎使用,并且在了解以下用例、优点/缺点和限制之前不要这样做:

• You may use the uncheck methods if you are calling a method which literally can never throw the exception that it declares. • 如果您调用的方法实际上永远不会抛出它声明的异常,则您可以使用uncheck方法。 For example: new String(byteArr, "UTF-8") throws UnsupportedEncodingException, but UTF-8 is guaranteed by the Java spec to always be present.例如: new String(byteArr, "UTF-8") 抛出 UnsupportedEncodingException,但 Java 规范保证 UTF-8 始终存在。 Here, the throws declaration is a nuisance and any solution to silence it with minimal boilerplate is welcome: String text = uncheck(() -> new String(byteArr, "UTF-8"));在这里,throws 声明是一个令人讨厌的东西,欢迎任何用最少的样板来使其静音的解决方案: String text = uncheck(() -> new String(byteArr, "UTF-8"));

• You may use the uncheck methods if you are implementing a strict interface where you don't have the option for adding a throws declaration, and yet throwing an exception is entirely appropriate. • 如果您正在实现一个严格的接口,而您没有添加 throws 声明的选项,但抛出异常却是完全合适的,那么您可以使用uncheck方法。 Wrapping an exception just to gain the privilege of throwing it results in a stacktrace with spurious exceptions which contribute no information about what actually went wrong.包装异常只是为了获得抛出它的特权会导致堆栈跟踪带有虚假异常,这些异常不提供有关实际出错的信息。 A good example is Runnable.run(), which does not throw any checked exceptions.一个很好的例子是 Runnable.run(),它不会抛出任何已检查的异常。

• In any case, if you decide to use the uncheck methods, be aware of these 2 consequences of throwing CHECKED exceptions without a throws clause: 1) The calling-code won't be able to catch it by name (if you try, the compiler will say: Exception is never thrown in body of corresponding try statement). • 在任何情况下,如果您决定使用uncheck方法,请注意在没有 throws 子句的情况下抛出 CHECKED 异常的以下 2 个后果:1) 调用代码将无法按名称捕获它(如果您尝试,编译器会说:在相应的 try 语句的主体中永远不会抛出异常)。 It will bubble and probably be caught in the main program loop by some "catch Exception" or "catch Throwable", which may be what you want anyway.它会冒泡,并且可能会被一些“catch Exception”或“catch Throwable”捕获在主程序循环中,无论如何这可能是您想要的。 2) It violates the principle of least surprise: it will no longer be enough to catch RuntimeException to be able to guarantee catching all possible exceptions. 2)它违反了最小惊奇原则:仅仅捕获RuntimeException已经不足以保证捕获所有可能的异常。 For this reason, I believe this should not be done in framework code, but only in business code that you completely control.为此,我认为这不应该在框架代码中完成,而只能在您完全控制的业务代码中完成。

You can't do this safely.你不能安全地做到这一点。 You can cheat, but then your program is broken and this will inevitably come back to bite someone (it should be you, but often our cheating blows up on someone else.)你可以作弊,但是你的程序被破坏了,这将不可避免地反过来咬人(应该是你,但我们的作弊往往会在其他人身上爆发。)

Here's a slightly safer way to do it (but I still don't recommend this.)这是一种稍微安全的方法(但我仍然不推荐这样做。)

class WrappedException extends RuntimeException {
    Throwable cause;

    WrappedException(Throwable cause) { this.cause = cause; }
}

static WrappedException throwWrapped(Throwable t) {
    throw new WrappedException(t);
}

try 
    source.stream()
          .filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
          ...
}
catch (WrappedException w) {
    throw (IOException) w.cause;
}

Here, what you're doing is catching the exception in the lambda, throwing a signal out of the stream pipeline that indicates that the computation failed exceptionally, catching the signal, and acting on that signal to throw the underlying exception.在这里,您正在做的是捕获 lambda 中的异常,从流管道中抛出一个信号,指示计算异常失败,捕获信号,并根据该信号采取行动以抛出底层异常。 The key is that you are always catching the synthetic exception, rather than allowing a checked exception to leak out without declaring that exception is thrown.关键是你总是在捕捉合成异常,而不是允许检查的异常泄漏出去而不声明异常被抛出。

You can!你可以!

Extending @marcg 's UtilException and adding throw E where necessary: this way, the compiler will ask you to add throw clauses and everything's as if you could throw checked exceptions natively on java 8's streams.扩展 @marcg 的UtilException并在必要时添加throw E :这样,编译器会要求您添加 throw 子句,一切就好像您可以在 Java 8 的流上本地抛出已检查的异常一样。

Instructions: just copy/paste LambdaExceptionUtil in your IDE and then use it as shown in the below LambdaExceptionUtilTest .说明:只需在您的 IDE 中复制/粘贴LambdaExceptionUtil ,然后使用它,如下面的LambdaExceptionUtilTest所示。

public final class LambdaExceptionUtil {

    @FunctionalInterface
    public interface Consumer_WithExceptions<T, E extends Exception> {
        void accept(T t) throws E;
    }

    @FunctionalInterface
    public interface Function_WithExceptions<T, R, E extends Exception> {
        R apply(T t) throws E;
    }

    /**
     * .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
     */
    public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
        return t -> {
            try {
                consumer.accept(t);
            } catch (Exception exception) {
                throwActualException(exception);
            }
        };
    }

    /**
     * .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName))
     */
    public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E  {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception exception) {
                throwActualException(exception);
                return null;
            }
        };
    }

    @SuppressWarnings("unchecked")
    private static <E extends Exception> void throwActualException(Exception exception) throws E {
        throw (E) exception;
    }

}

Some test to show usage and behaviour:一些显示用法和行为的测试:

public class LambdaExceptionUtilTest {

    @Test(expected = MyTestException.class)
    public void testConsumer() throws MyTestException {
        Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
    }

    private void checkValue(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
    }

    private class MyTestException extends Exception { }

    @Test
    public void testConsumerRaisingExceptionInTheMiddle() {
        MyLongAccumulator accumulator = new MyLongAccumulator();
        try {
            Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
            fail();
        } catch (MyTestException e) {
            assertEquals(9L, accumulator.acc);
        }
    }

    private class MyLongAccumulator {
        private long acc = 0;
        public void add(Long value) throws MyTestException {
            if(value==null) {
                throw new MyTestException();
            }
            acc += value;
        }
    }

    @Test
    public void testFunction() throws MyTestException {
        List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
        assertEquals(2, sizes.size());
        assertEquals(4, sizes.get(0).intValue());
        assertEquals(5, sizes.get(1).intValue());
    }

    private Integer transform(String value) throws MyTestException {
        if(value==null) {
            throw new MyTestException();
        }
        return value.length();
    }

    @Test(expected = MyTestException.class)
    public void testFunctionRaisingException() throws MyTestException {
        Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
    }

}

Just use any one of NoException (my project), jOOλ's Unchecked , throwing-lambdas , Throwable interfaces , or Faux Pas .只需使用NoException (我的项目)、 jOOλ 的 Uncheckedthrowing-lambdasThrowable interfacesFaux Pas 中的任何一种

// NoException
stream.map(Exceptions.sneak().function(Class::forName));

// jOOλ
stream.map(Unchecked.function(Class::forName));

// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());

// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));

// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));

I wrote a library that extends the Stream API to allow you to throw checked exceptions.我编写了一个扩展 Stream API的库,允许您抛出已检查的异常。 It uses Brian Goetz's trick.它使用了 Brian Goetz 的技巧。

Your code would become你的代码会变成

public List<Class> getClasses() throws ClassNotFoundException {     
    Stream<String> classNames = 
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");

    return ThrowingStream.of(classNames, ClassNotFoundException.class)
               .map(Class::forName)
               .collect(Collectors.toList());
}

This answer is similar to 17 but avoiding wrapper exception definition:此答案类似于 17,但避免了包装器异常定义:

List test = new ArrayList();
        try {
            test.forEach(obj -> {

                //let say some functionality throws an exception
                try {
                    throw new IOException("test");
                }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            });
        }
        catch (RuntimeException re) {
            if(re.getCause() instanceof IOException) {
                //do your logic for catching checked
            }
            else 
                throw re; // it might be that there is real runtime exception
        }

You cannot.你不能。

However, you may want to have a look at one of my projects which allows you to more easily manipulate such "throwing lambdas".但是,您可能想看看我的一个项目,它可以让您更轻松地操作此类“抛出 lambda”。

In your case, you would be able to do that:在您的情况下,您可以这样做:

import static com.github.fge.lambdas.functions.Functions.wrap;

final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);

List<Class> classes =
    Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(f.orThrow(MyException.class))
          .collect(Collectors.toList());

and catch MyException .并捕获MyException

That is one example.这就是一个例子。 Another example is that you could .orReturn() some default value.另一个例子是你可以.orReturn()一些默认值。

Note that this is STILL a work in progress, more is to come.请注意,这仍然是一项正在进行的工作,还有更多。 Better names, more features etc.更好的名字,更多的功能等。

TL;DR Just use Lombok's @SneakyThrows . TL;DR 只需使用 Lombok 的@SneakyThrows

Christian Hujer has already explained in detail why throwing checked exceptions from a stream is, strictly speaking, not possible due to Java's limitations. Christian Hujer 已经详细解释了为什么从流中抛出受检异常,严格来说,由于 Java 的限制是不可能的。

Some other answers have explained tricks to get around the limitations of the language but still being able to fulfil the requirement of throwing "the checked exception itself, and without adding ugly try/catches to the stream" , some of them requiring tens of additional lines of boilerplate.其他一些答案已经解释了绕过语言限制的技巧,但仍然能够满足抛出“已检查异常本身,并且不会向流添加丑陋的尝试/捕获”的要求,其中一些需要数十行额外的行样板。

I am going to highlight another option for doing this that IMHO is far cleaner than all the others: Lombok's @SneakyThrows .我将强调这样做的另一个选项,恕我直言,它比所有其他选项都干净得多:Lombok 的@SneakyThrows It has been mentioned in passing by other answers but was a bit buried under a lot of unnecessary detail.它已被其他答案顺便提及,但被许多不必要的细节所掩盖。

The resulting code is as simple as:生成的代码非常简单:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
        Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                .map(className -> getClass(className))
                .collect(Collectors.toList());
    return classes;
}

@SneakyThrows                                 // <= this is the only new code
private Class<?> getClass(String className) {
    return Class.forName(className);
}

We just needed one Extract Method refactoring (done by the IDE) and one additional line for @SneakyThrows .我们只需要一个Extract Method重构(由 IDE 完成)和一个额外的@SneakyThrows行。 The annotation takes care of adding all the boilerplate to make sure that you can throw your checked exception without wrapping it in a RuntimeException and without needing to declare it explicitly.注释负责添加所有样板文件,以确保您可以抛出已检查的异常,而无需将其包装在RuntimeException ,也无需显式声明它。

Summarizing the comments above the advanced solution is to use a special wrapper for unchecked functions with builder like API which provides recovering, rethrowing and suppresing.总结高级解决方案上面的评论是使用一个特殊的包装器来处理未检查的函数,像 API 这样的构建器提供了恢复、重新抛出和抑制。

Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
          .map(Try.<String, Class<?>>safe(Class::forName)
                  .handle(System.out::println)
                  .unsafe())
          .collect(toList());

Code below demonstrates it for Consumer, Supplier and Function interfaces.下面的代码为 Consumer、Supplier 和 Function 接口演示了它。 It can be easly expanded.它可以很容易地扩展。 Some public keywords were removed for this example.此示例中删除了一些公共关键字。

Class Try is the endpoint for client code.Try是客户端代码的端点。 Safe methods may have unique name for each function type.对于每个函数类型,安全方法可能具有唯一的名称。 CheckedConsumer , CheckedSupplier and CheckedFunction are checked analogs of lib functions which can be used independently of Try CheckedConsumerCheckedSupplierCheckedFunction是 lib 函数的检查类比,可以独立于Try 使用

CheckedBuilder is the interface for handling exceptions in some checked function. CheckedBuilder是用于处理某些已检查函数中的异常的接口。 orTry allows execute another same type function if previous was failed.如果前一个失败, orTry允许执行另一个相同类型的函数。 handle provides exception handling including exception type filtering. handle提供异常处理,包括异常类型过滤。 The order of handlers is important.处理程序的顺序很重要。 Reduce methods unsafe and rethrow rethrows last exception in the execution chain. Reduce 方法不安全rethrow重新抛出执行链中的最后一个异常。 Reduce methods orElse and orElseGet return alternate value like Optional ones if all functions failed.如果所有函数都失败,Reduce 方法orElseorElseGet 会返回替代值,例如 Optional 。 Also there is method suppress .还有方法抑制 CheckedWrapper is the common implementation of CheckedBuilder. CheckedWrapper是 CheckedBuilder 的常见实现。

final class Try {

    public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T> 
        safe(CheckedSupplier<T> supplier) {
        return new CheckedWrapper<>(supplier, 
                (current, next, handler, orResult) -> () -> {
            try { return current.get(); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().get() : orResult.apply(ex);
            }
        });
    }

    public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
        return supplier;
    }

    public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void> 
        safe(CheckedConsumer<T> consumer) {
        return new CheckedWrapper<>(consumer, 
                (current, next, handler, orResult) -> t -> {
            try { current.accept(t); } catch (Exception ex) {
                handler.accept(ex);
                if (next.isPresent()) {
                    next.get().accept(t);
                } else {
                    orResult.apply(ex);
                }
            }
        });
    }

    public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
        return consumer;
    }

    public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R> 
        safe(CheckedFunction<T, R> function) {
        return new CheckedWrapper<>(function, 
                (current, next, handler, orResult) -> t -> {
            try { return current.applyUnsafe(t); } catch (Exception ex) {
                handler.accept(ex);
                return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
            }
        });
    }

    public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
        return function;
    }

    @SuppressWarnings ("unchecked")
    static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E { 
        throw (E) exception; 
    }
}

@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
    void acceptUnsafe(T t) throws Exception;
    @Override default void accept(T t) {
        try { acceptUnsafe(t); } catch (Exception ex) {
            Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
    R applyUnsafe(T t) throws Exception;
    @Override default R apply(T t) {
        try { return applyUnsafe(t); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
    T getUnsafe() throws Exception;
    @Override default T get() {
        try { return getUnsafe(); } catch (Exception ex) {
            return Try.throwAsUnchecked(ex);
        }
    }
}

interface ReduceFunction<TSafe, TUnsafe, R> {
    TSafe wrap(TUnsafe current, Optional<TSafe> next, 
            Consumer<Throwable> handler, Function<Throwable, R> orResult);
}

interface CheckedBuilder<TSafe, TUnsafe, R> {
    CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);

    CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
            Class<E> exceptionType, Consumer<E> handler);

    CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);

    <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Class<E> exceptionType, Consumer<? super E> handler);

    TSafe unsafe();
    TSafe rethrow(Function<Throwable, Exception> transformer);
    TSafe suppress();
    TSafe orElse(R value);
    TSafe orElseGet(Supplier<R> valueProvider);
}

final class CheckedWrapper<TSafe, TUnsafe, R> 
        implements CheckedBuilder<TSafe, TUnsafe, R> {

    private final TUnsafe function;
    private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;

    private final CheckedWrapper<TSafe, TUnsafe, R> root;
    private CheckedWrapper<TSafe, TUnsafe, R> next;

    private Consumer<Throwable> handlers = ex -> { };
    private Consumer<Throwable> lastHandlers = ex -> { };

    CheckedWrapper(TUnsafe function, 
            ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
        this.function = function;
        this.reduceFunction = reduceFunction;
        this.root = this;
    }

    private CheckedWrapper(TUnsafe function, 
            CheckedWrapper<TSafe, TUnsafe, R> prev) {
        this.function = function;
        this.reduceFunction = prev.reduceFunction;
        this.root = prev.root;
        prev.next = this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
        return new CheckedWrapper<>(next, this);
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
            Consumer<Throwable> handler) {
        handlers = handlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handle(Class<E> exceptionType, Consumer<E> handler) {
        handlers = handlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
            Consumer<Throwable> handler) {
        lastHandlers = lastHandlers.andThen(handler);
        return this;
    }

    @Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> 
        handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
        lastHandlers = lastHandlers.andThen(ex -> {
            if (exceptionType.isInstance(ex)) {
                handler.accept(exceptionType.cast(ex));
            }
        });
        return this;
    }

    @Override public TSafe unsafe() {
        return root.reduce(ex -> Try.throwAsUnchecked(ex));
    }

    @Override
    public TSafe rethrow(Function<Throwable, Exception> transformer) {
        return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
    }

    @Override public TSafe suppress() {
        return root.reduce(ex -> null);
    }

    @Override public TSafe orElse(R value) {
        return root.reduce(ex -> value);
    }

    @Override public TSafe orElseGet(Supplier<R> valueProvider) {
        Objects.requireNonNull(valueProvider);
        return root.reduce(ex -> valueProvider.get());
    }

    private TSafe reduce(Function<Throwable, R> orResult) {
        return reduceFunction.wrap(function, 
                Optional.ofNullable(next).map(p -> p.reduce(orResult)), 
                this::handle, orResult);
    }

    private void handle(Throwable ex) {
        for (CheckedWrapper<TSafe, TUnsafe, R> current = this; 
                current != null; 
                current = current.next) {
            current.handlers.accept(ex);
        }
        lastHandlers.accept(ex);
    }
}

Probably, a better and more functional way is to wrap exceptions and propagate them further in the stream.可能更好、更实用的方法是包装异常并在流中进一步传播它们。 Take a look at the Try type of Vavr for example.例如,看看VavrTry类型。

Example:例子:

interface CheckedFunction<I, O> {
    O apply(I i) throws Exception; }

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return i -> {
        try {
            return f.apply(i);
        } catch(Exception ex) {

            throw new RuntimeException(ex);
        }
    } }

fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))

OR或者

@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
    throw (E) e;
}

static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
    return arg -> {
        try {
            return f.apply(arg);
        } catch(Exception ex) {
            return throwUnchecked(ex);
        }
    };
}

2nd implementation avoids wrapping the exception in a RuntimeException .第二个实现避免将异常包装在RuntimeException throwUnchecked works because almost always all generic exceptions are treated as unchecked in java. throwUnchecked有效,是因为几乎所有的通用异常在 java 中都被视为未检查的。

You can also write a wrapper method to wrap unchecked exceptions, and even enhance wrapper with additional parameter representing another functional interface (with the same return type R ).您还可以编写包装器方法来包装未经检查的异常,甚至可以使用表示另一个功能接口的附加参数(具有相同的返回类型R )来增强包装器。 In this case you can pass a function that would be executed and returned in case of exceptions.在这种情况下,您可以传递一个在异常情况下将被执行和返回的函数。 See example below:请参阅下面的示例:

private void run() {
    List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
            String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
    System.out.println(list.toString());
}

private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function, 
Function<T, R> onException) {
    return i -> {
        try {
            return function.apply(i);
        } catch (ArithmeticException e) {
            System.out.println("Exception: " + i);
            return onException.apply(i);
        } catch (Exception e) {
            System.out.println("Other: " + i);
            return onException.apply(i);
        }
    };
}

@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
    R apply(T t) throws E;
}

Here is a different view or solution for the original problem.这是原始问题的不同视图或解决方案。 Here I show that we have an option to write a code that will process only a valid subset of values with an option to detect and handle caseses when the exception was thrown.在这里,我展示了我们可以选择编写仅处理值的有效子集的代码,并且可以选择检测和处理抛出异常的情况。

    @Test
    public void getClasses() {

        String[] classNames = {"java.lang.Object", "java.lang.Integer", "java.lang.Foo"};
        List<Class> classes =
                Stream.of(classNames)
                        .map(className -> {
                            try {
                                return Class.forName(className);
                            } catch (ClassNotFoundException e) {
                                // log the error
                                return null;
                            }
                        })
                        .filter(c -> c != null)
                        .collect(Collectors.toList());

        if (classes.size() != classNames.length) {
            // add your error handling here if needed or process only the resulting list
            System.out.println("Did not process all class names");
        }

        classes.forEach(System.out::println);
    }

I agree with the comments above, in using Stream.map you are limited to implementing Function which doesn't throw Exceptions.我同意上面的评论,在使用 Stream.map 时,您只能实现不抛出异常的 Function。

You could however create your own FunctionalInterface that throws as below..但是,您可以创建自己的 FunctionalInterface,如下所示。

@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
  void accept(T instance) throws X;
}

then implement it using Lambdas or references as shown below.然后使用 Lambda 或引用实现它,如下所示。

import java.io.FileWriter;
import java.io.IOException;

//lambda expressions and the execute around method (EAM) pattern to
//manage resources

public class FileWriterEAM  {
  private final FileWriter writer;

  private FileWriterEAM(final String fileName) throws IOException {
    writer = new FileWriter(fileName);
  }
  private void close() throws IOException {
    System.out.println("close called automatically...");
    writer.close();
  }
  public void writeStuff(final String message) throws IOException {
    writer.write(message);
  }
  //...

  public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {

    final FileWriterEAM writerEAM = new FileWriterEAM(fileName);    
    try {
      block.accept(writerEAM);
    } finally {
      writerEAM.close();
    }
  }

  public static void main(final String[] args) throws IOException {

    FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));

    FileWriterEAM.use("eam2.txt", writerEAM -> {
        writerEAM.writeStuff("how");
        writerEAM.writeStuff("sweet");      
      });

    FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);     

  }


 void writeIt() throws IOException{
     this.writeStuff("How ");
     this.writeStuff("sweet ");
     this.writeStuff("it is");

 }

}

The only built-in way of handling checked exceptions that can be thrown by a map operation is to encapsulate them within a CompletableFuture .处理可以由map操作抛出的已检查异常的唯一内置方法是将它们封装在CompletableFuture (An Optional is a simpler alternative if you don't need to preserve the exception.) These classes are intended to allow you to represent contingent operations in a functional way. (如果您不需要保留异常,则Optional是一种更简单的替代方法。)这些类旨在允许您以函数方式表示可能发生的操作。

A couple of non-trivial helper methods are required, but you can arrive at code that's relatively concise, while still making it apparent that your stream's result is contingent on the map operation having completed successfully.需要一些重要的辅助方法,但您可以得到相对简洁的代码,同时仍然明显地表明您的流的结果取决于map操作是否成功完成。 Here's what it looks like:这是它的样子:

    CompletableFuture<List<Class<?>>> classes =
            Stream.of("java.lang.String", "java.lang.Integer", "java.lang.Double")
                  .map(MonadUtils.applyOrDie(Class::forName))
                  .map(cfc -> cfc.thenApply(Class::getSuperclass))
                  .collect(MonadUtils.cfCollector(ArrayList::new,
                                                  List::add,
                                                  (List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
                                                  x -> x));
    classes.thenAccept(System.out::println)
           .exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });

This produces the following output:这会产生以下输出:

[class java.lang.Object, class java.lang.Number, class java.lang.Number]

The applyOrDie method takes a Function that throws an exception, and converts it into a Function that returns an already-completed CompletableFuture -- either completed normally with the original function's result, or completed exceptionally with the thrown exception. applyOrDie方法接受一个抛出异常的Function ,并将它转换成一个返回一个已经完成的CompletableFutureFunction ——要么用原始函数的结果正常完成,要么用抛出的异常异常完成。

The second map operation illustrates that you've now got a Stream<CompletableFuture<T>> instead of just a Stream<T> .第二个map操作说明您现在获得了Stream<CompletableFuture<T>>而不是Stream<T> CompletableFuture takes care of only executing this operation if the upstream operation succeeded. CompletableFuture仅在上游操作成功时才​​执行此操作。 The API makes this explict, but relatively painless. API 使这变得明确,但相对轻松。

Until you get to the collect phase, that is.直到您进入collect阶段,即。 This is where we require a pretty significant helper method.这是我们需要一个非常重要的辅助方法的地方。 We want to "lift" a normal collection operation (in this case, toList() ) "inside" the CompletableFuture -- cfCollector() lets us do that using a supplier , accumulator , combiner , and finisher that don't need to know anything at all about CompletableFuture .我们想在CompletableFuture “内部”“提升”一个正常的收集操作(在这种情况下, toList() )—— cfCollector()允许我们使用不需要知道的supplieraccumulatorcombinerfinisher来做到这一点关于CompletableFuture任何事情。

The helper methods can be found on GitHub in my MonadUtils class, which is very much still a work in progress.可以在 GitHub 上我的MonadUtils类中找到辅助方法,该类仍在进行中。

I use this kind of wrapping exception:我使用这种包装异常:

public class CheckedExceptionWrapper extends RuntimeException {
    ...
    public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
        throw (T) getCause();
    }
}

It will require handling these exceptions statically:它将需要静态处理这些异常:

void method() throws IOException, ServletException {
    try { 
        list.stream().forEach(object -> {
            ...
            throw new CheckedExceptionWrapper(e);
            ...            
        });
    } catch (CheckedExceptionWrapper e){
        e.<IOException>rethrow();
        e.<ServletExcepion>rethrow();
    }
}

Try it online! 在线试试吧!

Though exception will be anyway re-thrown during first rethrow() call (oh, Java generics...), this way allows to get a strict statical definition of possible exceptions (requires to declare them in throws ).尽管在第一次rethrow()调用期间无论如何都会重新抛出异常(哦,Java 泛型......),但这种方式允许获得可能异常的严格静态定义(需要在throws声明它们)。 And no instanceof or something is needed.并且不需要instanceof或其他东西。

You can do it with apache commons-lang3 library.您可以使用 apache commons-lang3 库来做到这一点。

https:\/\/commons.apache.org\/proper\/commons-lang\/javadocs\/api-release\/org\/apache\/commons\/lang3\/function\/Failable.html<\/a> https:\/\/commons.apache.org\/proper\/commons-lang\/javadocs\/api-release\/org\/apache\/commons\/lang3\/function\/Failable.html<\/a>

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes =
            Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
                    .map(Failable.asFunction(Class::forName))
                    .collect(Collectors.toList());
    return classes;
}

I think this approach is the right one:我认为这种方法是正确的:

public List<Class> getClasses() throws ClassNotFoundException {
    List<Class> classes;
    try {
        classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
            try {
                return Class.forName(className);
            } catch (ClassNotFoundException e) {
                throw new UndeclaredThrowableException(e);
            }
        }).collect(Collectors.toList());
    } catch (UndeclaredThrowableException e) {
        if (e.getCause() instanceof ClassNotFoundException) {
            throw (ClassNotFoundException) e.getCause();
        } else {
            // this should never happen
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
    return classes;
}

Wrapping the checked exception inside the Callable in a UndeclaredThrowableException (that's the use case for this exception) and unwrapping it outside.Callable中的已检查异常包装在UndeclaredThrowableException (这是此异常的用例)并将其解包到外面。

Yes, I find it ugly, and I would advise against using lambdas in this case and just fall back to a good old loop, unless you are working with a parallel stream and paralellization brings an objective benefit that justifies the unreadability of the code.是的,我觉得它很丑,我建议不要在这种情况下使用 lambdas 并退回到一个好的旧循环,除非您正在使用并行流并且并行化带来客观的好处,证明代码的不可读性是合理的。

As many others have pointed out, there are solutions to this situation, and I hope one of them will make it into a future version of Java.正如许多其他人指出的那样,这种情况有解决方案,我希望其中一个将其纳入 Java 的未来版本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM