以调用 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,为了方便测试,这里只有addforEach方法。
由于是重写的上面自定义接口的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),指的是代码运行时出现的异常,比如ArrayIndexOutOfBoundsExceptionClassCastException

在JDK源码中,像java.util.ArrayList#forEach一样的函数式形参Consumer#accept的方法声明中,没有声明对应的受检异常,所以传递lambda表达式内部可以抛出不受检异常RuntimeException,但是对于受检异常(如**IOException**)只能在内部捕获该异常
如果函数式形参声明了对应的受检异常,则lambda表达式内部可以抛出对应的受检异常,即不用在内部捕获
函数式编程本质就是重写方法,返回对象实例的过程,既然是重写方法,原方法未声明异常,自然重写的方法就不能声明异常;
Lambda表达式内部是否需要捕获受检异常,实际上与Lambda表达式重写的方法有关

作者:rhyme