learn and grow up

lambda中的受检查异常

字数统计: 861阅读时长: 3 min
2019/11/30 Share

写在前面

最近开始尝试使用lambda表达式,使用起来感觉代码写起来速度快了很多,也整洁了不少。但是lambda表达式也有多坑,比如隐晦难懂,debug困难等等。今天就遇到了一个问题:lambda无法抛出受检查异常。

正文

  • 场景

    我们都知道java异常分为:受检查异常(Checked Exception)跟非受检异常(UnChecked Exception)

    受检异常表示代码中必须显示的处理该异常,比如try-catch或者在方法声明上加入throws关键字把异常向上抛出。

    非受检异常也被称作运行时异常,就像我们常用的RuntimeException,表示代码中可以不进行处理,直白点说就是Java认为这是人为导致的问题,也是可以人为的在编写代码阶段就避免掉的问题。

    由Error跟RuntimeException派生出来的类都是非受检异常,也就是运行时异常。其他的异常则问受检异常,主要就是IOException及其子类。

    回到上面的代码片段,第一个之所以编译通过就是因为它抛出的是非受检异常,只有运行中才会处理,而编译器在编译阶段并不关心,因此没有问题。

    第二段代码抛出的是IOException,它是一个受检异常,也就是说编译器强制要求开发人员在编译阶段就要显示的处理该异常,不能留给运行阶段,因此编译不通过。

    来看下面这段代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //编译不通过1
    images.stream().forEach(item-> {document.add(item);});
    //编译不通过2
    images.stream().forEach(item-> {
    try {
    document.add(item);
    } catch (DocumentException e) {
    throw e;
    }
    });

    为什么会这样呢?因为 我们使用lambda表达式其实是实现了一个接口 ,可以看到forEach的方法签名如下:

    1
    void forEach(Consumer<? super T> action);

    Consumer类如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @FunctionalInterface
    public interface Consumer<T> {

    /**
    * Performs this operation on the given argument.
    *
    * @param t the input argument
    */
    void accept(T t);

    /**
    * Returns a composed {@code Consumer} that performs, in sequence, this
    * operation followed by the {@code after} operation. If performing either
    * operation throws an exception, it is relayed to the caller of the
    * composed operation. If performing this operation throws an exception,
    * the {@code after} operation will not be performed.
    *
    * @param after the operation to perform after this operation
    * @return a composed {@code Consumer} that performs in sequence this
    * operation followed by the {@code after} operation
    * @throws NullPointerException if {@code after} is null
    */
    default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
    }
    }

    我们throw DocumentException其实是加在了 accept方法中 ,但是接口中的accept是没有抛出异常的声明。所以编译不会通过。

  • 解决方法

    • 放弃使用lambda表达式,以普通循环代替

    • 将异常包装为运行时异常后抛出: throw new RuntimeException(e);

      不建议这种,会破坏代码的结构性

    • 通过泛型的方式包装异常后抛出,代码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      //这里的原理是利用泛型把要抛出异常的类型隐藏起来了,从泛型方法的声明来看,编译器不能明确的知道抛出异常类型
      static <E extends Exception> void lambdaThrowException(Exception e) throws E {
      throw (E)e;
      }


      images.stream().forEach(item-> {
      try {
      document.add(item);
      } catch (DocumentException e) {
      lambdaThrowException(e);
      }
      });

      感兴趣的可以参考stackoverflow中的讨论:

      How can I throw CHECKED exceptions from inside Java 8 streams/lambdas?

CATALOG
  1. 1. 写在前面
  2. 2. 正文