这样也行,在lambda表达式中优雅的处理checked exception

简介: 最近发现很多小伙伴还不知道如何在lambda表达式中优雅的处理checked exception,所以今天就重点和大家来探讨一下这个问题。lambda表达式本身是为了方便程序员书写方便的工具,使用lambda表达式可以让我们的代码更加简洁。

简介

最近发现很多小伙伴还不知道如何在lambda表达式中优雅的处理checked exception,所以今天就重点和大家来探讨一下这个问题。

lambda表达式本身是为了方便程序员书写方便的工具,使用lambda表达式可以让我们的代码更加简洁。

可能大多数小伙伴在使用的过程中从来没有遇到过里面包含异常的情况,所以对这种在lambda表达式中异常的处理可能没什么经验。

不过没关系,今天我们就来一起探讨一下。

lambda表达式中的checked exception

java中异常的类型,大家应该是耳熟能详了,具体而言可以有两类,一种是checked exception, 一种是unchecked exception。

所谓checked exception就是需要在代码中手动捕获的异常。unchecked exception就是不需要手动捕获的异常,比如运行时异常。

首先我们定义一个checked exception,直接继承Exception就好了:

public class MyCheckedException extends Exception{
    @java.io.Serial
    private static final long serialVersionUID = -1574710658998033284L;
    public MyCheckedException() {
        super();
    }
    public MyCheckedException(String s) {
        super(s);
    }
}

接下来我们定义一个类,这个类中有两个方法,一个抛出checked exception,一个抛出unchecked exception:

public class MyStudents {
    public int changeAgeWithCheckedException() throws MyCheckedException {
        throw new MyCheckedException();
    }
    public int changeAgeWithUnCheckedException(){
        throw new RuntimeException();
    }
}

好了,我们首先在lambda表达式中抛出CheckedException:

public static void streamWithCheckedException(){
        Stream.of(new MyStudents()).map(s->s.changeAgeWithCheckedException()).toList();
    }

这样写在现代化的IDE中是编译不过的,它会提示你需要显示catch住CheckedException,所以我们需要把上面的代码改成下面这种:

public static void streamWithCheckedException(){
        Stream.of(new MyStudents()).map(s-> {
            try {
                return s.changeAgeWithCheckedException();
            } catch (MyCheckedException e) {
                e.printStackTrace();
            }
        }).toList();
    }

这样做是不是就可以了呢?

再考虑一个情况,如果stream中不止一个map操作,而是多个map操作,每个map都抛出一个checkedException,那岂不是要这样写?

public static void streamWithCheckedException(){
        Stream.of(new MyStudents()).map(s-> {
            try {
                return s.changeAgeWithCheckedException();
            } catch (MyCheckedException e) {
                e.printStackTrace();
            }
        }).map(s-> {
            try {
                return s.changeAgeWithCheckedException();
            } catch (MyCheckedException e) {
                e.printStackTrace();
            }
        }).
        toList();
    }

实在是太难看了,也不方便书写,那么有没有什么好的方法来处理,lambda中的checked异常呢?办法当然是有的。

lambda中的unchecked exception

上面例子中我们抛出了一个checked exception,那么就必须在lambda表达式中对异常进行捕捉。

那么我们可不可以换个思路来考虑一下?

比如,把上面的checked exception,换成unchecked exception会怎么样呢?

public static void streamWithUncheckedException(){
        Stream.of(new MyStudents()).map(MyStudents::changeAgeWithUnCheckedException).toList();
    }

我们可以看到程序可以正常编译通过,可以减少或者几乎不需要使用try和catch,这样看起来,代码是不是简洁很多。

那么我们是不是可以考虑把checked exception转换成为unchecked exception,然后用在lambda表达式中,这样就可以简化我们的代码,给程序员以更好的代码可读性呢?

说干就干。

基本的思路就是把传入的checked exception转换为unchecked exception,那么怎么转换比较合适呢?

这里我们可以用到JDK中的类型推断,通过使用泛型来达到这样的目的:

public static <T extends Exception,R> R sneakyThrow(Exception t) throws T {
        throw (T) t;
    }

这个方法接收一个checked exception,在内部强制转换之后,抛出T。

看看在代码中如何使用:

public static void sneakyThrow(){
            Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
    }

代码可以编译通过,这说明我们已经把checked异常转换成为unchecked异常了。

运行之后你可以得到下面的输出:

Exception in thread "main" java.io.IOException
    at com.flydean.Main.lambdasneakyThrow1(Main.java:28)
    at java.base/java.util.stream.ReferencePipeline31.accept(ReferencePipeline.java:197)
    at java.base/java.util.stream.Streams$StreamBuilderImpl.forEachRemaining(Streams.java:411)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575)
    at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260)
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616)
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622)
    at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627)
    at com.flydean.Main.sneakyThrow(Main.java:28)
    at com.flydean.Main.main(Main.java:9)

从日志中,我们可以看出最后抛出的还是java.io.IOException,但是如果我们尝试对这个异常进行捕获:

public static void sneakyThrow(){
        try {
            Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
        }catch (IOException e){
           System.out.println("get exception");
        }
    }

在编译器中会提示编译不通过,因为代码并不会抛出IOException。如果你把IOException修改为RuntimeException,也没法捕获到最后的异常。

只能这样修改:

public static void sneakyThrow(){
        try {
            Stream.of(new MyStudents()).map(s -> SneakilyThrowException.sneakyThrow(new IOException())).toList();
        }catch (Exception e){
           System.out.println("get exception");
        }
    }

才能最终捕获到stream中抛出的异常。所以如果你使用了我这里说的这种异常转换技巧,那就必须要特别注意这种异常的捕获情况。

对lambda的最终改造

上面可以封装异常了是不是就完成了我们的工作了呢?

并不是,因为我们在map中传入的是一个Function而不是一个专门的异常类。所以我们需要对Function进行额外的处理。

首先JDK中的Function中必须实现这样的方法:

R apply(T t);

如果这个方法里面抛出了checked Exception,那么必须进行捕获,如果不想捕获的话,我们可以在方法申明中抛出异常,所以我们需要重新定义一个Function,如下所示:

@FunctionalInterface
public interface FunctionWithThrow<T, R> {
    R apply(T t) throws Exception;
}

然后再定义一个unchecked方法,用来对FunctionWithThrow进行封装,通过捕获抛出的异常,再次调用sneakyThrow进行checked异常和unchecked异常的转换:

static <T, R> Function<T, R> unchecked(FunctionWithThrow<T, R> f) {
        return t -> {
            try {
                return f.apply(t);
            } catch (Exception ex) {
                return SneakilyThrowException.sneakyThrow(ex);
            }
        };
    }

最后,我们就可以在代码中优雅的使用了:

public static void sneakyThrowFinal(){
        try {
            Stream.of(new MyStudents()).map(SneakilyThrowException.unchecked(MyStudents::changeAgeWithCheckedException)).toList();
        }catch (Exception e){
            System.out.println("get exception");
        }
    }

总结

以上就是如何在lambda表达式中优雅的进行异常转换的例子了。大家使用的过程中一定要注意最后对异常的捕获。

好了,本文的代码:

本文的例子https://github.com/ddean2009/learn-java-base-9-to-20/tree/master/lambda-and-checked-exception/


相关文章
|
10月前
|
人工智能 自然语言处理 JavaScript
宜搭上新,DeepSeek 插件来了!
钉钉宜搭近日上线了DeepSeek插件,无需编写复杂代码,普通用户也能轻松调用强大的AI大模型能力。安装后,平台新增「AI生成」组件,支持创意内容生成、JS代码编译、工作汇报等场景,大幅提升工作效率。快来体验这一高效智能的办公方式吧!
2759 9
|
9月前
|
机器学习/深度学习 人工智能 安全
一篇关于DeepSeek模型先进性的阅读理解
本文以DeepSeek模型为核心,探讨了其技术先进性、训练过程及行业影响。首先介绍DeepSeek的快速崛起及其对AI行业的颠覆作用。DeepSeek通过强化学习(RL)实现Time Scaling Law的新范式,突破了传统大模型依赖算力和数据的限制,展现了集成式创新的优势。文章还提到开源的重要性以及数据作为制胜法宝的关键地位,同时警示了业务发展中安全滞后的问题。
1331 176
一篇关于DeepSeek模型先进性的阅读理解
|
消息中间件 Java 数据库
解密Spring Boot:深入理解条件装配与条件注解
Spring Boot中的条件装配与条件注解提供了强大的工具,使得应用程序可以根据不同的条件动态装配Bean,从而实现灵活的配置和管理。通过合理使用这些条件注解,开发者可以根据实际需求动态调整应用的行为,提升代码的可维护性和可扩展性。希望本文能够帮助你深入理解Spring Boot中的条件装配与条件注解,在实际开发中更好地应用这些功能。
294 2
|
关系型数据库 MySQL Java
天天使用MySQL,你知道MySQL数据库能抗多少压力吗?附(真实案例)
天天使用MySQL,你知道MySQL数据库能抗多少压力吗?附(真实案例)
2564 0
|
监控 网络协议 Java
Java TCP长连接详解:实现稳定、高效的网络通信
Java TCP长连接详解:实现稳定、高效的网络通信
|
监控 jenkins 测试技术
理解并实现持续集成(CI)与持续部署(CD):加速软件开发的关键步骤
【5月更文挑战第27天】本文介绍了CI/CD在加速软件开发中的关键作用。CI(持续集成)通过频繁集成代码并自动构建测试,减少错误,提高开发速度和代码质量。实现CI需要版本控制系统(如Git)、自动化构建工具(如Jenkins)和测试框架。CD(持续部署)则进一步自动将通过测试的代码部署到生产环境,提供快速反馈,降低风险。实现CD需配置管理工具(如Ansible)、容器技术(如Docker)和云基础设施。CI与CD结合,形成高效开发流程,最佳实践包括保持主干干净、自动化所有流程、持续监控、快速回滚和持续学习。
|
存储 JSON API
AWS-assumeRole.控制不同token访问不同目录
应用程序需要做sessionToken,提供给用户访问S3的存储资源,但是需要做用户隔离(只能访问自己应用内的资源,不能访问别的用户的资源)
486 0
|
消息中间件 Java 中间件
RocketMQ延迟消息的代码实战及原理分析
在RocketMQ中,支持延迟消息,但是不支持任意时间精度的延迟消息,只支持特定级别的延迟消息。如果要支持任意时间精度,不能避免在Broker层面做消息排序,再涉及到持久化的考量,那么消息排序就不可避免产生巨大的性能开销。
3847 0
|
Oracle 关系型数据库 数据安全/隐私保护
如何选择适合企业的ERP系统
如何选择适合企业的ERP系统
385 0
|
应用服务中间件 nginx
nginx输入请求的header到日志
nginx输入请求的header到日志
942 1