Java 函数式编程

Lambda 表达式是一个带有参数和函数体的匿名函数,它本身是构造了一个继承自某个函数式接口的子类,所以可以用父类指针指向它。在 Java 中,Lambda 表达式就是闭包;闭包一般指存在自由变量的代码块,它与对象类似,都是用来描述一段代码与其环境的关系。事实上,内部类一直都是闭包,而 Java8 中为闭包赋予了更吸引人的语法。一个 Lambda 表达式包括三个部分:一段代码、参数、自由变量的值,这里的“自由”指的是那些不是参数并且没有在代码中定义的变量。Java 中本质上闭包中是采用的值捕获,即不可以在闭包中使用可变对象。但是它实际上是允许捕获事实上不变量,譬如不可变的 ArrayList,只是指针指向不可变罢了。虽然实现用的是值捕获,但效果看起来跟引用捕获一样;就算以后的 Java 扩展到允许通用的(对可变变量的)引用捕获,也不会跟已有的代码发生不兼容。

Java 中最常见的闭包的使用如下所示:

Runnable task = () -> {
    // do something
};
Comparator<String> cmp = (s1, s2) -> {
    return Integer.compare(s1.length(), s2.length());
};

Stream API

java.util.Stream 表示了某一种元素的序列,在这些元素上可以进行各种操作。Stream 操作可以是中间操作,也可以是完结操作。完结操作会返回一个某种类型的值,而中间操作会返回流对象本身,并且你可以通过多次调用同一个流操作方法来将操作结果串起来(就像 StringBuffer 的 append 方法一样————译者注)。Stream 是在一个源的基础上创建出来的,例如 java.util.Collection 中的 list 或者 set(map 不能作为 Stream 的源)。Stream 操作往往可以通过顺序或者并行两种方式来执行。

首先以 String 类型的 List 的形式创建流:

List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");
//直接从数组创建流
int m = Arrays.stream(ints)
              .reduce(Integer.MIN_VALUE, Math::max);Copy to clipboardErrorCopied

方法引用

有时候 Lambda 表达式的代码就只是一个简单的方法调用而已,遇到这种情况,Lambda 表达式还可以进一步简化为方法引用(Method References)。一共有四种形式的方法引用:

  • 静态方法引用
    List<Integer> ints = Arrays.asList(1, 2, 3);
    ints.sort(Integer::compare);Copy to clipboardErrorCopied
    
  • 某个特定对象的实例方法
    • 例如前面那个遍历并打印每一个 word 的例子可以写成这样:
      words.forEach(System.out::println);Copy to clipboardErrorCopied
      
  • 某个类的实例方法
    words.stream().map(word -> word.length()); // lambda
    words.stream().map(String::length); // method referenceCopy to clipboardErrorCopied
    
  • 构造函数引用
    // lambda
    words.stream().map(word -> {
        return new StringBuilder(word);
    });
    // constructor reference
    words.stream().map(StringBuilder::new);
    

Vavr

Java 8 引入了函数式编程范式,思路是:将函数作为其他函数的参数传递,其实在 Java 8 之前,Java 也支持类似的功能,但是需要使用接口实现多态,或者使用匿名类实现。不管是接口还是匿名类,都有很多模板代码,因此 Java 8 引入了 Lambda 表达式,正式支持函数式编程。

比方说,我们要实现一个比较器来比较两个对象的大小,在 Java 8 之前,只能使用下面的代码:

Compartor<Apple> byWeight = new Comparator<Apple>() {
  public int compare(Apple a1, Apple a2) {
    return a1.getWeight().compareTo(a2.getWeight());
  }
}Copy to clipboardErrorCopied

上面的代码使用 Lambda 表达式可以写成下面这样(IDEA 会提示你做代码的简化)

Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());Copy to clipboardErrorCopied

受限于 Java 标准库的通用性要求和二进制文件大小,Java 标准库对函数式编程的 API 支持相对比较有限。函数的声明只提供了 Function 和 BiFunction 两种,流上所支持的操作的数量也较少。基于这些原因,你也许需要 vavr 来帮助你更好得使用 Java 8 进行函数式开发。如下图所示,vavr 提供了不可变的集合框架;更好的函数式编程特性;元组。vavr 是在尝试让 Java 拥有跟 Scala 类似的语法。

扩展支持

<dependency>
  <groupId>io.vavr</groupId>
  <artifactId>vavr-jackson</artifactId>
  <version>0.10.3</version>
</dependency>Copy to clipboardErrorCopied

首先注册 VavrModule 的实例:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new VavrModule());Copy to clipboardErrorCopied

然后就可以进行序列化操作了:

String json = mapper.writeValueAsString(List.of(1));
// = [1]
List<Integer> restored = mapper.readValue(json, new TypeReference<List<Integer>>() {});
// = List(1)