5.11 泛型异常包装器的应用

问题

存在一个抛出异常的 lambda 表达式,但用户希望使用泛型包装器(generic wrapper)来捕获所有受检异常,并将它们作为非受检异常重新抛出。

方案

创建特殊的异常类(exception class),添加一个接受这些类的泛型方法,并返回不抛出异常的 lambda 表达式。

讨论

范例 5.9 和范例 5.10 讨论了如何委托一个单独的方法来处理 lambda 表达式抛出的异常,不过我们需要为每个可能抛出异常的操作定义一个私有方法。可以采用泛型包装器使其更为通用。

为此,我们定义一个单独的函数式接口,它包含一个声明抛出 Exception 的方法,然后通过某种包装器方法(wrapper method)将其连接到用户代码。

例如,Stream 接口的 map 方法需要一个 Function,但 Function 接口的 apply 方法不会声明任何受检异常。为了在 map 方法中使用可能抛出受检异常的 lambda 表达式,我们创建一个单独的函数式接口,声明它将抛出 Exception,如例 5-39 所示。

例 5-39 抛出 Exception 的函数式接口

  1. @FunctionalInterface
  2. public interface FunctionWithException<T, R, E extends Exception> {
  3. R apply(T t) throws E;
  4. }

接下来,在 try/catch 代码块中包装 apply 方法,以添加一个传入 FunctionWithException 并返回 Function 的包装器方法,如例 5-40 所示。

例 5-40 用于处理异常的包装器方法

  1. private static <T, R, E extends Exception>
  2. Function<T, R> wrapper(FunctionWithException<T, R, E> fe) {
  3. return arg -> {
  4. try {
  5. return fe.apply(arg);
  6. } catch (Exception e) {
  7. throw new RuntimeException(e);
  8. }
  9. };
  10. }

如上所示,wrapper 方法接受抛出 Exception 的代码,并构建必要的 try/catch 块,同时委托给 apply 方法。在本例中,wrapper 方法被声明为 static,但这并非强制要求。如例 5-41 所示,我们可以使用任何抛出异常的 Function 来调用包装器。

例 5-41 泛型静态包装器方法的应用

  1. public List<String> encodeValuesWithWrapper(String... values) {
  2. return Arrays.stream(values)
  3. .map(wrapper(s -> URLEncoder.encode(s, "UTF-8")))
  4. .collect(Collectors.toList());
  5. }

➊ 使用 wrapper 方法

接下来,可以在抛出异常的 map 操作中编写代码,wrapper 方法会将其作为未受检异常重新抛出。这种方案的不足之处在于,需要为所有计划使用的函数式接口创建单独的泛型包装器(如 ConsumerWithExceptionSupplierWithException)。

这未免有些复杂。有鉴于此,不难理解为何某些 Java 框架(如 Spring 和 Hibernate)甚至整个语言(如 Groovy 和 Kotlin)在捕获所有受检异常后,再将它们作为非受检异常重新抛出。

另见

有关受检异常与 lambda 表达式的讨论请参见范例 5.10,有关利用提取的方法处理 lambda 表达式中的异常请参见范例 5.9。