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

新手上路,请多包涵

如何从 Java 8 流/lambda 中抛出 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 ,它已被检查。

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

原文由 MarcG 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 469
2 个回答

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

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

注意 Class::forName 抛出 ClassNotFoundException ,已 检查。流本身也会抛出 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");
    }

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


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


注 2: 上面的 LambdaExceptionUtil 类的 uncheck 方法是奖励方法,如果您不想使用它们,可以安全地将它们从类中删除。如果您确实使用了它们,请小心使用,并且在了解以下用例、优点/缺点和限制之前不要这样做:

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

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

• 在任何情况下,如果您决定使用 uncheck 方法,请注意在没有 throws 子句的情况下抛出 CHECKED 异常的这两个后果:1) 调用代码将无法通过以下方式捕获它name(如果你尝试,编译器会说:Exception is never throw in the body of corresponding try statement)。它会冒泡并且可能被一些“catch Exception”或“catch Throwable”捕获在主程序循环中,这可能是你想要的。 2) 它违反了最小意外原则:不再足以捕获 RuntimeException 以保证捕获所有可能的异常。出于这个原因,我认为这不应该在框架代码中完成,而只能在您完全控制的业务代码中完成。

原文由 MarcG 发布,翻译遵循 CC BY-SA 4.0 许可协议

对你的问题的简单回答是:你不能,至少不能直接。 这不是你的错。 甲骨文搞砸了。 他们坚持检查异常的概念,但在设计功能接口、流、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

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

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

 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

这并不完美,因为类型推断似乎在寻找单一类型,即使在异常情况下也是如此。 Because the type inference needs a single type, E needs to resolve to a common super of ClassNotFoundException and IOException , which is Exception

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

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

更糟糕的消息是,Brian Goetz 在 2010 年已经讨论过这个话题( https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java,http://mail.openjdk.java.net/pipermail/lambda-dev /2010-June/001484.html ),但我被告知这项调查最终没有成功,而且据我所知,Oracle 当前没有任何工作可以减轻检查异常和 lambda 之间的交互。

原文由 Christian Hujer 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题