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

前端 未结 18 1560
你的背包
你的背包 2020-11-22 06:59

How can I throw CHECKED exceptions from inside Java 8 streams/lambdas?

In other words, I want to make code like this compile:

public List

        
相关标签:
18条回答
  • 2020-11-22 07:31

    I wrote a library that extends the Stream API to allow you to throw checked exceptions. It uses Brian Goetz's trick.

    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());
    }
    
    0 讨论(0)
  • 2020-11-22 07:31

    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.

    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. It can be easly expanded. Some public keywords were removed for this example.

    Class Try is the endpoint for client code. 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

    CheckedBuilder is the interface for handling exceptions in some checked function. orTry allows execute another same type function if previous was failed. handle provides exception handling including exception type filtering. The order of handlers is important. Reduce methods unsafe and rethrow rethrows last exception in the execution chain. Reduce methods orElse and orElseGet return alternate value like Optional ones if all functions failed. Also there is method suppress. CheckedWrapper is the common implementation of 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 讨论(0)
  • 2020-11-22 07:35

    If you use Spring Framework you can use ReflectionUtils.rethrowRuntimeException(ex) described here https://docs.spring.io/spring/docs/5.2.8.RELEASE/javadoc-api/org/springframework/util/ReflectionUtils.html#rethrowRuntimeException-java.lang.Throwable- The beauty of this util method is that it re-throws exactly same exception but of Runtime type, so your catch block that expects exception of checked type will still catch it as intended.

    0 讨论(0)
  • 2020-11-22 07:40

    TL;DR Just use Lombok's @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.

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

    0 讨论(0)
  • 2020-11-22 07:40

    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);
        }
    
    0 讨论(0)
  • 2020-11-22 07:41

    Just use any one of NoException (my project), jOOλ's Unchecked, throwing-lambdas, Throwable interfaces, or Faux 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));
    
    0 讨论(0)
提交回复
热议问题