lambda处理异常四种方式

news2024/11/16 16:22:22

最近对接第三方呼叫系统,第三方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 类型,那么你可以选择已有的工具库来使用

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

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

相关文章

目前收集到好用且免费的ChatGPT镜像站

ChatGPT是一个基于人工智能的聊天机器人&#xff0c;它可以与用户进行自然语言交互。ChatGPT使用了最新的自然语言处理技术&#xff0c;包括深度学习和神经网络&#xff0c;以便更好地理解用户的意图和回答用户的问题。 ChatGPT可以回答各种问题&#xff0c;包括但不限于常见问…

Linux——进程信号2

阻塞信号 信号其他相关常见概念 实际执行信号的处理动作称为信号递达(Delivery) 信号从产生到递达之间的状态,称为信号未决(Pending)。 进程可以选择阻塞 (Block )某个信号。 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作. 注意,阻塞和忽略…

一文4000字用Jmeter +Maven+jenkins实现接口性能全自动化测试

背景&#xff1a; 首先用jmeter录制或者书写性能测试的脚本&#xff0c;用maven添加相关依赖&#xff0c;把性能测试的代码提交到github&#xff0c;在jenkins配置git下载性能测试的代码&#xff0c;配置运行脚本和测试报告&#xff0c;配置运行失败自动发邮件通知&#xff0c…

分布式id解决方法--雪花算法

uuid&#xff0c;jdk自带&#xff0c;但是数据库性能差&#xff0c;32位呀。 mysql数据库主键越短越好&#xff0c;Btree产生节点分裂&#xff0c;大大降低数据库性能&#xff0c;所以uuid不建议。 redis的自增&#xff0c;但是要配置维护redis集群&#xff0c;就为了一个id&a…

【前后端分离博客】学习笔记01 --- 登录模块Sa-Token

前言 用于记录自己学习博客项目的流程 基于Springboot Vue3 开发的前后端分离博客 项目源码&#xff1a;Blog: 基于SpringBoot Vue3 TypeScript Vite的个人博客&#xff0c;MySQL数据库&#xff0c;Redis缓存&#xff0c;ElasticSearch全文搜索&#xff0c;支持QQ、Gite…

20230509MTCNN2

卷积切分图片 怎么切分图片? 使用opencv,PIL切分图片有什么问题? 慢 使用 卷积来切分图片 卷积的运算过程 类似于切图 卷积 对输入图片的尺寸 有没有 的要求? 就是 输入的图片尺寸 必须大于 卷积核的大小 test1.py import torch from torch import nn""&quo…

springboot + vue3实现视频播放Demo

文章目录 学习链接前言ffmpeg安装ffmpeg配置环境变量分割视频文件 后台配置WebConfig 前端代码video.js示例安装依赖视频播放组件效果 Vue3-video-play示例安装依赖main.js中使用视频播放组件效果 学习链接 ffmpeg官网 长时长视频java存储及vue播放解决方法 【 攻城略地 】vue…

BitKeep逆势崛起:千万用户的信任,终点还未到来

在全球范围内&#xff0c;BitKeep钱包如今已拥有超过千万忠实用户。 当我得知这一令人震撼的数字时&#xff0c;既感到惊讶&#xff0c;同时也觉得这是意料之中的事情。几年来关注BitKeep的发展历程&#xff0c;我深切地感受到了这家公司的蓬勃壮大。回顾2018年他们发布的第一个…

linux0.12-8-9-fork.c

[362页] 1、 verify_area函数给其他文件使用的&#xff0c;跳转开始位置&#xff1b; 2、 copy_mem函数复制内存页表&#xff1b; 3、 copy_process函数是fork.c主要函数&#xff1b; 4、find_empty_process函数就2个作用&#xff1a;在一个范围内找last_pid和找空槽&#xff1…

如何利用互联网优势进行茶叶销售?

茶叶是中国传统文化的重要组成部分&#xff0c;具有丰富的文化内涵和高度的营养价值。如今&#xff0c;随着互联网的普及&#xff0c;越来越多的茶叶销售商&#xff08;文章编辑ycy6221&#xff09;开始利用互联网的优势来开拓市场。本文将介绍如何利用互联网优势进行茶叶销售。…

SecureCRT的下载安装

亲测成功了&#xff0c;按照下面的步骤完成即可&#xff01; 下载安工具包包地址连接&#xff1a;网盘地址点击即可 提取码&#xff1a;0lp7 1、下载SecureCRT 2、从百度网盘下载SecureCRT&#xff0c;页面如下 3、安装SecureCRT 4、激活SecureCRT 第一步&#xff1a;打开安装…

自学Java怎么找工作?好程序员学员大厂面试经验分享!

简历要详细&#xff1a; 简历中的项目用到的技术点和个人负责的模块尽量写详细一些。其次&#xff0c;根据自己项目中用到的熟悉的技术点&#xff0c;在个人技能介绍模块去突出&#xff0c;面试官基本会根据你简历上写的去提问的&#xff0c;这样我们回答起来就会更加得心应手。…

【多线程初阶四】单例模式阻塞队列

目录 &#x1f31f;一、单例模式 &#x1f308;1、饿汉模式 &#x1f308;2、懒汉模式&#xff08;重点&#xff01;&#xff09; &#x1f31f;二、工厂模式 &#x1f31f;三、阻塞式队列 &#x1f308;1、阻塞队列是什么&#xff1f; &#x1f308;2、…

如何注册appuploader账号​

如何注册appuploader账号​ 我们上一篇讲到appuploader的下载安装&#xff0c;要想使用此软件呢&#xff0c;需要注册账号才能使用&#xff0c;今​ 天我们来讲下如何注册appuploader账号来使用软件。​ 1.Apple官网注册Apple ID​ 首先我们点击首页左侧菜单栏中的“常见网…

为什么企业选择局域网即时通讯软件?局域网即时通讯软件哪家好?

在当今互联网普及的时代&#xff0c;企业内部的沟通对企业管理有着非常重要的意义&#xff0c;即时通讯软件已成为企业工作中广泛采用的沟通工具。 然而&#xff0c;随着企业内部敏感信息通过互联网泄露的频繁发生&#xff0c;例如在工作期间&#xff0c;企业员工自发地频繁使…

盘点四款免费在线采购管理系统

今天来盘点五款免费在线采购管理系统。中小型企业在选择采购管理系统时成本是需要考虑的重要因素之一&#xff0c;因此免费在线的采购管理系统是最合适的第一步选择&#xff0c;本文将为您盘点免费在线采购管理系统&#xff1a;1.简道云&#xff1b;2.甄云&#xff1b;3.携客云…

【正点原子STM32连载】 第九章 STM32启动过程分析 摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第九章…

Redis可持久化详解1

目录 Redis可持久化 以下是RDB持久化的代码示例&#xff1a; 面试常问 1什么是Redis的持久化机制&#xff1f; 2Redis支持哪些持久化机制&#xff1f;它们有什么区别&#xff1f; 3Redis的RDB持久化机制的原理是什么&#xff1f; 4Redis的AOF持久化机制的原理是什么&…

《三》包管理工具 npm

包管理工具 npm&#xff1a; npm&#xff1a;Node Package Manager&#xff0c;Node 包管理器&#xff0c;目前已经不仅仅作为 Node 的包管理工具&#xff0c;也作为前端的包管理工具来管理包。 npm 管理的包是存放在一个名为 registry 的仓库中的&#xff0c;发布一个包时是…

分享推荐32位MCU低成本替换8/16位升级完美之选——MM32G0001

灵动微在嵌入式闪存技术上做了优化&#xff0c;采用更稳定和经大规模量产验证的12寸晶圆工艺平台&#xff0c;对产品的功能、性能和成本进行了全方位的打磨&#xff0c;在保持MM32品质目标的前提下&#xff0c;推出了这款极具性价比、低成本的MM32G0001系列MCU产品。 不同于市…