以调用
java.util.ArrayList#forEach
方法为例,用不同的lambda表达式重写该
Consumer
接口。
RuntimeException
list
中有两个元素,调用
forEach
遍历,使用lambda重写
Consumer
接口,抛出不受检异常
RuntimeException
。
下面代码编译通过。
@Test
public void listForeachRuntimeExceptionTest() {
final List<Integer> list = new ArrayList<>(2);
list.add(1);
list.add(2);
list.forEach(
i -> {
System.out.println(i);
throw new RuntimeException();
lambda内部抛出IOException
对上面的代码做一点修改,修改抛出的异常为IOException
:
@Test
public void listForeachCheckedExceptionTest() {
final List<Integer> list = new ArrayList<>(2);
list.add(1);
list.add(2);
list.forEach(
i -> {
System.out.println(i);
throw new IOException();
如此编译会报错,因为IOException
是受检异常,需要被捕获或抛到外层处理。
外层方法抛出该异常
将代码修改为在外层方法抛出该异常,编译也是会报错的,如下代码:
@Test
public void listForeachCheckedExceptionTest() throws IOException {
final List<Integer> list = new ArrayList<>(2);
list.add(1);
list.add(2);
list.forEach(
i -> {
System.out.println(i);
throw new IOException();
这是由于java.util.function.Consumer#accept
方法签名没有声明抛出IOException
,并且java.util.ArrayList#forEach
方法也没有对异常的处理。所以导致lambda表达式重写后的方法签名也是没有声明有异常抛出的。
java.util.function.Consumer#accept
源码
@FunctionalInterface
public interface Consumer<T> {
* Performs this operation on the given argument.
* @param t the input argument
void accept(T t);
......
所以要想编译不报错,上面代码只能在lambda内部捕获该异常:
@Test
public void listForeachCheckedExceptionTest() {
final List<Integer> list = new ArrayList<>(2);
list.add(1);
list.add(2);
list.forEach(
i -> {
System.out.println(i);
try {
throw new IOException();
} catch (IOException e) {
e.printStackTrace();
由于java.util.ArrayList#forEach
的函数式形参java.util.function.Consumer
类,它的accept
方法签名没有声明抛出IOException
,所以内部的受检异常IOException
必须捕获。
自定义函数式接口(声明受检异常)
难道所有的lambda表达式,遇到受检异常就必须都在内部捕获吗?
当然不是。
受检异常函数式接口
为了方便后续测试,自定义一个接口,只有一个方法action
,方法签名带有受检异常(IOException
)。
* @author https://www.cnblogs.com/theRhyme/
@FunctionalInterface
public interface CheckedExceptionAction<T> {
void action(T t) throws IOException;
自定义forEach接口声明异常
这里自定义一个接口,声明的是抛出受检异常IOException
。
* @author https://www.cnblogs.com/theRhyme/
public interface MyIterable<T> {
void forEach(CheckedExceptionAction<? super T> action) throws IOException;
自定义forEach实现
自定义MyList
,为了方便测试,这里只有add
和forEach
方法。
由于是重写的上面自定义接口的forEach
方法,方法声明自然也有throws IOException
,这里的声明异常是为了将CheckedExceptionAction#action
产生的异常抛出去,当然也可以在自定义的forEach
中捕获。
* @author https://www.cnblogs.com/theRhyme/
public class MyList<T> implements RandomAccess, MyIterable<T> {
private List<T> list;
public MyList() {
list = new ArrayList<>();
public boolean add(T t) {
return list.add(t);
@Override
public void forEach(CheckedExceptionAction<? super T> action) throws IOException {
for (T t : list) {
action.action(t);
添加两个元素,并且在forEach
中抛出的受检异常IOException
,代码编译通过。
所以当forEach
中的函数式形参CheckedExceptionAction
声明了对应的受检异常,则lambda表达式内部不用捕获该异常。
* @author https://www.cnblogs.com/theRhyme/
public class Main {
public static void main(String[] args) throws IOException {
final MyList<Integer> myList = new MyList<>();
myList.add(1);
myList.add(2);
myList.forEach(i -> {
System.out.println(i);
throw new IOException();
受检异常与不受检异常
受检异常(checkedException),又叫做编译时异常,指的是代码编译期间,必须由编程人员手动处理(try-catch捕获或者throws抛出)的异常,否则代码报错,例如IOException
。
不受检异常(uncheckedException),又叫运行时异常(RuntimeException),指的是代码运行时出现的异常,比如ArrayIndexOutOfBoundsException
,ClassCastException
。
在JDK源码中,像java.util.ArrayList#forEach
一样的函数式形参Consumer#accept
的方法声明中,没有声明对应的受检异常,所以传递lambda表达式内部可以抛出不受检异常RuntimeException,但是对于受检异常(如**IOException**
)只能在内部捕获该异常;
如果函数式形参声明了对应的受检异常,则lambda表达式内部可以抛出对应的受检异常,即不用在内部捕获;
函数式编程本质就是重写方法,返回对象实例的过程,既然是重写方法,原方法未声明异常,自然重写的方法就不能声明异常;
Lambda表达式内部是否需要捕获受检异常,实际上与Lambda表达式重写的方法有关。