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

前端 未结 18 1599
你的背包
你的背包 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:20

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

    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.

    That is one example. Another example is that you could .orReturn() some default value.

    Note that this is STILL a work in progress, more is to come. Better names, more features etc.

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

    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). And no instanceof or something is needed.

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

    This LambdaExceptionUtil helper class lets you use any checked exceptions in Java streams, like this:

    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. The stream itself also throws ClassNotFoundException, and NOT some wrapping unchecked exception.

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

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


    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. 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. For example: new String(byteArr, "UTF-8") throws UnsupportedEncodingException, but UTF-8 is guaranteed by the Java spec to always be present. 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"));

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

    • 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). 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. 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. For this reason, I believe this should not be done in framework code, but only in business code that you completely control.

    • References:
      • http://www.philandstuff.com/2012/04/28/sneakily-throwing-checked-exceptions.html
      • http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
      • Project Lombok annotation: @SneakyThrows
      • Brian Goetz opinion (against) here: How can I throw CHECKED exceptions from inside Java 8 streams?
      • https://softwareengineering.stackexchange.com/questions/225931/workaround-for-java-checked-exceptions?newreg=ddf0dd15e8174af8ba52e091cf85688e *
    0 讨论(0)
  • 2020-11-22 07:25

    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.

    Instructions: just copy/paste LambdaExceptionUtil in your IDE and then use it as shown in the below 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());
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 07:26

    I agree with the comments above, in using Stream.map you are limited to implementing Function which doesn't throw Exceptions.

    You could however create your own FunctionalInterface that throws as below..

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

    then implement it using Lambdas or references as shown below.

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

    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.

    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.

    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.

    0 讨论(0)
提交回复
热议问题