lambda表达式无法抛出异常_Lambda 异常处理

lambda表达式无法抛出异常

背景:在一个方法中使用了lambda表达式,表达式中需要捕获异常,使用throws关键字发现并不起作用,必须使用trycatch才行

public class BeanUtil {public static <T,R> List<R> copyList(List<T> source , Class<R> clazz) throws Exception {if(CollectionUtils.isEmpty(source)){return null;}List<R> rList = source.stream().map(s ->{R r = null;r = clazz.newInstance();BeanUtils.copyProperties(s,r);return r;} ).collect(Collectors.toList());return rList;}
}

如上,虽然在方法copyList上使用了throws Exception但是已经无法通过编译,提示有未处理异常,正确代码如下,使用trycatch

			try {r = clazz.newInstance();} catch (Exception e) {e.printStackTrace();}

提问:为何lambda表达式中的异常不能在外部throws,只能内部捕获?

看Stream.map(Function<? super T, ? extends R> mapper)方法,参数为Function实例,源码:

public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {Objects.requireNonNull(mapper);return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {@OverrideSink<P_OUT> opWrapSink(int flags, Sink<R> sink) {return new Sink.ChainedReference<P_OUT, R>(sink) {@Overridepublic void accept(P_OUT u) {downstream.accept(mapper.apply(u));}};}};}

最终调用了Function中的apply方法,事实上,我们在map方法中写的s ->{……},其实就是重写apply方法的实现,而抛出异常的语句"r = clazz.newInstance()"就是在apply方法中抛出的,就是说在异常语句和copyList方法之间还存在一个apply方法,而apply方法是不允许向上抛出异常的,所以copyList方法自然不能使用throws抛异常。


Lambda异常处理

java8 lambda表达式利用函数式编程提供精简的方式表达行为。然而,JDK函数式接口没有很好地处理异常,使得处理异常代码非常臃肿和麻烦。本文探讨在lambda表达式中处理异常的一些方式。

处理非检查异常

首先我们通过示例来说明问题。有List和常量除,比如50和list中每个元素除并打印出结果:

List<Integer> integers = Arrays.asList(3, 9, 7, 6, 10, 20);
integers.forEach(i -> System.out.println(50 / i));

上述代码正常工作,但有问题。如果list有元素值为0,那么会抛出异常ArithmeticException: / by zero。我们利用传统的try-catch块处理该异常,打印异常内容并继续执行一个元素:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> {try {System.out.println(50 / i);} catch (ArithmeticException e) {System.err.println("Arithmetic Exception occured : " + e.getMessage());}
});

利用try-catch块解决了问题,但没有了lambda表达式的精简性,不再是一个小函数了。为了解决这个问题,我们给lambda函数写一个lambda包装器。请看代码:

static Consumer<Integer> lambdaWrapper(Consumer<Integer> consumer) {return i -> {try {consumer.accept(i);} catch (ArithmeticException e) {System.err.println("Arithmetic Exception occured : " + e.getMessage());}};
}

然后进行调用:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(lambdaWrapper(i -> System.out.println(50 / i)));

上面代码我们首先定义包装方法负责处理异常,然后将其作为参数传给遍历方法。通过包装方法解决了问题,但你可能争辩这仅仅从一个地方删除try-catch,移动到另一个方法中,实际并没有减少代码。

这时事实,因为包装器仅给特定的类型使用,但我们可以使用泛型提升包装方法的使用范围:

static <T, E extends Exception> Consumer<T>consumerWrapper(Consumer<T> consumer, Class<E> clazz) {return i -> {try {consumer.accept(i);} catch (Exception ex) {try {E exCast = clazz.cast(ex);System.err.println("Exception occured : " + exCast.getMessage());} catch (ClassCastException ccEx) {throw ex;}}};
}

调用代码:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(consumerWrapper(i -> System.out.println(50 / i),  ArithmeticException.class));12

你看到包装方法带两个参数,lamdba表达式和捕获异常类型。lamdba包装器能够处理任何类型,不仅是Integer,也可以捕获任何类型的异常,而不仅是超类Exception。

你可能注意到包装器的方法名从lambdaWrapper改成了consumerWrapper。那是因为这个方法仅处理Consumer类型的函数接口的lambda表达式。同样我们也可以定义其他函数接口的包装方法,如:Function, BiFunction, BiConsumer 等。

处理检查异常

考虑之前的示例,我们不再除元素并打印结果至控制台,我们想写至文件,该操作会抛出异常IOException:

static void writeToFile(Integer integer) throws IOException {// logic to write to file which throws IOException
}
```java
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> writeToFile(i));

一旦编译,会出现下面错误:

java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException

因为IOException异常是检查异常,代码必须处理。现在我们有两种方法,通过throw签名方法让调用者处理,或在lambda方法内部处理。下面分别进行讨论。

从Lambda表达式中throw检查异常

在包括lambda表达式的方法上通过throw抛出异常:

public static void main(String[] args) throws IOException {List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);integers.forEach(i -> writeToFile(i));
}

然而,编译仍然报相同的未处理异常IOException,因为lambda表达式类似于匿名内部类。在这种情况下,lambda表达式是带有accept(T t)方法的Consumer接口的实现。从main方法中throw异常没有意义,因为在父接口中的方法没有throw任何异常,其实现中也不能有throw异常:

Consumer<Integer> consumer = new Consumer<Integer>() {@Overridepublic void accept(Integer integer) throws Exception {writeToFile(integer);}
};

上面代码不能编译,因为accept实现方法不能throw任何异常。最直接方法使用try-catch块并包装检查异常至非检查异常并重新throw:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> {try {writeToFile(i);} catch (IOException e) {throw new RuntimeException(e);}
});

通过这个方法获得编译器通过并正常工作,但出现了和前面提及的问题一样。既然我们仅项抛出异常,我们只需要定义自己的Consumer 函数接口可以抛出异常,然后在包装方法中使用,下面定义ThrowingConsumer:

@FunctionalInterface
public interface ThrowingConsumer<T, E extends Exception> {void accept(T t) throws E;
}1234
static <T> Consumer<T> throwingConsumerWrapper(ThrowingConsumer<T, Exception> throwingConsumer) {return i -> {try {throwingConsumer.accept(i);} catch (Exception ex) {throw new RuntimeException(ex);}};
}

现在我们可以写lambda表达式,既可以抛异常也不失简洁性。

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(throwingConsumerWrapper(i -> writeToFile(i)));

处理Lambda表达式中检查异常

最后,我们仅需要修改包装器使其可以处理检查异常。既然ThrowingConsumer 使用泛型,我们能处理任何类型异常:

static <T, E extends Exception> Consumer<T> handlingConsumerWrapper(ThrowingConsumer<T, E> throwingConsumer, Class<E> exceptionClass) {return i -> {try {throwingConsumer.accept(i);} catch (Exception ex) {try {E exCast = exceptionClass.cast(ex);System.err.println("Exception occured : " + exCast.getMessage());} catch (ClassCastException ccEx) {throw new RuntimeException(ex);}}};
}

我们能使用该包装器处理IOException,包装器把任何其他检查异常至一个非检查异常:

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(handlingConsumerWrapper( i -> writeToFile(i), IOException.class));

与处理非检查异常一样,其他类似的函数接口也可类似处理,如ThowingFunction, ThrowingBiFunction, ThrowingBiConsumer 。

总结

本文我们介绍了如何使用包装器在lambda表达式中处理特定异常并不失简洁性。学习了如何定义自己的函数式接口,通过包装可以把检查异常变为非检查异常并处理。

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

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

相关文章

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基础上.建立一…

vxworks linux 多线程api,vxWorks多任务编程初探(转)

进程(Process)是具有一定独立功能的程序关于某个数据集合上的一次运行活动&#xff0c;是系统进行资源分配和调度的一个独立单位。程序只是一组指令的有序集合&#xff0c;它本身没有任何运行的含义&#xff0c;只是一个静态实体。而进程则不同&#xff0c;它是程序在某个数据集…

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

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

10个最受欢迎的JavaScript图表库

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

Linux查看lvm元数据,LVM元数据服务lvmetad分析

lvmetad是LVM中用于处理元数据缓存的服务。缓存状态Empty &#xff1a; 系统中可用的设备还没有被添加到lvmetad。Scanning &#xff1a; 部分系统中可用的设备已经被添加进lvmetad。Initialized &#xff1a; 全部系统中可用的设备已经被添加进lvmetad。Outdated &#xff1a;…

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

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

备份恢复linux,备份和恢复Linux系统

备份系统 首先进入文件系统的根目录(当然&#xff0c;如果你不想备份整个文件系统&#xff0c;你也可以进入你想要备份的目录&#xff0c;包括远程目录或者移动硬盘上的目录)&#xff1a; # cd / 下面是我用来备份系统的完整命令&#xff1a; # tar cvpzf backup.tgz - - exclu…

ProcessBuilder类的介绍及使用_Process和ProcessBuilder入门

ProcessBuilder类的介绍及使用 前言 在做一个项目的时候需要用到运行时动态执行JAVA命令&#xff0c;一开始的思路是运行时生成bat脚本&#xff0c;然后通过Runtime类的exec方法之行bat脚本&#xff0c;但是此方法不好的地方在于脚本执行时会弹出一个cmd程序界面框。后面在参…

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;电子钟表逐渐成为了人们生活中不可缺少的一部分。设计思路…