优雅地在Java 8中处理异常的方法详解

时间:2021-10-04 00:51:55

前言

java 8 引入的流 (stream) api 和 lambda 表达式为我们打开了新世界的大门,自此之后我们也可以在 java 中进行函数式编程了。然而,在实际工作中,许多小伙伴并不知道如何正确的在 lambda 中处理异常,今天就来给大家讲解一下。

优雅地在Java 8中处理异常的方法详解

小编给大家推荐一个java技术交流群:937053620!群内提供设计模式、spring/mybatis源码分析、高并发与分布式、微服务、性能优化,面试题整合文档等免费资料!给大家提供一个交流学习的平台!

我们都知道,java 异常分为检查异常和非检查异常。检查异常就是编译器要求开发者必须处理的异常,而非检查异常则没有这个要求。所以当我们需要调用某个抛出检查异常的方法时,必须明确捕获它:

?
1
2
3
4
5
6
7
8
9
mylist.stream()
 .map(item ->
  try{
  return dosomething(item);
  } catch(myexception e){
  throw new runtimeexception (e);
  }
 })
 .foreach(system.out::printion);

如上面代码所示,我们捕获了 myexception 这个检查异常,然后将其转化为 runtimeexception 非检查异常,重新抛出。但是你自己心里面其实清楚的很,这不是最好的处理方式。

优化一: 提升可读性

如下所示,我们将方法体单独提取到 trysomething 方法中,这样的话,我们就可以使用一行代码完成 lambda 表达式,整个代码可读性也会提升不少:

?
1
2
3
4
5
6
7
8
9
10
11
mylist.stream()
 .map(this::trysomething)
 .foreach(system.out::printion);
 
private item trysomething(item item) {
 try{
  return dosomething(item);
 } catch(myexception e){
  throw new runtimeexception (e);
 }
}

优化二: 复用代码

现在你已经解决了上述的问题,然而当我们再碰到需要处理异常的其它方法时,难道我们都要用 try ... catch ... 包裹一层吗?那样的话,你可以想象代码中可能到处都是这种类似的写法。为了避免陷入到这种重复的写法中,我们应该将上述代码片段抽象为一个小的工具类,专门用来干这件事情。你只需要定义一次,然后再需要的地方多次调用它就可以了。

为了实现这个目标,我们首先需要自己定义一个函数式接口,这个接口可能会抛出一个异常:

然后,我们来写一个静态帮助函数 wrap ,该方法接受一个函数式接口参数,在方法体内捕获检查异常,并抛出非检查异常 runtimeexception:

借助于 wrap 静态函数,现在你可以在 lambda 表达式中这么写了

优化三: 出现异常时继续运行

上述代码的可读性、抽象性已经很好了,然而还存在一个比较大的问题,那就是当出现异常的时候,你的 stream 代码会立即停止,不会接着处理下一个元素。大多数情况下,当抛出异常的时候,我们可能还想让 stream 继续运行下去。

我们与其抛出异常,将异常当成一种特殊的情况处理,还不如直接将异常当成是一个 “正常” 的返回值。即这个函数要么返回一个正确的结果,要么返回一个异常,所以我们现在需要定义一个新的封装类 either,用来存储这两种结果。为了方便,我们将异常存储到 left 这个字段中,将正常返回的值存储到 right 这个字段中。下面就是 either 类的一个简单示例:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class eithercl<l,r>{
 
  private final l left:
  private final r right;
 
  private either(l left, r right){
   this left=left;
   this right =right;
}
public static <l, r> either,<l,r> left( l value) {
 return new either(value, null):
}
public static <l, r> either<l, r> right( r value) {
 return new either(null, value)
}
public optional<l> getleft() {
 return optional. ofnullable(left)
}
public optional<r> getright() {
 return optional.ofnullable(right);
}
public boolean isleft() {
 return left i- null
}
public boolean isright(){
 return right != null
}
public < t> optional<t> mapleft(function<? super l, t> mapper){
 if (isleft()) {
  return optional of(mapper. apply(left));
  }
  return optional empty();
}
public <t> optional<t> mapright(function<? super r, t> mapper) {
 if (isright()) {
   return optional of(mapper. apply(right));
 }
 return optionalempty();
}
public string tostring(){
 if (isleft()){
  return"left(”+left+")"
 }
 return "right("+ right +")"
 }
}

现在我们需要再定义一个 lift 函数,该函数内部将 function 函数正常返回的值或者抛出的异常都使用 either 类进行了一层封装

现在我们的代码变成这个样子了,也不用担心方法抛出异常会提前终止 stream 了

优化四: 保留原始值

现在思考一个问题,如果在上述处理过程中,当结果是异常信息的时候,我们想要重试,即重新调用这个方法怎么办? 你会发现我们 either 封装类没有保存最原始的这个值,我们丢掉了原始值,因此我们可以进一步优化,将原始值 t 也封装进 left 字段中,就像下面这样:

pair 类是一个非常简单的封装类,用以封装两个值:

?
1
2
3
4
5
6
7
8
9
10
11
12
public class pair<f, s> {
 public final f fst;
 public final s snd;
 
 private pair(f fst, s snd){
   this fst fst;
   this snd= snd;
 }
public static <f, s> pair<f, s> of(f fst, s snd){
 return new pair<>(fst, snd);
 }
}

这样,当我们遇见异常的时候,我们可以从 pair 中取出最原始的值 t,无论是想重试,还是做一些其他操作,都很方便了。

总结

我们经过上文一点一点地优化代码,得到了一个比较满意的在 java 8 中处理异常的通用方式。其实,大家还可以关注 github 上的有关函数式编程方面的库,比如 javaslang ,它实现了多种多样的函数式帮助方法和封装类来帮助开发者写好 lambda 表达式。但是,如果你只是为了处理异常,而引入这么大的一个第三方库的话,就不太建议了哦~

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对服务器之家的支持。

原文链接:https://studygolang.com/articles/17555