stream.map(obj -> doMap(obj)).collect(Collectors.toList());
private String doMap(Object obj) {
if (objectIsInvalid) {
throw new ParseException("Object could not be parsed");
}
}
问题:如何抛出异常并使流迭代知道它不应该破坏整个迭代,而是继续使用下一个元素(并最终记录失败的对象)?
答案 0 :(得分:4)
这是一个可以用来改善异常处理的奇怪技巧。
让我们说你的映射器函数是这样的:
String doMap(Object obj) {
if (isInvalid(obj)) {
throw new IllegalArgumentException("info about obj");
} else {
return obj.toString();
}
}
如果对象有效,则返回结果,但如果对象无效则抛出异常。不幸的是,如果您将此直接粘贴到管道中,任何错误都将停止管道执行。你想要的是像"或者"可以包含值或错误指示符的类型(在Java中这将是一个例外)。
事实证明CompletableFuture
可以包含值或异常。虽然它是用于异步处理的 - 这里没有发生 - 但我们只需要稍微扭曲它就可以用于我们的目的。
首先,给定要处理的stream
个对象,我们将调用映射函数包含在对supplyAsync
的调用中:
CompletableFuture<String>[] cfArray =
stream.map(obj -> CompletableFuture.supplyAsync(() -> doMap(obj), Runnable::run))
.toArray(n -> (CompletableFuture<String>[])new CompletableFuture<?>[n]);
(不幸的是,通用数组创建提供了一个未经检查的警告,必须将其抑制。)
奇怪的构造
CompletableFuture.supplyAsync(supplier, Runnable::run)
以异步方式运行供应商&#34;在提供的Executor Runnable::run
上,它只是在此线程中立即执行任务。换句话说,它同步运行供应商。
诀窍在于,此调用返回的CompletableFuture
实例包含供应商的值,如果它正常返回,或者包含异常,如果供应商扔了一个。 (我在这里无视取消。)然后我们将CompletableFuture
个实例收集到一个数组中。为什么一个阵列?它是下一部分的设置:
CompletableFuture.allOf(cfArray).join();
这通常会等待CF数组完成。由于它们已经同步运行,它们应该已经完成了。对于这种情况,重要的是join()
如果阵列中的任何任何异常完成,则会抛出CompletionException
。如果连接正常完成,我们可以简单地收集返回值。如果连接抛出异常,我们可以传播它,或者我们可以捕获它并处理存储在数组中的CF中的异常。例如,
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();
}
如果全部成功,则返回值列表。如果有任何异常,则计算异常数并返回空列表。当然,您可以轻松地执行其他操作,例如记录异常的详细信息,过滤掉异常并返回有效值列表等。
答案 1 :(得分:2)
如果没有例外,您可以使用Optionals:
stream.map(obj -> doMap(obj))
.filter(obj -> obj.isPresent())
.collect(Collectors.toList());
private Optional<String> doMap(Object obj) {
if (objectIsInvalid) {
return Optional.empty();
}
}
答案 2 :(得分:2)
如果你想要一种方法来静静地忽略那些在映射时抛出异常的元素,你可以定义一个帮助方法,它返回成功或不返回任何内容,作为0或1个元素的流,然后使用该方法对你的流进行flatMap方法
import static java.util.stream.Collectors.toList;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class Fish {
private int weight;
public Fish(int weight) { this.weight = weight; }
public String size() {
if (weight < 10) {
return "small";
} else if (weight < 50) {
return "medium";
} else if (weight < 100) {
return "large";
} else {
throw new RuntimeException("I don't believe you");
}
}
public static <T> Stream<T> successOrNothing(Supplier<T> supplier) {
try {
return Stream.of(supplier.get());
} catch (Exception e) {
return Stream.empty();
}
}
public static void main(String[] args) {
Stream<Fish> fishes = Stream.of(new Fish(1000), new Fish(2));
List<String> sizes = fishes
.flatMap(fish -> successOrNothing(fish::size))
.collect(toList());
System.out.println(sizes);
}
}
流中的每个元素都映射到1个元素(如果调用成功返回)或0(如果没有)。这可能更简单或更简单,然后更改方法返回null或Optional.empty()而不是抛出&#34;模式,特别是在处理已经定义合同并且您不想改变的方法时。
警告:这个解决方案仍然默默地忽略异常;这在许多情况下是不合适的。
答案 3 :(得分:0)
我会跟着......
stream.map(doMapLenient())
.filter(Objects::nonNull)
.collect(Collectors.toList());
private String doMap(Object obj) {
if (objectIsInvalid(obj)) {
throw new ParseException("Object could not be parsed");
}
return "Streams are cool";
}
private Function<Object, String> doMapLenient() {
return obj -> {
try {
return doMap(obj);
} catch (ParseExcepion ex) {
return null;
}
}
在这里你可以很好地添加一些日志记录在catch部分
答案 4 :(得分:0)
您可以使用迭代器显式迭代流元素:
Stream<Object> stream = ... // throws unchecked exceptions;
Iterator<Object> iterator = stream.iterator();
while (iterator.hasNext()) {
try {
Object obj = it.next();
// process
} catch (ParseException ex) {
// handle
}
}