<返回更多

浅析Java中函数式编程、匿名函数和泛型

2023-09-10    Java学研大本营
加入收藏

使用函数式编程可以减少代码重复,使代码更易于理解。

JAVA编程语言以其面向对象的特性而闻名,但也因其冗长和繁琐的异常处理机制而而广受批评。当Java语言在1.8版本引入函数式编程能力时,人们并没有马上理解到这如何给程序员提供帮助。

图片

本文给大家讲解一个示例,以说明函数式编程如何提高代码的重用性和可读性。

1 问题

为了实现功能,第三方编写的通信客户端使用了依赖注入和注释。然而,该客户端存在一些问题,例如抛出已检查异常、缺乏日志记录和重试能力。因此,我们需要对该客户端的功能进行封装,以添加重试功能、日志记录和良好的异常处理。

如果没有函数式编程,那么程序需要创建一个外观,将每个客户端功能都包装在委托中,并添加日志记录、异常处理和通信重试的逻辑。客户端共有50个需要封装的函数,这将导致几乎完全相同的50个副本的代码,仅有调用的客户端函数和传递的数据类型有所不同。这样会带来大量的重复代码问题。

为了解决这个问题,要使用函数式编程的方式对该客户端功能进行封装,以实现重试能力、日志记录和良好的异常处理。

2 使用函数式编程解决问题

解决方案是添加1或2个专门用于处理异常、执行日志记录和实现重试循环的方法。但是,它们需要调用通信客户端中的特定函数。

使用函数式编程,方法可以将函数作为其参数接收。该方法不需要在设计时知道哪个函数。Java(因为这与其他编程语言不同)所要求的是函数具有预期的签名。

在我们的情况下,我们需要2种变体:BiFunction签名和BiConsumer签名。区别在于BiFunction返回一个值,而BiConsumer则不返回。

2.1 示例代码:委托

public final ActionResult<List<Order>>
    downloadOrders() {
    return get(
      ".downloadOrders()",
      (
        client,
        apiKey
      ) -> client.downloadOrders(apiKey.toString())
    );
  }

上面的示例没有显示错误处理、日志记录和重试循环。这是由get(String, BiFunction)方法执行的。因此,我们不需要50个重复的错误处理、日志记录和重试循环,而是有50个易于理解的委托。

get(String, BiFunction)方法回调传入的BiFunction,我们在上面看到了这一点:

(    
  client,
  apiKey
) -> client.downloadOrders(apiKey.toString())

我们可能会从JavaScript中认识到这一点。语法是一个BiFunction的lambda表示法:它接受2个参数,可能做一些事情,并返回一些东西。它是一个回调函数,根据需要即时创建,并且因为它没有名称,所以它保持匿名(并由编译器分配标识)。它的参数client和apiKey在方法get(String, BiFunction)内生成,并在运行时传回回调函数中。它看起来像这样:

2.2 示例代码:接受函数作为参数的包装器

private final <T>
    ActionResult<T>
    get(
      final String caller,
      final BiFunction<
        Client,
        ApiKey,
        T
      > callback
    ) {
    final Client client = Client.newInstance(caller);
    final ApiKey key = this.getKey();
    final MutableList<Exception> errors = Lists.mutable.empty();
    boolean mustRetry = true;
    for (
      int retryCount = 0;
      mustRetry && retryCount < MAX_RETRIES;
      retryCount += 1
    ) {
      try {
        return new ActionResult<T>(
          callback.Apply(
            client,
            apiKey
          )
        ).with(errors);
      } catch (Exception ex) {
        mustRetry = mustRetry(ex);
        if (mustRetry) {
          try {
            TimeUnit.SECONDS.wAIt(1 << (retryCount + 1));
          } catch (InterruptedException ie) {
            errors.add(ie);
          }
        } else {
          errors.add(ex);
        }
      }
    }
    return ActionResult.<T>empty()
      .with(errors);
  }

在那段代码中,BiFunction通过声明进行回调:

callback.apply(
  client,
  apiKey
)

请注意,get(String, BiFunction)不知道回调返回的数据类型。在像Java这样的强类型和显式类型编程语言中,通常不可能。直到泛型引入Java编程语言之前,这是不可能的。这就是为什么代码中存在:它是回调返回的数据类型的占位符。

请注意,调用站点也没有指定返回值的数据类型。相反,它由客户端函数的返回值和委托上指定的返回数据类型隐含。如果它们不匹配,编译器将发出警告,保持数据类型良好且检查过。

3 总结

关键词:函数式编程      点击(4)
声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多函数式编程相关>>>