Java 8 引入的流 (Stream) API 和 lambda 表达式为咱们打开了新世界的大门,自此以后咱们也能够在 Java 中进行函数式编程了。然而,在实际工做中,许多小伙伴并不知道如何正确的在 lambda 中处理异常,今天就来给你们讲解一下。面试
咱们都知道,Java 异常分为检查异常和非检查异常。检查异常就是编译器要求开发者必须处理的异常,而非检查异常则没有这个要求。因此当咱们须要调用某个抛出检查异常的方法时,必须明确捕获它:spring
myList.stream() .map(item -> try{ return doSomething(item); } catch(MyException e){ throw new RuntimeException (e); } }) .forEach(System.out::printion);
如上面代码所示,咱们捕获了 MyException 这个检查异常,而后将其转化为 RuntimeException 非检查异常,从新抛出。可是你本身内心面其实清楚的很,这不是最好的处理方式。编程
以下所示,咱们将方法体单独提取到 trySomething 方法中,这样的话,咱们就可使用一行代码完成 lambda 表达式,整个代码可读性也会提高很多:后端
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:mybatis
借助于 wrap 静态函数,如今你能够在 lambda 表达式中这么写了并发
上述代码的可读性、抽象性已经很好了,然而还存在一个比较大的问题,那就是当出现异常的时候,你的 stream 代码会当即中止,不会接着处理下一个元素。大多数状况下,当抛出异常的时候,咱们可能还想让 stream 继续运行下去。app
咱们与其抛出异常,将异常当成一种特殊的状况处理,还不如直接将异常当成是一个 “正常” 的返回值。即这个函数要么返回一个正确的结果,要么返回一个异常,因此咱们如今须要定义一个新的封装类 Either,用来存储这两种结果。为了方便,咱们将异常存储到 left 这个字段中,将正常返回的值存储到 right 这个字段中。下面就是 Either 类的一个简单示例:分布式
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 类是一个很是简单的封装类,用以封装两个值:
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后端技术群:479499375!群内提供设计模式、spring/mybatis源码分析、高并发与分布式、微服务、性能优化,面试题整合文档等免费资料!给你们提供一个交流学习的平台!
咱们通过上文一点一点地优化代码,获得了一个比较满意的在 Java 8 中处理异常的通用方式。其实,你们还能够关注 Github 上的有关函数式编程方面的库,好比 Javaslang ,它实现了多种多样的函数式帮助方法和封装类来帮助开发者写好 lambda 表达式。可是,若是你只是为了处理异常,而引入这么大的一个第三方库的话,就不太建议了哦~