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

前端 未结 18 1562
你的背包
你的背包 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: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 {
        void accept(T t) throws E;
        }
    
    @FunctionalInterface
    public interface BiConsumer_WithExceptions {
        void accept(T t, U u) throws E;
        }
    
    @FunctionalInterface
    public interface Function_WithExceptions {
        R apply(T t) throws E;
        }
    
    @FunctionalInterface
    public interface Supplier_WithExceptions {
        T get() throws E;
        }
    
    @FunctionalInterface
    public interface Runnable_WithExceptions {
        void run() throws E;
        }
    
    /** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
    public static  Consumer rethrowConsumer(Consumer_WithExceptions consumer) throws E {
        return t -> {
            try { consumer.accept(t); }
            catch (Exception exception) { throwAsUnchecked(exception); }
            };
        }
    
    public static  BiConsumer rethrowBiConsumer(BiConsumer_WithExceptions 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  Function rethrowFunction(Function_WithExceptions 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  Supplier rethrowSupplier(Supplier_WithExceptions 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 uncheck(Supplier_WithExceptions supplier)
        {
        try { return supplier.get(); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        }
    
    /** uncheck(Class::forName, "xxx"); */
    public static  R uncheck(Function_WithExceptions function, T t) {
        try { return function.apply(t); }
        catch (Exception exception) { throwAsUnchecked(exception); return null; }
        }
    
    @SuppressWarnings ("unchecked")
    private static  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 classes1
              = Stream.of("Object", "Integer", "String")
                      .map(rethrowFunction(className -> Class.forName("java.lang." + className)))
                      .collect(Collectors.toList());
    
        List 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 *

提交回复
热议问题