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表达式中处理特定异常并不失简洁性。学习了如何定义自己的函数式接口,通过包装可以把检查异常变为非检查异常并处理。

lambda表达式无法抛出异常_Lambda 异常处理相关推荐

  1. lambda表达式可以用来声明_Lambda 的骚操作,你都get到了没

    点击上方关注,选择设为星标每天分享优质干货 Lambda简介 Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中 ...

  2. java lambda表达式详解_Lambda表达式详解

    1 Lambda表达式是Java8中的新特性 Java8中引入Lambda表达式,使得java可以函数式编程,在并发性能上迈出了实质性的一步. 什么是函数式编程?函数式编程(英语:functional ...

  3. lambda表达式可以用来声明_lambda表达式可以用来创建包含多个表达式的匿名函数...

    [单选题]下面程序中语句print(i*j)一共执行了____次. for i in range(5): for j in range(2,5): print(i*j) [填空题]_____是目前比较 ...

  4. C++11:Lambda表达式(匿名函数)理解

    C++在C11标准中引入了匿名函数,即没有名字的临时函数,又称之为lambda表达式.lambda表达式 实质上是创建一个匿名函数/对象.即你可以理解为(Lambda 表达式实际上是一个函数,只是它没 ...

  5. C++11 学习笔记 lambda表达式

    http://blog.csdn.net/fjzpdkf/article/details/50249287 lambda表达式是C++11最重要也最常用的一个特性之一.lambda来源于函数式编程的概 ...

  6. 《Java8实战》笔记(03):Lambda表达式

    本文源码 Lambda 管中窥豹 可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表.函数主体.返回类型,可能还有一个可以抛出的异常列表. Lambda表达 ...

  7. java 8 lambda表达式中的异常处理

    文章目录 简介 处理Unchecked Exception 处理checked Exception 总结 java 8 lambda表达式中的异常处理 简介 java 8中引入了lambda表达式,l ...

  8. Java高级-Lambda 表达式、异常处理、集合、泛型、List集合、Map集合、Collections类

    目录 Lambda 表达式 Java Lambda 表达式的优缺点 异常处理 Exception 处理及常见异常 try catch语句 try catch finally语句 throws 声明异常 ...

  9. lambda表达式_Lambda表达式详解

    Lambda简介 Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构. JDK 也提供了 ...

最新文章

  1. jenkins 命令找不到
  2. SpringBoot 自定义Banner
  3. 由VMWorld2010想到的Social Media宣传
  4. python变量类型是集合_python基础-基本数据类型:集合
  5. 论文浅尝 | 用异源监督进行关系抽取:一种表示学习方法
  6. iis5.1/6.0/7.0+ 配置url重写 无扩展名伪静态
  7. Linux idle基础
  8. AI如何驱动软件开发?华为云DevCloud 权威专家邀你探讨
  9. DrawArc绘制弧线
  10. 这次,让算法走下神坛!
  11. poj 1113 graham模板(水平序)
  12. 李沐-动手学深度学习
  13. vivado 下载代码到flash
  14. react 最佳入门_详解React-Todos入门例子
  15. 解决linux kernel 提交gerrit时,运行checkpatch.pl产生的xxxx64_defconfig not generated by savedefconfig问题
  16. 在小写与大写之间加下划线
  17. 制订项目进度计划的讨论
  18. 谁曾从谁的时光里停留
  19. 笔记本合上盖子不能从睡眠中唤醒解决办法
  20. 基于javaweb的医院挂号预约系统-计算机毕业设计

热门文章

  1. LeetCode 力扣 56. 合并区间
  2. IDEA 卡住半天,buid(编译)不动——解决办法(适用于maven和gradle)及定位思路...
  3. MySQL Day05 子查询、函数、MD5加密、SELECT小结
  4. 【多线程】多线程基础知识
  5. Arduino I2C任意更换SDA SCL GPIO引脚
  6. 计算机应用教学总结,计算机教学总结
  7. 使用监听器Listener实现在线人数统计功能
  8. 开关电源产生浪涌电流的原因
  9. kumo词云使用io_Microsoft Kumo Search可以在Google上使用。 再次。
  10. 求1+2!+3!+...+N!的和