我很难理解在使用Java 8流时如何处理异常。我想将对象添加到空列表中,并且每个对象都从一个函数返回,该函数可能会根据输入的内容抛出异常。如果仅对输入列表中的某些值抛出异常,我希望它继续循环输入列表的其余部分。使用for循环似乎很容易:
List<Item> itemList = new ArrayList<>();
List<Input> inputs = getInputs(); //returns a list of inputs
int exceptionCount = 0;
// If an exception is thrown
for (Input input : inputs){
try {
itemList.add(getItem(input));
} catch (Exception e ) {
// handle exception from getItem(input)
exceptionCount = exceptionCount + 1;
}
}
似乎可以用Java流实现这一点,但我不太确定如何处理getItem()
函数可能引发的异常。
这是我到目前为止所做的:
final List<Item> itemList = new ArrayList<>();
try {
itemList = getInputs().stream()
.map(this::getItem)
.collect(Collectors.toList());
} catch (Exception e ) {
// handle exception from getItem(input)
exceptionCount = exceptionCount + 1;
}
上面显然不起作用,因为只要从getItem
抛出一个异常,循环就不会继续,并且只会为整个流抛出一个异常。有没有什么方法可以实现与Java 8流的基本for循环相同的实现?
答案 0 :(得分:3)
您应该在map
操作中捕获异常:
class ExceptionCounter {
private int count = 0;
void inc() { count++; }
int getCount() { return count; }
}
ExceptionCounter counter = new ExceptionCounter();
List<Item> items = getInputs().stream()
.map(input -> {
try {
return getItem(input);
} catch (Exception e) {
// handle exception here
counter.inc();
}
})
.collect(Collectors.toList());
虽然这对顺序流的预期效果如此,但它不适用于并行流。即使流不是并行的,我们仍然需要ExceptionCounter
holder类,因为从流操作的参数(例如map
)中引用的变量必须有效最终。 (您可以使用一个元素的数组或AtomicInteger
而不是持有者类。)
如果我们将synchronized
添加到inc
类的ExceptionCounter
方法,那么上面的解决方案将支持并行流。但是,inc
方法的锁会有很多线程争用,从而失去了并行化的优势。这(以及试图不创建容易出错的代码)是为什么不鼓励副作用的原因。计算例外数量实际上是副作用。
对于这种特殊情况,如果您使用自定义收集器,则可以避免副作用:
class Result<R> {
List<R> values = new ArrayList<>();
int exceptionCount = 0;
// TODO getters
}
static <T, R> Result<R> mappingToListCountingExceptions(Function<T, R> mapper) {
class Acc {
Result<R> result = new Result<>();
void add(T t) {
try {
R res = mapper.apply(t);
result.value.add(res);
} catch (Exception e) {
result.exceptionCount++;
}
}
Acc merge(Acc another) {
result.values.addAll(another.values);
result.exceptionCount += another.exceptionCount;
}
}
return Collector.of(Acc::new, Acc::add, Acc::merge, acc -> acc.result);
}
您可以按如下方式使用此自定义收集器:
Result<Item> items = getInputs().stream()
.collect(mappingToListCountingExceptions(this::getItem));
现在由您来决定这种方法是否优于传统的for
循环。
答案 1 :(得分:1)
事件有几种处理流中异常的方法:
files.stream()
.parallel()
.map(file-> {
try {
return file.getInputStream();
} catch (IOException e) {
e.printStackTrace();
return null;
}
})
.forEach(inputStream -> carService.saveCars(inputStream));
files.stream()
.parallel()
.map(file-> extractInputStream(file))
.forEach(inputStream -> carService.saveCars(inputStream));
和
private InputStream extractInputStream(MultipartFile file) {
try {
return file.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@FunctionalInterface
interface FunctionWithException<T, R, E extends Exception> {
R apply(T t) throws E;
}
和
private <T, R, E extends Exception>
Function<T, R> wrapper(FunctionWithException<T, R, E> fun) {
return arg -> {
try {
return fun.apply(arg);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
然后像这样使用它
files.stream()
.parallel()
.map(wrapper(file->file.getInputStream()))
.forEach(inputStream -> carService.saveCars(inputStream));
<dependency>
<groupId>com.pivovarit</groupId>
<artifactId>throwing-function</artifactId>
<version>1.5.0</version>
</dependency>
中用示例进行了全部解释