场景

以调用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表达式重写的方法有关