Java 8:如何在流中处理抛出异常的方法?

232

假设我有一个类和一个方法

class A {
  void foo() throws Exception() {
    ...
  }
}

现在我想为流传递的每个A实例调用foo:

void bar() throws Exception {
  Stream<A> as = ...
  as.forEach(a -> a.foo());
}

问题:如何正确处理异常?我的代码在我的电脑上无法编译,因为我没有处理foo()可能抛出的异常。这里barthrows Exception似乎毫无用处。为什么会这样呢?


4
可能是重复问题:Java 8:Lambda-Streams,通过带有异常的方法过滤 - durron597
相关链接:https://dev59.com/C10a5IYBdhLWcg3woJ55 - AlikElzin-kilaka
1
使用Zalando的faux-pas库 - koppor
7个回答

184

你需要将方法调用包装到另一个方法中,在这个方法中你不会抛出已检查异常。你仍然可以抛出任何是RuntimeException子类的异常。

常规的包装惯用语如下:

private void safeFoo(final A a) {
    try {
        a.foo();
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

(超类型异常Exception仅用作示例,永远不要尝试自行捕获它)

然后,您可以这样调用它:as.forEach(this::safeFoo)


1
如果你想让它成为一个包装方法,我会声明它为静态的。它不使用任何来自'this'的东西。 - aalku
277
很遗憾我们不得不这样做,而不是使用我们的本地异常......哦Java啊,它给予了我们,然后又拿走了。 - Erich
1
@Stephan 那个答案已经被删除,但是仍然可以在这里找到:http://stackoverflow.com/a/27661562/309308 - Michael Mrozek
4
在我的公司,这个做法会被立即否决:我们不允许抛出未经检查的异常。 - Stelios Adamantidis
13
@SteliosAdamantidis,这个规则非常严格和低效。您是否知道所有API中的所有方法都被授权抛出各种运行时异常,而您甚至不需要知道它(当然您知道)?您是否因为JavaScript没有实现检查异常的概念而禁用了它?如果我是您的首席开发人员,我会禁用检查异常。 - spi
显示剩余3条评论

53

如果你只想调用foo,并且希望按原样传播异常(而不是包装),那么你也可以只使用Java的for循环(在使用一些技巧将流转换为可迭代对象后):

for (A a : (Iterable<A>) as::iterator) {
   a.foo();
}

至少在我的JUnit测试中,这就是我所做的,在那里我不想费心包装我的已检查异常(事实上,我更喜欢我的测试抛出未包装的原始异常)


1
您也可以使用传统的for循环,例如:for (Iterator <A> it = astream.iterator() ; it.hasNext() ;) { it.next().foo(); } - guymac

23

这个问题可能有点老,但我认为这里的“正确”答案只有一种方式,这可能会导致以后在您的代码中隐藏一些问题。即使存在一些争议,Checked Exceptions的存在是有原因的。

在我看来,最优雅的方法是由Misha在这里给出的Aggregate runtime exceptions in Java 8 streams,只需在“futures”中执行操作。这样,您可以运行所有工作部分并将未工作的Exceptions收集为单个异常。否则,您可以将它们全部收集到一个列表中,稍后再处理它们。

类似的方法来自于Benji Weber。他建议创建自己的类型来收集工作和未工作的部分。

根据您想要实现的内容,输入值和输出值之间的简单映射可能也适合您。

如果您不喜欢任何这些方式,请考虑使用(根据原始异常)至少一个自定义异常。


4
这应该被标记为正确答案,但我不能说这是一篇好帖子。你应该更详细地阐述它。自己的异常如何帮助解决问题?发生了哪些异常?为什么没有在这里列出不同的变体?在SO上,把所有示例代码都放在参考文献中被视为一种不允许的文章风格。你的文本看起来更像一堆评论。 - Gangnus
2
你应该能够选择在哪个级别上捕获异常并处理流。Stream API 应该允许你将异常一直传递到最终操作(如 collect),并在那里使用处理程序处理或以其他方式抛出。stream.map(Streams.passException(x->mightThrowException(x))).catch(e->whatToDo(e)).collect(...)。它应该期望异常并允许你像 futures 一样处理它们。 - aalku

16

您可能希望执行以下操作之一:

  • 传播已检查异常,
  • 将其包装并传播未经检查的异常,或者
  • 捕获异常并停止传播。

多个库可以轻松实现这一点。下面的示例使用我的NoException库编写。

// Propagate checked exception
as.forEach(Exceptions.sneak().consumer(A::foo));

// Wrap and propagate unchecked exception
as.forEach(Exceptions.wrap().consumer(A::foo));
as.forEach(Exceptions.wrap(MyUncheckedException::new).consumer(A::foo));

// Catch the exception and stop propagation (using logging handler for example)
as.forEach(Exceptions.log().consumer(Exceptions.sneak().consumer(A::foo)));

3
图书馆做得真不错!我正想写类似的东西。 - Jasper de Vries

14

我建议使用Google Guava的Throwables类

propagate(Throwable throwable)

如果throwable实例是RuntimeException或Error,则按原样传播它;否则,作为最后的手段,将其包装在RuntimeException中,然后传播。**

void bar() {
    Stream<A> as = ...
    as.forEach(a -> {
        try {
            a.foo()
        } catch(Exception e) {
            throw Throwables.propagate(e);
        }
    });
}

更新:

现在它已经被弃用,请使用:

void bar() {
    Stream<A> as = ...
    as.forEach(a -> {
        try {
            a.foo()
        } catch(Exception e) {
            Throwables.throwIfUnchecked(e);
            throw new RuntimeException(e);
        }
    });
}

5
这种方法已经过时了(很遗憾)。 - Robert Važan

10
您可以使用这种方法包装和取消包装异常。
class A {
    void foo() throws Exception {
        throw new Exception();
    }
};

interface Task {
    void run() throws Exception;
}

static class TaskException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    public TaskException(Exception e) {
        super(e);
    }
}

void bar() throws Exception {
      Stream<A> as = Stream.generate(()->new A());
      try {
        as.forEach(a -> wrapException(() -> a.foo())); // or a::foo instead of () -> a.foo()
    } catch (TaskException e) {
        throw (Exception)e.getCause();
    }
}

static void wrapException(Task task) {
    try {
        task.run();
    } catch (Exception e) {
        throw new TaskException(e);
    }
}

6
更易读的方式:
class A {
  void foo() throws MyException() {
    ...
  }
}

只需将其隐藏在 RuntimeException 中,即可通过 forEach()

  void bar() throws MyException {
      Stream<A> as = ...
      try {
          as.forEach(a -> {
              try {
                  a.foo();
              } catch(MyException e) {
                  throw new RuntimeException(e);
              }
          });
      } catch(RuntimeException e) {
          throw (MyException) e.getCause();
      }
  }

尽管在这一点上,如果有人说跳过流而使用for循环,我也不会反对,除非:

  • 您没有使用Collection.stream()创建您的流,即不能直接将其翻译为for循环。
  • 您正在尝试使用parallelstream()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接