首页 文章

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

提问于
浏览
239

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

换句话说,我想像这样编译代码:

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;
    }

此代码无法编译,因为上面的 Class.forName() 方法会抛出 ClassNotFoundException ,这会被检查 .

请注意我不想将已检查的异常包装在运行时异常中,而是抛出包装的未经检查的异常 . I want to throw the checked exception itself ,并且没有将丑陋的 try / catches 添加到流中 .

15 回答

  • 5

    The simple answer to your question is: You can't, at least not directly. 这不是你的错 . 甲骨文搞砸了 . 他们坚持检查异常的概念,但在设计功能接口,流,lambda等时不一致地忘记处理已检查的异常 . 这对于像Robert C. Martin这样称为检查异常的专家来说是一个失败的实验 .

    这实际上是API中的一个巨大错误,也是语言规范中的一个小错误 .

    API中的错误是它没有提供转发已检查异常的工具,这实际上会对函数式编程产生很大的意义 . 正如我将在下面演示的那样,这样的设施很容易实现 .

    语言规范中的错误是它不允许类型参数推断类型列表而不是单个类型,只要类型参数仅用于允许类型列表的情况( throws 子句) .

    我们作为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);
        }
    }
    

    但是,它给出了:

    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
    

    当前定义功能接口的方式阻止了编译器转发异常 - 没有声明如果 Function.apply() throws EStream.map() throws E 也会告诉 Stream.map() .

    缺少的是用于传递已检查异常的类型参数的声明 . 以下代码显示了如何使用当前语法声明这样的传递类型参数 . 除了标记行中的特殊情况(这是下面讨论的限制)之外,此代码按预期编译和运行 .

    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();
        }   
    }
    

    throwSomeMore 的情况下,我们希望看到 IOException 被遗漏,但它实际上错过了 Exception .

    这并不完美,因为即使在异常的情况下,类型推断似乎也在寻找单一类型 . 因为类型推断需要单个类型, E 需要解析为 superIOException 的共同 super ,即 Exception .

    需要对类型推断的定义进行调整,以便如果在允许类型列表的情况下使用类型参数,编译器将查找多个类型( throws 子句) . 然后,编译器报告的异常类型将与引用方法的已检查异常的原始 throws 声明一样具体,而不是单个catch-all超类型 .

    坏消息是,这意味着Oracle搞砸了 . 当然它们不会破坏用户域代码,但是将异常类型参数引入现有的功能接口会破坏显式使用这些接口的所有用户域代码的编译 . 他们必须发明一些新的语法糖来解决这个问题 .

    更糟糕的消息是,Brian Goetz在2010年https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java已经讨论过这个话题,似乎这个问题被忽略了,所以我想知道Oracle在做什么 .

  • 0

    这个 LambdaExceptionUtil 帮助器类允许您在Java流中使用任何已检查的异常,如下所示:

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

    注意 Class::forName 抛出 ClassNotFoundException ,即 checked . 流本身也抛出 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; }
    
    }
    

    关于如何使用它的许多其他示例(在静态导入 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: 上面 LambdaExceptionUtil 类的 rethrow 方法可以毫无顾虑地使用,并且是 OK to use in any situation . 非常感谢帮助解决最后一个问题的用户@PaoloC:现在编译器会要求你添加throw子句和所有东西,好像你可以在Java 8流上本地抛出已检查的异常 .


    NOTE 2: 上面 LambdaExceptionUtil 类的 uncheck 方法是奖励方法,如果您不想使用它们,可以安全地从课程中删除它们 . 如果您确实使用过它们,请小心操作,而不是在了解以下用例,优点/缺点和限制之前:

    •如果要调用的方法实际上永远不会抛出它声明的异常,则可以使用 uncheck 方法 . 例如:new String(byteArr,"UTF-8")抛出UnsupportedEncodingException,但Java规范保证UTF-8始终存在 . 在这里,投掷声明是一个麻烦,任何解决方案,以最小的样板沉默它是受欢迎的: String text = uncheck(() -> new String(byteArr, "UTF-8"));

    •如果您实现的是严格的接口,而您没有添加throws声明的选项,则可以使用 uncheck 方法,但抛出异常是完全合适的 . 仅仅包装一个例外获得抛出它的特权会导致带有虚假异常的堆栈跟踪,这些异常不会导致实际出错的信息 . 一个很好的例子是Runnable.run(),它不会抛出任何已检查的异常 .

    •在任何情况下,如果您决定使用 uncheck 方法,请注意在没有throws子句的情况下抛出CHECKED异常的这两个后果:1)调用代码将无法按名称捕获它(如果您尝试,编译器会说:异常永远不会抛出相应的try语句的主体) . 它会冒泡并可能被主程序循环中的一些"catch Exception"或"catch Throwable"捕获,这可能是你想要的 . 2)它违反了最不令人意外的原则:它将不再足以捕获 RuntimeException 以保证捕获所有可能的异常 . 出于这个原因,我认为这不应该在框架代码中完成,而只能在您完全控制的业务代码中完成 .

  • 1

    你不能安全地做到这一点 . 你可以作弊,但是你的程序被打破了,这将不可避免地再次咬人(应该是你,但我们的作弊常常会打击别人 . )

    这是一个稍微安全的方法(但我仍然不建议这样做 . )

    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;
    }
    

    在这里,您正在做的是捕获lambda中的异常,从流管道中抛出一个信号,指示计算异常失败,捕获信号,并对该信号进行操作以抛出基础异常 . 关键是你总是捕获合成异常,而不是允许检查异常泄漏而不声明抛出异常 .

  • 1

    您可以!

    扩展@marcg的 UtilException 并在必要时添加 throw E :这样, the compiler will ask you to add throw clauses 就像你可以在java 8的流上本地抛出已检查的异常一样 .

    说明:只需在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;
        }
    
    }
    

    一些测试显示用法和行为:

    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());
        }
    
    }
    
  • 11

    只需使用NoException(我的项目),jOOλ's 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));
    
  • 3

    我编写了a library,它扩展了Stream API,允许您抛出已检查的异常 . 它使用Brian Goetz的技巧 .

    你的代码会变成

    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());
    }
    
  • 0

    这个答案类似于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
            }
    
  • 21

    你不能 .

    但是,您可能需要查看one of my projects,它允许您更轻松地操作此类"throwing lambdas" .

    在您的情况下,您将能够这样做:

    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());
    

    并 grab MyException .

    这是一个例子 . 另一个例子是你可以 .orReturn() 一些默认值 .

    请注意,这仍然是一项正在进行的工作,更多的是即将到来 . 更好的名字,更多的功能等

  • 152

    总结上面的注释,高级解决方案是使用特殊的包装器来实现未经检查的功能,使用类似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());
    

    下面的代码演示了消费者,供应商和功能接口 . 它可以很容易地扩展 . 此示例删除了一些公共关键字 .

    Try 是客户端代码的 endpoints . 安全方法可能具有每种功能类型的唯一名称 . CheckedConsumerCheckedSupplierCheckedFunction 是lib函数的检查类似物,可以独立于 Try 使用

    CheckedBuilder 是用于处理某些已检查函数中的异常的接口 . 如果上一次失败, orTry 允许执行另一个相同类型的函数 . handle 提供异常处理,包括异常类型过滤 . 处理程序的顺序很重要 . 减少方法 unsaferethrow 重新抛出执行链中的最后一个异常 . 如果所有函数都失败,则减少方法 orElseorElseGet 返回备用值,如可选值 . 还有方法 suppress . 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);
        }
    }
    
  • 0

    我使用这种包装异常:

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

    它需要静态处理这些异常:

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

    虽然在第一次 rethrow() 调用期间无论如何都会重新抛出异常(哦,Java泛型......),这种方式允许获得可能异常的严格静态定义(需要在 throws 中声明它们) . 不需要 instanceof 或其他东西 .

  • 21

    TL;DR Just use Lombok's @SneakyThrows .

    Christian Hujer已经详细解释了为什么由于Java的限制而严格地说,从流中抛出已检查的异常是不可能的 .

    其他一些答案解释了解决语言局限性的技巧,但仍然能够满足抛出"the checked exception itself, and without adding ugly try/catches to the stream"的要求,其中一些需要额外的几行样板 .

    我要强调另一个选择这样做,恕我直言比所有的更清洁其他人:龙目岛的 @SneakyThrows . 它已经被其他答案所提及,但在很多不必要的细节下被埋没了 .

    生成的代码非常简单:

    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);
    }
    

    我们只需要一个 Extract Method 重构(由IDE完成)和一个额外的行 @SneakyThrows . 注释负责添加所有样板文件,以确保您可以抛出已检查的异常而不将其包装在_710163中,而无需明确声明它 .

  • 214

    我同意上面的注释,在使用Stream.map时,您仅限于实现不抛出异常的Function .

    但是,您可以创建自己的FunctionalInterface,如下所示抛出..

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

    然后使用Lambdas或引用实现它,如下所示 .

    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");
    
     }
    
    }
    
  • 0

    处理 map 操作可以抛出的已检查异常的唯一内置方法是将它们封装在CompletableFuture中 . (如果您不需要保留异常,则Optional是一种更简单的替代方法 . )这些类旨在允许您以功能方式表示或有操作 .

    需要一些非平凡的辅助方法,但是您可以获得's relatively concise, while still making it apparent that your stream' s结果取决于 map 操作已成功完成的代码 . 这是它的样子:

    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; });
    

    这会产生以下输出:

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

    applyOrDie方法接受一个抛出异常的 Function ,并将其转换为 Function ,返回已经完成的 CompletableFuture - 正常完成原始函数的结果,或者异常完成抛出异常 .

    第二个 map 操作说明你现在有一个 Stream<CompletableFuture<T>> 而不是 Stream<T> . 如果上游操作成功,则 CompletableFuture 仅负责执行此操作 . API使这个显而易见,但相对无痛 .

    直到你进入 collect 阶段,那就是 . 这是我们需要一个非常重要的辅助方法的地方 . 我们希望"lift"正常的集合操作(在这种情况下, toList() )"inside" CompletableFuture - cfCollector()让我们使用 supplieraccumulatorcombinerfinisher 这样做,根本不需要了解 CompletableFuture .

    帮助器方法可以在我的MonadUtils类的GitHub上找到,这仍然是一项正在进行的工作 .

  • 6

    我认为这种方法是正确的:

    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;
    }
    

    Callable 中的已检查异常包含在 UndeclaredThrowableException 中(这是此异常的用例)并将其展开到外部 .

    是的,我发现它很丑陋,我建议不要在这种情况下使用lambdas,只是回到一个好的旧循环,除非你正在使用并行流和paralellization带来客观的好处,证明代码的不可读性 .

    正如许多其他人所指出的,这种情况有解决方案,我希望其中一个能够成为未来的Java版本 .

  • 6

    可能更好,更实用的方法是将异常包装起来并在流中进一步传播它们 . 例如,查看Try类型的Vavr .

    例:

    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)))
    

    要么

    @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);
            }
        };
    }
    

    第二个实现避免将异常包装在 RuntimeException 中 . throwUnchecked 有效,因为几乎所有的通用异常在java中都被视为未选中 .

相关问题