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
- 例如前面那个遍历并打印每一个 word 的例子可以写成这样:
- 某个类的实例方法
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)