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的函数式接口
- @FunctionalInterface
- public interface FunctionWithException<T, R, E extends Exception> {
- R apply(T t) throws E;
- }
接下来,在 try/catch 代码块中包装 apply 方法,以添加一个传入 FunctionWithException 并返回 Function 的包装器方法,如例 5-40 所示。
例 5-40 用于处理异常的包装器方法
- private static <T, R, E extends Exception>
- Function<T, R> wrapper(FunctionWithException<T, R, E> fe) {
- return arg -> {
- try {
- return fe.apply(arg);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- };
- }
如上所示,wrapper 方法接受抛出 Exception 的代码,并构建必要的 try/catch 块,同时委托给 apply 方法。在本例中,wrapper 方法被声明为 static,但这并非强制要求。如例 5-41 所示,我们可以使用任何抛出异常的 Function 来调用包装器。
例 5-41 泛型静态包装器方法的应用
- public List<String> encodeValuesWithWrapper(String... values) {
- return Arrays.stream(values)
- .map(wrapper(s -> URLEncoder.encode(s, "UTF-8"))) ➊
- .collect(Collectors.toList());
- }
➊ 使用 wrapper 方法
接下来,可以在抛出异常的 map 操作中编写代码,wrapper 方法会将其作为未受检异常重新抛出。这种方案的不足之处在于,需要为所有计划使用的函数式接口创建单独的泛型包装器(如 ConsumerWithException 和 SupplierWithException)。
这未免有些复杂。有鉴于此,不难理解为何某些 Java 框架(如 Spring 和 Hibernate)甚至整个语言(如 Groovy 和 Kotlin)在捕获所有受检异常后,再将它们作为非受检异常重新抛出。
另见
有关受检异常与 lambda 表达式的讨论请参见范例 5.10,有关利用提取的方法处理 lambda 表达式中的异常请参见范例 5.9。
