Java 8中的Stream
与Lambda表达式结合在一起,确实使得开发中的很多常见任务变得更简单。通过级联多个不同的操作符,如map
、filter
和reduce
等,可以在一行代码里面完成很多的任务。那种一行代码搞定一切的愉悦感,着实让人很满意。今天我们来说说Stream
中不那么愉悦的部分 – 异常处理。
如果你看一眼Stream
中包含的所有方法,你会发现,这些方法都没有声明抛出任何checked异常。再看一眼接口Function
、Consumer
和Supplier
,其中包含的方法也是不抛出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)获取更多纯干货技术文章