欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java 8 Stream中的异常处理

程序员文章站 2022-06-24 17:43:39
...

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中进行异常处理的三种常见思路。可以根据业务逻辑的需要进行选择。

 

rel: https://zhuanlan.zhihu.com/p/30747343

相关标签: java