将函数返回值中的对象添加到列表时,Java 8 Stream异常处理

时间:2017-07-21 22:31:03

标签: java exception-handling java-8 java-stream

我很难理解在使用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循环相同的实现?

2 个答案:

答案 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)

事件有几种处理流中异常的方法:

  1. 在地图中捕获异常
files.stream()
   .parallel()
   .map(file-> {
       try {
           return file.getInputStream();
       } catch (IOException e) {
           e.printStackTrace();
           return null;
       }
   })
   .forEach(inputStream -> carService.saveCars(inputStream));

  1. 提取函数参数以映射到自己的方法中:
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;
    }

  1. 创建另一个类似于Function的功能接口,该接口的apply方法声明了抛出异常:
@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));

  1. 但是,如果您想高效地使用所有Functional接口,建议您使用此库
<dependency>
    <groupId>com.pivovarit</groupId>
    <artifactId>throwing-function</artifactId>
    <version>1.5.0</version>
</dependency>

在这篇帖子Java 8, How to handle exceptions in a stream?

中用示例进行了全部解释