lambda处理异常四种方式

最近对接第三方呼叫系统,第三方SDK的所有方法里都有异常抛出,因为用到了lambda,所以异常处理还是很必要的。

本文主要用到了四种解决方案:

  1. 直接代码块处理
  2. 自定义函数式接口,warp静态方法
  3. 通过Either 类型包装
  4. 通过Pair 类型进行再次包装

方法一:

直接代码块处理:

    /*** 上线* @param schoolId  学校id* @param cno  座席工号,4-6 位数字* @param bindType  电话类型,1:电话;2:分机* @param bindTel 绑定电话* @return*/@Overridepublic Optional<OnlineResponse> online(Integer schoolId, String cno, Integer bindType, String bindTel) {Optional<ClientConfiguration> clientConfig = getClientConfig(schoolId);OnlineResponse response = clientConfig.map(x -> {try {return new Client(x).getResponseModel(new OnlineRequest() {{setCno(cno);setBindType(bindType);setBindTel(bindTel);}});} catch (ClientException e) {log.error("调用天润-上线接口,客户端异常",e);} catch (ServerException e) {log.error("调用天润-上线接口,服务端异常",e);}return null;}).orElse(null);return Optional.ofNullable(response);}

我们大多数人都知道,lambda 代码块是笨重的,可读性较差。而且一点也不优雅,丢失了lambda的简洁性。

如果我们在 lambda 表达式中需要做多行代码,那么我们可以把这些代码提取到一个单独的方法中,并简单地调用新方法。

所以,解决此问题的更好和更易读的方法是将调用包装在一个普通的方法中,该方法执行 try-catch 并从 lambda 中调用该方法,如下面的代码所示:

 
myList.stream().map(this::trySomething).forEach(System.out::println);private T trySomething(T t) {try {return doSomething(t);} catch (MyException e) {throw new RuntimeException(e);}

这个解决方案至少有点可读性,但是如果lambda 表达式中发生了异常,catch里的异常是抛不出来的,因为java8里原生的Function是没有throw异常的,如图:

img

方法二:

为了解决方法一的缺陷,我们要自定义一个函数式接口Function,并抛出异常:

/*** 异常处理函数式接口*/
@FunctionalInterface
public interface CheckedFunction<T,R> {R apply(T t) throws Exception;}

现在,可以编写自己的通用方法了,它将接收这个 CheckedFunction 参数。你可以在这个通用方法中处理 try-catch 并将原始异常包装到 RuntimeException中,如下列代码所示:

    /*** lamber 抛出异常* 发生异常时,流的处理会立即停止* @param function* @param <T>* @param <R>* @return*/public static <T,R> Function<T,R> warp(CheckedFunction<T,R> function){return t -> {try {return function.apply(t);} catch (Exception e) {throw new RuntimeException(e);}};}

实际中应用(warp静态方法放在了Either类里):

    /*** 上线* @param schoolId  学校id* @param cno  座席工号,4-6 位数字* @param bindType  电话类型,1:电话;2:分机* @param bindTel 绑定电话* @return*/public Optional<OnlineResponse> online(Integer schoolId, String cno, Integer bindType, String bindTel) {Optional<ClientConfiguration> clientConfig = getClientConfig(schoolId);OnlineRequest request = new OnlineRequest() {{setCno(cno);setBindType(bindType);setBindTel(bindTel);}};Optional<OnlineResponse> onlineResponse = clientConfig.map(Either.warp(x -> getResponseModel(x, request)));return onlineResponse;}

剩下的唯一问题是,当发生异常时,你的 lambda处理会立即停止,如果是stream 处理,我相信大多数人都不希望报异常后流被停止。如果你的业务可以容忍这种情况的话,那没问题,但是,我可以想象,在许多情况下,直接终止并不是最好的处理方式。

方法三

我们可以把 “异常情况” 下产生的结果,想象成一种特殊性的成功的结果。那我们可以把他们都看成是一种数据,不管成功还是失败,都继续处理流,然后决定如何处理它。我们可以这样做,这就是我们需要引入的一种新类型 - Either类型。

Either 类型是函数式语言中的常见类型,而不是 Java 的一部分。与 Java 中的 Optional 类型类似,一个 Either 是具有两种可能性的通用包装器。它既可以是左派也可以是右派,但绝不是两者兼而有之。左右两种都可以是任何类型。

如果我们将此原则用于异常处理,我们可以说我们的 Either 类型包含一个 Exception 或一个成功的值。为了方便处理,通常左边是 Exception,右边是成功值。

下面,你将看到一个 Either 类型的基本实现 。在这个例子中,我使用了 Optional 类型,代码如下:

import lombok.ToString;
import org.springframework.data.util.Pair;import java.util.Optional;
import java.util.function.Function;@ToString
public class Either<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 != 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 Optional.empty();}/*** lamber 抛出异常* 发生异常时,流的处理会立即停止* @param function* @param <T>* @param <R>* @return*/public static <T,R> Function<T,R> warp(CheckedFunction<T,R> function){return t -> {try {return function.apply(t);} catch (Exception e) {throw new RuntimeException(e);}};}/*** lamber 抛出异常* 发生异常时,流的处理会继续* 不保存原始值* @param function* @param <T>* @param <R>* @return*/public static <T, R> Function<T, Either> lift(CheckedFunction<T,R> function){return t -> {try {return Either.Right(function.apply(t));} catch (Exception e) {return Either.Left(e);}};}/*** lamber 抛出异常* 发生异常时,流的处理会继续* 异常和原始值都保存在左侧* @param function* @param <T>* @param <R>* @return*/public static <T,R> Function<T, Either> liftWithValue(CheckedFunction<T,R> function) {return t -> {try {return Either.Right(function.apply(t));} catch (Exception ex) {return Either.Left(Pair.of(ex,t));}};}
}

你现在可以让你自己的函数返回 Either 而不是抛出一个 Exception。但是如果你想在现有的抛出异常的 lambda 代码中直接使用 Either 的话,你还需要对原有的代码做一些调整(同warp方法一样,我都放在了Either 类里了),如下所示:

    /*** lamber 抛出异常* 发生异常时,流的处理会继续* 不保存原始值* @param function* @param <T>* @param <R>* @return*/public static <T, R> Function<T, Either> lift(CheckedFunction<T,R> function){return t -> {try {return Either.Right(function.apply(t));} catch (Exception e) {return Either.Left(e);}};}

这里我们把异常信息保存到Left里,其实也可以直接把left的泛型L改为Exception类型,但丢失了灵活性(就是下面提到的一点Try类型)。

通过添加这种静态提升方法 Either,我们现在可以简单地“提升”抛出已检查异常的函数,并让它返回一个 Either。这样做的话,我们现在最终得到一个 Eithers 流而不是一个可能会终止我们的 Stream 的 RuntimeException,具体的代码如下:

    /*** 上线* @param schoolId  学校id* @param cno  座席工号,4-6 位数字* @param bindType  电话类型,1:电话;2:分机* @param bindTel 绑定电话* @return*/public Optional<OnlineResponse> online(Integer schoolId, String cno, Integer bindType, String bindTel) {Optional<ClientConfiguration> clientConfig = getClientConfig(schoolId);OnlineRequest request = new OnlineRequest() {{setCno(cno);setBindType(bindType);setBindTel(bindTel);}};Optional<Either> either = clientConfig.map(Either.lift(x -> getResponseModel(x, request)));return null;}

因为返回的是Optional类型,所以我们还要做一下解析:

    /*** 处理包装的返回结果* @param either* @param <T>* @return*/public T disposeResponse(Optional<Either> either) throws Exception {if (either.isPresent()){Either entity = either.get();if (entity.isLeft()){Optional<Exception> optional = entity.mapLeft(x -> x);log.error("调用天润接口异常:"+optional.get().getMessage(),optional.get());throw new Exception(optional.get().getMessage());}else {Optional<T> optional = entity.mapRight(x -> x);log.info("调用天润接口返回信息:"+ JSON.toJSONString(optional.get()));return optional.get();}}return null;}

实际应用代码:

    /*** 上线* @param schoolId  学校id* @param cno  座席工号,4-6 位数字* @param bindType  电话类型,1:电话;2:分机* @param bindTel 绑定电话* @return*/public Optional<OnlineResponse> online(Integer schoolId, String cno, Integer bindType, String bindTel) throws Exception {Optional<ClientConfiguration> clientConfig = getClientConfig(schoolId);OnlineRequest request = new OnlineRequest() {{setCno(cno);setBindType(bindType);setBindTel(bindTel);}};return Optional.ofNullable(disposeResponse(clientConfig.map(Either.lift(x -> getResponseModel(x, request)))));}

这样的话,就解决了lambda中有异常停止的问题,上面的disposeResponse里我抛出了异常,因为我需要知道第三方的异常信息,如果你的业务不需要,可以不往外抛,直接把异常消化掉也可以。

方法四

其实也就是方法三的扩展,比如说我还想知道请求参数是什么,请求参数我也想用到,方法三是获取不了请求参数的。

我们现在可能遇到的问题是,如果 Either 只保存了包装的异常,并且我们无法重试,因为我们丢失了原始值。

因为 Either 类型是一个通用的包装器,所以它可以用于任何类型,而不仅仅用于异常处理。这使我们有机会做更多的事情而不仅仅是将一个 Exception 包装到一个 Either 的左侧实例中。

通过使用 Either 保存任何东西的能力,我们可以将异常和原始值都保存在左侧。为此,我们只需制作第二个静态提升功能,spring的org.springframework.data.util.Pair类。

    /*** lamber 抛出异常* 发生异常时,流的处理会继续* 异常和原始值都保存在左侧* @param function* @param <T>* @param <R>* @return*/public static <T,R> Function<T, Either> liftWithValue(CheckedFunction<T,R> function) {return t -> {try {return Either.Right(function.apply(t));} catch (Exception ex) {return Either.Left(Pair.of(ex,t));}};}

你可以看到,在这个 liftWithValue 函数中,这个 Pair 类型用于将异常和原始值配对到 Either 的左侧,如果出现问题我们可能需要所有信息,而不是只有 Exception。

解析方法

    /*** 处理包装的返回结果* @param either* @param <T>* @return*/public <T extends ResponseModel> T disposeResponsePair(Optional<Either> either) throws Exception {if (either.isPresent()){Either entity = either.get();if (entity.isLeft()){Optional<Pair> optional = entity.mapLeft(x -> x);Object second = optional.get().getSecond();log.info("请求参数:{}",JSON.toJSONString(second));Exception first = (Exception)optional.get().getFirst();log.error("调用天润接口异常:"+first.getMessage(),first);throw new Exception(first.getMessage());}else {Optional<Pair> optional = entity.mapRight(x -> x);log.info("调用天润接口返回信息:"+ JSON.toJSONString(optional.get().getSecond()));return (T) optional.get().getSecond();}}return null;}

实际应用:

    /*** 上线* @param schoolId  学校id* @param cno  座席工号,4-6 位数字* @param bindType  电话类型,1:电话;2:分机* @param bindTel 绑定电话* @return*/public Optional<OnlineResponse> online(Integer schoolId, String cno, Integer bindType, String bindTel) throws Exception {Optional<ClientConfiguration> clientConfig = getClientConfig(schoolId);OnlineRequest request = new OnlineRequest() {{setCno(cno);setBindType(bindType);setBindTel(bindTel);}};return Optional.ofNullable(disposeResponsePair(clientConfig.map(Either.liftWithValue(x -> getResponseModel(x, request)))));}

img

如果 Either 是一个 Right 类型,我们知道我们的方法已正确执行,我们可以正常的提取结果。另一方面,如果 Either 是一个 Left 类型,那意味着有地方出了问题,我们可以提取 Exception 和原始值,然后我们可以按照具体的业务来继续处理。

扩展

包装成 Try 类型

使用过 Scala 的人可能会使用 Try 而不是 Either 来处理异常。Try 类型与 Either 类型是非常相似的。

它也有两种情况:“成功”或“失败”。失败时只能保存 Exception 类型,而成功时可以保存任何你想要的类型。

所以 Try 可以说是 Either 的一种固定的实现,因为他的 Left 类型被确定为 Exception了,如下列的代码所示:

public class Try<Exception, R> {private final Exception failure;private final R succes;public Try(Exception failure, R succes) {this.failure = failure;this.succes = succes;}}

有人可能会觉得 Try 类型更加容易使用,但是因为 Try 只能将 Exception 保存在 Left 中,所以无法将原始数据保存起来,这就和最开始 Either 不使用 Pair 时遇到的问题一样了。所以我个人更喜欢 Either 这种更加灵活的。

无论如何,不管你使用 Try 还是 Either,这两种情况,你都解决了异常处理的初始问题,并且不要让你的流因为 RuntimeException而终止。

使用已有的工具库

无论是 Either 和 Try 是很容易实现自己。另一方面,您还可以查看可用的功能库。例如:VAVR(以前称为Javaslang)确实具有可用的类型和辅助函数的实现。我建议你去看看它,因为它比这两种类型还要多得多。

但是,你可以问自己这样一个问题:当你只需几行代码就可以自己实现它时,是否希望将这个大型库作为依赖项进行异常处理。

结论

当你想在 lambda 表达式中调用一个会抛出异常的方法时,你需要做一些额外的处理才行。

  • 将其包装成一个 RuntimeException 并且创建一个简单的包装工具来复用它,这样你就不需要每次都写try/catch 了
  • 如果你想要有更多的控制权,那你可以使用 Either 或者 Try 类型来包装方法执行的结果,这样你就可以将结果当成一段数据来处理了,并且当抛出 RuntimeException 时,你的流也不会终止。
  • 如果你不想自己封装 Either 或者 Try 类型,那么你可以选择已有的工具库来使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/555172.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux 历史命令快捷键,Linux历史命令及bash快捷键

本文环境 Centos71.历史命令1.1 在使用linux中熟练的使用历史命令&#xff0c;能给我很多便捷&#xff0c;下面我来总结一些常用的一些历史命令。a) 执行上一条命令,这个很重要 &#xff0c;执行上一条命令有四种方法&#xff0c;如下:[rootCentOS-7-64data]#[rootCentOS-7-64 …

linux的shell命令 a,linux shell命令大全(都是随堂笔记)

1. Tftp服务器(上传下载文件)注意&#xff1a;上传和下载文件时不能用目录表示 。因此&#xff0c;需要先cd到当前目录。注意目录权限&#xff1a;chmod 0777目录文件 8进制表示&#xff1a;Chmod的文件、目录的权限U自己 g 所属组 o其它 可加减步骤&#xff1a;安装服务器&…

异常 —— throws

1.1 异常概念 1.2 异常体系 java.lang.Throwable:类是 Java 语言中所有错误或异常的超类。 Exception:编译期异常,进行编译(写代码)java程序出现的问题 RuntimeException:运行期异常,java程序运行过程中出现的问题 异常就相当于程序得了一个小毛病(感冒,发烧),把异常处理掉,程序…

制作 小 linux 教程,【NanoPi NEO Plus2开发板试用体验】编译uboot和linux制作最小根文件系统制作刷机包---详细教程...

二、Linux命令基础1、查找文件查找a.c的文件 find -name "a.c"在当前目录搜索a字样的文件 grep "a" *-nR2、解压解压tar.gz文件 tar zxvf xxxxx.tar.gz解压tar.xz文件 先 xz -d xxx.tar.xz 将 xxx.tar.xz解压成 xxx.tar 然后&#xff0c;再用 tar xvf xx…

执行throw后 后面代码还会执行吗?

1.当我们对throw的对象进行try catch之后 public void re(int i) {if (i > 5){this.i i;}else {try {throw new Exception("数据非法&#xff01;");} catch (Exception e) {e.printStackTrace();}System.out.println("123");}结果显示&#xff1a;12…

linux怎么查看sklearn版本,Sklearn——Sklearn的介绍与安装

文章目录1.Sklearn简介2.Sklean安装2.1.pip安装2.2.conda安装1.Sklearn简介Scikit learn 也简称 sklearn, 是机器学习领域当中最知名的 python 模块之一.Sklearn 包含了很多种机器学习的方式:Classification 分类Regression 回归Clustering 非监督分类Dimensionality reduction…

一些防止 Java 代码被反编译的方法

由于Java字节码的抽象级别较高&#xff0c;因此它们较容易被反编译。本节介绍了几种常用的方法&#xff0c;用于保护Java字节码不被反编译。通常&#xff0c;这些方法不能够绝对防止程序被反编译&#xff0c;而是加大反编译的难度而已&#xff0c;因为这些方法都有自己的使用环…

linux 离线安装中文,linux离线安装及配置redis-Go语言中文社区

本文以centos7系统为例&#xff0c;介绍离线安装redis步骤一、环境准备检查服务器上是否存在gcc-c的环境&#xff0c;使用命令&#xff1a;rpm -qa | grep gcc-c如果没有该环境&#xff0c;则需要安装该环境&#xff0c;离线安装步骤为&#xff1a;1、获取相关rpm包&#xff0c…

Java学习记录 AWT绘图篇

绘制图形 Canvas画布类 Class Canvas java.lang.Object java.awt.Component java.awt.Canvas 用来 绘制图形 或 捕获用户输入的事件。绘制图形需要绘图方法实现绘制图形 方法说明paint(Graphics g)绘图repaint(Graphics g)重新绘图&#xff08;刷新 Graphics绘图类 Class Gra…

linux pmap 内存泄露,一个驱动导致的内存泄漏问题的分析过程(meminfo-pmap-slabtop-alloc_calls)...

关键词&#xff1a;sqllite、meminfo、slabinfo、alloc_calls、nand、SUnreclaim等等。下面记录一个由于驱动导致的内存泄漏问题分析过程。首先介绍问题背景&#xff0c;在一款嵌入式设备上&#xff0c;新使用sqllite库进行数据库操作&#xff0c;在操作数据(大量读写操作)一段…

Java基础--awt详解以及简单应用

GUI 图形用户界面 CLI 命令行用户接口 Java为GUI提供的对象存在java.Awt和Javax.Swing两个包中. Java当中如何完成图形化界面的制作呢? AWT:abstract Window ToolKit.需要调用本地系统实现功能.属于重量级控件.依赖于平台.跨平台性不是特别好. Javax.Swing:在AWT基础上.建立一…

Java图形化界面设计之容器(JFrame)详解

Java图形化界面设计之容器&#xff08;JFrame&#xff09;详解 Java图形化界面设计——容器&#xff08;JFrame&#xff09; 程序是为了方便用户使用的&#xff0c;因此实现图形化界面的程序编写是所有编程语言发展的必然趋势&#xff0c;在命令提示符下运行的程序可以让我们…

10个最受欢迎的JavaScript图表库

目前网上有很多用于绘制图表图形的免费JavaScript插件和图表库&#xff0c;技术学派在这里给大家推荐10个比较强大的绘制图表图形的JavaScript图表库。其中一些插件需要主流浏览器的支持&#xff0c;而另外一些经过整合后&#xff0c;也能在不同的平台和老版本的浏览器上工作。…

sudo 命令_su、sudo、sudo su、sudo -i的用法和区别

sudo 命令 1、sudo 简介 sudo是linux系统管理指令&#xff0c;是允许系统管理员让普通用户执行一些或者全部的root命令的一个工具&#xff0c;如halt&#xff0c;reboot&#xff0c;su等等。这样不仅减少了root用户的登录 和管理时间&#xff0c;同样也提高了安全性。sudo不是…

c语言不能在函数中求数组大小,C语言中数组长度不能用变量定义吗?

翻翻过去那场雪1、C语言中不支持。C中支持变长数组(你可以自行度娘变长数组和alloca函数)&#xff0c;但是因为其实在栈上分配&#xff0c;不被推荐使用。做为解决方案&#xff0c;你可以使用C式的malloc函数或者C式的new函数来在堆上动态分配内存&#xff0c;这样长度是完全可…

Java如何基于ProcessBuilder类调用外部程序

Java如何基于ProcessBuilder类调用外部程序 demo1 Testpublic void testProcessBuilder() {ProcessBuilder processBuilder new ProcessBuilder(); // processBuilder.command("ping","127.0.0.1");processBuilder.command("ipconfig");//…

计算机专业课程设计报告c语言,计算机程序设计(C语言)课程设计报告.doc

计算机程序设计(C语言)课程设计报告.doc计算机程序设计C语言课程设计报告题目电子动画时钟 学院 机电工程学院专业 班级090109班学号 姓名 指导教师 设计日期 一、概述选题背景 随着社会的进步和科技的发展&#xff0c;电子钟表逐渐成为了人们生活中不可缺少的一部分。设计思路…

c语言用栈编写数制转换程序,数制转换-栈的应用(C++实现)

本程序实现的是十进制与不同进制之间的的数据转换&#xff0c;利用的数据结构是栈&#xff0c;基本数学方法辗转相除法。conversion.h#includeusing namespace std;//将十进制的数据n转换成m进制的数据stack conversion(unsigned int n,unsigned int m){stack s;while(n){s.pus…

两个变量实现查找坏环c语言,C/C++编程笔记:C语言编程知识要点总结!大一C语言知识点(全)...

程序员无言 2020-07-07一、C语言程序的构成与C、Java相比&#xff0c;C语言其实很简单&#xff0c;但却非常重要。因为它是C、Java的基础。不把C语言基础打扎实&#xff0c;很难成为程序员高手。1、C语言的结构先通过一个简单的例子&#xff0c;把C语言的基础打牢。C语言的结构…

累加器A用c语言,累加器A的主要作用是什么_一文解析累加器a和acc的区别

描述累加器简介在中央处理器中&#xff0c;累加器(accumulator) 是一种寄存器&#xff0c;用来储存计算产生的中间结果。如果没有像累加器这样的寄存器&#xff0c;那么在每次计算 (加法&#xff0c;乘法&#xff0c;移位等等) 后就必须要把结果写回到内存&#xff0c;也许马上…