即使抛出异常,如何迭代流?

mem*_*und 7 java java-8 java-stream

stream.map(obj -> doMap(obj)).collect(Collectors.toList());

private String doMap(Object obj) {
    if (objectIsInvalid) {
    throw new ParseException("Object could not be parsed");
    }
}
Run Code Online (Sandbox Code Playgroud)

问题:如何抛出异常并使流迭代知道它不应该破坏整个迭代,而是继续下一个元素(并最终记录失败的对象)?

Stu*_*rks 5

这是您可以用来改进异常处理的一种怪异技巧。

假设您的mapper函数是这样的:

String doMap(Object obj) {
    if (isInvalid(obj)) {
        throw new IllegalArgumentException("info about obj");
    } else {
        return obj.toString();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果对象有效,则返回结果,但如果对象无效,则引发异常。不幸的是,如果直接将其粘贴到管道中,则任何错误都会停止管道执行。您想要的是一种类似“两种”类型的东西,可以容纳一个值或一个错误指示符(这在Java中是一个例外)。

事实证明,它CompletableFuture可以包含值或异常。尽管它是用于异步处理的(此处未发生),但我们只需对其进行扭曲即可使用。

首先,给定stream要处理的对象,我们调用包装在的调用中的映射函数supplyAsync

 CompletableFuture<String>[] cfArray = 
        stream.map(obj -> CompletableFuture.supplyAsync(() -> doMap(obj), Runnable::run))
              .toArray(n -> (CompletableFuture<String>[])new CompletableFuture<?>[n]);
Run Code Online (Sandbox Code Playgroud)

(不幸的是,通用数组的创建给出了未经检查的警告,必须将其取消。)

奇数构造

 CompletableFuture.supplyAsync(supplier, Runnable::run)
Run Code Online (Sandbox Code Playgroud)

在提供的Executor上“异步”运行供应商,该Executor Runnable::run只是在该线程中立即运行任务。换句话说,它同步运行供应商。

诀窍是,CompletableFuture从该调用返回的实例包含来自供应商的值(如果它正常返回),或者包含异常(如果供应商抛出一个异常)。(我在这里不考虑取消。)然后,我们将CompletableFuture实例收集到一个数组中。为什么要数组?下一部分的设置:

CompletableFuture.allOf(cfArray).join();
Run Code Online (Sandbox Code Playgroud)

这通常等待CF阵列完成。由于它们已经同步运行,因此它们应该已经全部完成。对于这种情况,重要的是,如果阵列中的任何 CF已异常完成,join()将抛出a 。如果连接正常完成,我们可以简单地收集返回值。如果联接引发异常,则可以传播它,也可以捕获它并处理存储在数组中CF中的异常。例如,CompletionException

try {
    CompletableFuture.allOf(cfArray).join();
    // no errors
    return Arrays.stream(cfArray)
                 .map(CompletableFuture::join)
                 .collect(toList());
} catch (CompletionException ce) {
    long errcount =
        Arrays.stream(cfArray)
              .filter(CompletableFuture::isCompletedExceptionally)
              .count();
    System.out.println("errcount = " + errcount);
    return Collections.emptyList();
}
Run Code Online (Sandbox Code Playgroud)

如果所有操作均成功,则返回值列表。如果有任何例外,它将计算例外数量并返回一个空列表。当然,您可以轻松地执行其他操作,例如记录异常的详细信息,过滤掉异常并返回有效值列表等。


Dav*_*vio 4

无一例外你都可以使用Optional:

stream.map(obj -> doMap(obj))
      .filter(obj -> obj.isPresent())
      .collect(Collectors.toList());

private Optional<String> doMap(Object obj) {
   if (objectIsInvalid) {
    return Optional.empty();
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 我不知道你为什么要“使用空值*和*可选值”。通常,您应该决定*任一*、使用“null”或使用“Optional”。您的代码在“doMap”中返回“null”,并尝试在“filter(…)”中调用“.isPresent()”… (3认同)
  • 请不要这样做。如果某些内容无效,您可能希望返回一个*空*“Optional”而不是“null”。按照目前的情况,当对“null”调用“isPresent”时,您将得到NPE。 (3认同)
  • 我想这取决于你在做什么,但默默地抑制异常通常是一个非常糟糕的主意。如果有人传递了无效数据,或者用户指定了无效输入,您应该告诉他们,而不是表现得好像一切都很好。 (2认同)