如何从 Java 8 流内部抛出 CHECKED 异常? [英] How can I throw CHECKED exceptions from inside Java 8 streams?

查看:23
本文介绍了如何从 Java 8 流内部抛出 CHECKED 异常?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何从 Java 8 流/lambda 内部抛出 CHECKED 异常?

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<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,这是被检查的.

This code does not compile, since the Class.forName() method above throws ClassNotFoundException, which is checked.

请注意,我不想将已检查的异常包装在运行时异常中,而是抛出已包装的未检查异常.我想自己抛出已检查的异常,并且不向流中添加难看的try/catches.

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.

推荐答案

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

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

注意 Class::forName 抛出 ClassNotFoundException,这是检查.流本身也会抛出 ClassNotFoundException,而不是一些包装的未经检查的异常.

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

}

关于如何使用它的许多其他示例(在静态导入 LambdaExceptionUtil 之后):

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

截至 2015 年 11 月的更新 在@PaoloC 的帮助下,代码得到了改进,请检查他的在下面回答并投票.他帮助解决了最后一个问题:现在编译器会要求你添加 throw 子句,一切就好像你可以在 Java 8 流上本地抛出已检查的异常一样.

UPDATE as of Nov 2015 The code has been improved with the help of @PaoloC, please check his answer below and upvote it. He 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.

注意 1 上面LambdaExceptionUtil 类的rethrow 方法可以放心使用,并且可以在任何情况下使用情况.

NOTE 1 The rethrow methods of the LambdaExceptionUtil class above may be used without fear, and are OK to use in any situation.

注意 2: 上面 LambdaExceptionUtil 类的 uncheck 方法是额外的方法,如果你不想使用它们.如果您确实使用过它们,请谨慎使用,并且在了解以下用例、优点/缺点和限制之前不要这样做:

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:

• 如果您调用的方法实际上永远不会抛出它声明的异常,则可以使用uncheck 方法.例如: new String(byteArr, "UTF-8") 抛出 UnsupportedEncodingException,但 Java 规范保证 UTF-8 始终存在.在这里,throws 声明是一个令人讨厌的东西,欢迎任何用最少的样板来使其静音的解决方案:String text = uncheck(() -> new String(byteArr, UTF-8"));

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

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

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

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

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

  • 参考文献:

这篇关于如何从 Java 8 流内部抛出 CHECKED 异常?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆