5.10 受检异常与lambda表达式

问题

存在一个抛出受检异常的 lambda 表达式,且所实现的函数式接口中的抽象方法并未声明该异常。

方案

为 lambda 表达式添加一个 try/catch 代码块,或委托给某个提取的方法进行处理。

讨论

lambda 表达式实际上属于函数式接口中单一抽象方法的实现,因此只能抛出在抽象方法签名中声明的受检异常。

假设我们希望通过 URL 调用服务,且需要根据字符串参数的集合构建一个查询字符串,那么参数应该采用可以在 URL 中使用的方式进行编码。为此,Java 提供了一个称为 java.net.URLEncoder 的类(其用途从类名中一目了然),包括一个静态方法 encode,它传入 String 作为参数,并根据指定的编码方式对其进行编码。

我们很容易写出类似例 5-35 所示的代码。

例 5-35 对字符串集合进行 URL 编码(无法编译)

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

➊ 抛出一个必须处理的异常 UnsupportedEncodingException

encodeValues 方法传入一个字符串的可变参数列表,并根据推荐的 UTF-8 编码方式,尝试采用 UREncoder.encode 方法对每个字符串进行编码。然而,由于 encodeValues 方法抛出受检异常 UnsupportedEncodingException,导致上述代码无法编译。

即便声明 encodeValues 方法抛出该异常,代码仍然无法编译。

例 5-36 声明异常(仍然无法编译)

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

➊ 声明 encodeValues 方法抛出 UnsupportedEncodingException,但代码仍然无法编译

问题在于,从 lambda 表达式抛出异常就像采用某种方法构建一个完全独立的类,并从中抛出异常。我们可以将 lambda 表达式视为匿名内部类的实现,由此可见,内部对象抛出的异常仍然需要在内部对象而非周围对象(surrounding object)中进行处理或声明。正确的代码如例 5-37 所示,包括匿名内部类和 lambda 表达式两种实现。

例 5-37 利用 try/catch 代码块进行 URL 编码(可以编译)

  1. public List<String> encodeValuesAnonInnerClass(String... values) {
  2. return Arrays.stream(values)
  3. .map(new Function<String, String>() {
  4. @Override
  5. public String apply(String s) {
  6. try {
  7. return URLEncoder.encode(s, "UTF-8");
  8. } catch (UnsupportedEncodingException e) {
  9. e.printStackTrace();
  10. return "";
  11. }
  12. }
  13. })
  14. .collect(Collectors.toList());
  15. }
  16.  
  17. public List<String> encodeValues(String... values) {
  18. return Arrays.stream(values)
  19. .map(s -> {
  20. try {
  21. return URLEncoder.encode(s, "UTF-8");
  22. } catch (UnsupportedEncodingException e) {
  23. e.printStackTrace();
  24. return "";
  25. }
  26. })
  27. .collect(Collectors.toList());
  28. }

❶ 匿名内部类实现

❷ 包含将抛出受检异常的代码

❸ lambda 表达式实现

由于 apply 方法(Function 接口包含的单一抽象方法)并未声明任何受检异常,必须在任何实现该方法的 lambda 表达式中添加一个 try/catch 代码块。如果使用 lambda 表达式(如本例所示),我们甚至无法看到 apply 方法签名,遑论对其进行修改(程序也不允许)。

例 5-38 显示了如何利用提取的方法进行编码。

例 5-38 委托给 encodeString 方法进行 URL 编码

  1. private String encodeString(String s) {
  2. try {
  3. return URLEncoder.encode(s, "UTF-8");
  4. } catch (UnsupportedEncodingException e) {
  5. throw new RuntimeException(e);
  6. }
  7. }
  8.  
  9. public List<String> encodeValuesUsingMethod(String... values) {
  10. return Arrays.stream(values)
  11. .map(this::encodeString)
  12. .collect(Collectors.toList());
  13. }

❶ 用于异常处理的提取方法

❷ 提取方法的方法引用

上述代码有效且易于实现,并提供了一种可以分别进行测试和调试的方法。唯一的不足之处在于,我们需要为每个可能抛出异常的操作提取一个方法。但前面的范例曾经提到过,这使得对流处理的各个组件进行测试更加容易。

另见

有关利用提取的方法处理 lambda 表达式中的异常请参见范例 5.9,有关利用泛型包装器处理异常的讨论请参见范例 5.11。