Java 8 Stream中的异常处理

Java 8中的Stream与Lambda表达式结合在一起,确实使得开发中的很多常见任务变得更简单。通过级联多个不同的操作符,如mapfilterreduce等,可以在一行代码里面完成很多的任务。那种一行代码搞定一切的愉悦感,着实让人很满意。今天我们来说说Stream中不那么愉悦的部分 – 异常处理

如果你看一眼Stream中包含的所有方法,你会发现,这些方法都没有声明抛出任何checked异常。再看一眼接口FunctionConsumerSupplier,其中包含的方法也是不抛出checked异常的。关于checked和unchecked异常的讨论由来已久。目前的趋势是尽量使用unchecked异常,但是checked异常也有它的用武之地。当然,这个不是我们今天要讨论的重点。这些方法都不抛出checked异常,也就意味着那些抛出checked异常的方法不能直接使用。这就涉及到了我们今天要讨论的如何处理异常的问题。

为了避免枯燥的讨论,我们来看一个具体的例子。我们有一个接口UserService,其中包含了方法load来根据用户的ID返回对应的User对象。现在我们有一个包含了多个用户ID的List<String对象,我们需要返回与这些ID对应的List<User>对象。在load方法的实现中,可能出现各种错误。所以load方法声明了会抛出UserLoadException异常。

public interface UserService {
 User load(String id) throws UserLoadException;
}

现在我们要实现的是加载多个用户的方法List<User> loadAllUsers(List<String> userIds)

最直接的实现方式userIds.stream().map(userService::load)是行不通的,编译错误,因为没有处理UserLoadException异常。在继续之前,我们首先要明确的是,处理异常的逻辑是什么。这个看似简单的问题,其实很多时候都被我们忽略了。而这个问题的答案来自于具体的业务逻辑。

如果该方法一共接收到了10个用户ID,其中的2个用户在加载时出现了异常,那我们需要做什么?

第一种做法,我们可以忽略出错的那2个用户,而只返回一个包含了8个User对象的List这种做法对应的业务逻辑就是尽最大努力的完成任务。

我们只需要把用户ID的Stream映射成Optional<User>Stream,再进行过滤即可。

public List<User> loadAllUsersBestEffort(final List<String> userIds) {
 return userIds.stream().<Optional<User>>map(id -> {
   try {
     return Optional.of(this.userService.load(id));
   } catch (UserLoadException e) {
     return Optional.empty();
   }
 }).filter(Optional::isPresent)
     .map(Optional::get)
     .collect(Collectors.toList());
}

第二种做法,整个加载过程直接失败,抛出异常,不返回任何结果。这种做法对应的业务逻辑就是把整个加载过程看成一个整体,不允许部分成功。

我们只需要把抛出的checked异常封装成unchecked异常,再重新抛出即可。由该方法的调用者来负责处理异常。

public List<User> loadAllUsersFailFast(final List<String> userIds) {
 return userIds.stream().map(id -> {
   try {
     return this.userService.load(id);
   } catch (UserLoadException e) {
     throw new RuntimeException(e);
   }
 }).collect(Collectors.toList());
}

第三种做法与第一种做法类似,只不过会把加载时出现的异常也记录下来,并返回给调用者。这样提供给调用者给多的信息和更多的选择。调用者可以选择使用这部分成功的结果,也可以选择直接抛出所产生的异常。

在增加了异常信息之后,方法的返回类型变成了Tuple2<List<User>, Optional<UserLoadException>>。基本的实现方式是把用户ID的Stream映射成Tuple2<List<User>, Optional<UserLoadException>>类型的Stream之后,再使用reduce操作合并成单一的对象。我们通过Throwable.addSuppressed()方法来把多个异常对象进行合并。

public Tuple2<List<User>, Optional<UserLoadException>> loadAllUsersWithException(
   final List<String> userIds) {
 return userIds.stream().map(id -> {
   try {
     return Tuple
         .of((List<User>) Lists.newArrayList(this.userService.load(id)),
             Optional.<UserLoadException>empty());
   } catch (UserLoadException e) {
     return Tuple.of((List<User>) Lists.<User>newArrayList(), Optional.of(e));
   }
 }).reduce(Tuple.of(new ArrayList<>(), Optional.empty()), (r1, r2) -> {
   r1._1.addAll(r2._1);
   if (r2._2.isPresent()) {
     if (r1._2.isPresent()) {
       r1._2.get().addSuppressed(r2._2.get());
     } else {
       return Tuple.of(r1._1, r2._2);
     }
   }
   return r1;
 });
}

上面给出了在Stream中进行异常处理的三种常见思路。可以根据业务逻辑的需要进行选择。

关注微信公众号【灵动代码】(vividcode)获取更多纯干货技术文章

    原文作者:成富
    原文地址: https://zhuanlan.zhihu.com/p/30747343
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞