lambda处理异常四种方式

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

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

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

方法一:

直接代码块处理:

    /**
     * 上线
     * @param schoolId  学校id
     * @param cno  座席工号,4-6 位数字
     * @param bindType  电话类型,1:电话;2:分机
     * @param bindTel 绑定电话
     * @return
     */
    @Override
    public 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 类型,那么你可以选择已有的工具库来使用

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Android】Exam5 ListView组件简单应用

Exam5 ListView组件简单应用 ListView组件简单应用 Exam5 ListView组件简单应用目的实验内容及实验步骤采用SimpleAdapter自定义Adapter运行及结果&#xff1a;实验总结 目的 掌握常用的UI布局及组件&#xff1b; 掌握使用Intent启动Activity的方法 掌握ListView组件的简单应用…

[离散数学] 函数

文章目录 函数判断函数的条件复合函数复合函数的性质 逆函数 函数 判断函数的条件 dom F A ⇔ \Leftrightarrow ⇔所有x 都有 F&#xff08;x&#xff09;与之对应 有唯一的与其对应 < x , y > ∈ f ∧ < y , z > ∈ f ⇒ y z <x,y>\in f \land <y,z…

无需繁琐手工操作,如何利用Web自动化测试元素定位做到快速高效的测试?

1、什么是Web自动化测试元素定位&#xff1f; 在Web自动化测试中&#xff0c;元素定位是非常重要的环节。因为我们需要找到需要进行操作的页面元素&#xff0c;例如按钮、输入框、下拉菜单等等。元素定位可以帮助我们在自动化测试中对这些元素进行操作&#xff0c;如点击、输入…

生物识别技术能否成为应对安全挑战的绝佳选择?

生物识别技术能否成为应对安全挑战的绝佳选择&#xff1f; 生物识别技术是利用人体固有的生理特征或行为特征来进行身份鉴别的一种技术&#xff0c;如指纹、人脸、声纹、虹膜等。1 生物识别技术具有不可撤销性、高度便利性和较低错误率等优势&#xff0c;在安全领域中也备受瞩目…

React动态路由配置

目录 项目初始化 模块创建 统一导出 全局模块配置选项 核心代码 使用及效果展示 博文适用于react-router v6及以上&#xff0c;其中还有很多值得改进的地方 最近学习react的过程中&#xff0c;思考怎样实现动态路由的配置(最终实现从页面配置最终动态从数据库加载上线模…

Stable Diffusion webui安装使用

参考&#xff1a; https://stability.ai/blog/stable-diffusion-public-release https://github.com/AUTOMATIC1111/stable-diffusion-webui 1、安装&#xff08;6g显存&#xff09; 1、conda创建python 3.10.6环境 conda create -n stable-diffusion pythonpython 3.10.6 也安…

中国南方Oracle用户组沙龙活动:大环境下的Oracle数据库的机遇与挑战

2023年03月12日(周日)在杭州索菲特西湖大酒店 (浙江省杭州市上城区西湖大道333 号)&#xff0c;中国南方Oracle用户组创始人之一&#xff1a;周亮&#xff08;zhou liang&#xff09;组织举办了主题为《大环境下的Oracle数据库的机遇与挑战》活动&#xff0c;大约有50名左右的人…

刷完这个笔记,17K不能再少了....

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;得准备面试了&#xff0c;又不知道从何下手&#xff01;为了帮大家节约时间&#xff0c;特意准备了一份面试相关的资料&#xff0c;内容非常的全面&#xff0c;真的可以好好补一补&#xff0c;希望大家在都能拿到理想…

马赛克处理

去取马赛克的网址&#xff1a; Redact • Photo - Free And Private Image Redaction In The Browser https://redact.photo/ REDACT.PHOTO &#xff08;照片马赛克处理在线工具&#xff09;简介 REDACT.PHOTO是一个照片马赛克处理在线工具&#xff0c;能够帮助我们非常方便…

ChatGPT使用体验

ChatGPT使用体验 前言 介绍ChatGPT 体验ChatGPT 菜谱 编程学习 出行导航 导游攻略 中英翻译 电影推荐 文章总结 总结 前言 最近关于ChatGPT的话题已经火爆了&#xff0c;我也观察和体验了一段时间。平心而论&#xff0c;这东西真的黑科技&#xff0c;大多行业都能通…

Windows10安装二进制Mysql-5.7.41和汉化

1.创建my.ini [mysqld] ##skip-grant-tables1 port 3306 basedirD:/webStudy/mysql-5.7.41 datadirE:/adata/mysqlData max_connections200 character-set-serverutf8 default-storage-engineINNODB sql_modeNO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES [mysql] default-char…

论文解读|MetaAI图像分割基础模型SAM——解锁数字大脑“视觉区”

原创 | 文 BFT机器人 内容提要 事件背景: 2023年4月5日&#xff0c;MetaAI研究团队发布论文“分割一切”一《Segment Anything》并在官网发布了图像分割基础模型一Segment Anything Model(SAM)以及图像注释数据集Segment-Anything 1-Billion(SA-1B)。 论文核心观点 : 目…

Simulink 和 Gazebo联合仿真控制机械臂【Matlab R2022a】

逛 B 站&#xff0c;偶然发现一个 up 主上传的视频&#xff0c;可以实现 Simulink 中搭建机器人的控制器设计&#xff0c;对运行在虚拟机中 Gazebo 中的机械臂进行控制&#xff0c;链接&#xff1a;三关节机械臂Gazebo-Simulink联合仿真&#xff0c;这让我很感兴趣&#xff0c;…

60岁的机器视觉工程师,你还在敲代码?不想做机器视觉工程师,还可以做什么?机器视觉工程师职业生命线有多长​?

如果按程序员参加工作时间为22岁计算,平均退役年龄为35岁计算的话,程序员的职业寿命大概为14年。为什么程序员的职业生命线如此短暂呢?大致有以下几点—— 1、编程技术层出不穷,迭代速度非常快,这时候就需要我们不断的学习,不断地保持学习能力,当随着年龄的增长我们的学…

K8S系列之污点和容忍度详细分析

架构图 本篇文档主要介绍污点和容忍度的关系。 污点和容忍度 污点顾名思义就是脏的东西&#xff0c;给节点添加污点来限制pod调度到该节点上&#xff0c;如果pod可以容忍这种污点就可以被调度到有污点的节点上&#xff0c;如果不能容忍就不能被调度到该节点上。 污点作用于节…

Java版本企业电子招采系统源码——信息数智化招采系统

信息数智化招采系统 服务框架&#xff1a;Spring Cloud、Spring Boot2、Mybatis、OAuth2、Security 前端架构&#xff1a;VUE、Uniapp、Layui、Bootstrap、H5、CSS3 涉及技术&#xff1a;Eureka、Config、Zuul、OAuth2、Security、OSS、Turbine、Zipkin、Feign、Monitor、Stre…

Nginx实现负载均衡

张三开发了一个分享文学作品的网站&#xff0c;刚开始入驻的作者不多&#xff0c;可谓是人烟稀少。但入驻的作者们信念坚定&#xff0c;孜孜不倦地更新着自己的作品&#xff0c;功夫不负有心人&#xff0c;作品迎来了爆发式的阅读增长量&#xff0c;终于&#xff0c;大访问量使…

YooAsset | Unity资源管理方案

跳转官方仓库地址 一、说明 可空包、可首包DLC、可满足限制包体的需求、可玩家自己制作MOD上传到服务器、可分工程构建&#xff1b;支持内置渲染管线、可编程渲染管线&#xff1b;支持完整路径、可寻址资源定位&#xff1b;基于标签打包&#xff0c;自动分析冗余&#xff0c;基…

有效和无效的帮助中心区别在哪?如何设计有效的帮助中心?

帮助中心就是一个丰富的知识库&#xff0c;可以对企业的潜在客户进行引导。不仅能够提升用户的使用体验还能为企业塑造更加专业的品牌形象&#xff0c;在使用过程中为用户提供帮助。帮助中心的目的就是为了解决用户在使用过程中遇到的困难&#xff0c;同时为用户的使用提供引导…

《操作系统》——计算机系统概述

前言&#xff1a; 在之前的【Linux】学习中&#xff0c;我们已经对常见指令已经开发工具等进行了详细的了解。紧接着&#xff0c;我们将要学习的便是关于【Linux进程】的基本知识。但是为了帮助大家更好的理解相关的知识概念&#xff0c;我先带领大家来学习关于《操作系统》这…