作者 | 田伟然
回首向来萧瑟处,归去,也无风雨也无晴。
杏仁工程师,关注编码和诗词。
何方神圣?
众所周知, Java8 在一定程度上支持了函数式编程,但标准库提供的函数式 API 不是很完备和友好。
为了更好的进行函数式编程,我们就不得不借助于第三方库,而 VAVR 就是这方面的佼佼者,它可以有效减少代码量并提高代码质量。
VAVR 可不是默默无闻之辈,它的前身是发布于 2014 年的 Javaslang,目前在 github 上有着近 4k 的 star。
看到这儿,很多人就说我标题党了,一个 Java 库还来颠覆 Java ?
这可不不是我玩震惊体,打开 VAVR 的官网 ,它的首页就用加粗字体写着 「vavr - turns java™ upside down」
这翻译过来不就是颠覆 Java 吗?
食用指南
阅读本文需要读者对 Java8 的 lambda 语法和常用 API 有一定的了解。
由于是一篇框架的介绍文(地推 ing),为了避免写成官方文档的翻译,本文会有一些约束
不会穷尽所有特性和 API,仅做抛砖引玉
不会深入到源码细节
关于示例代码,基本会以单元测试的形式给出并保证运行通过
注:本文使用的 VAVR 版本为 0.10.3,JDK 版本为 11。
先来个概览
集合,全新的开始
不得不说 Java8 的集合库引入 Stream 以后确实很好用,但也正是因为使用了 Stream,不得不写很多样板代码,反而降低了不少体验。
// of 方法是 Java9 开始提供的静态工厂
java.util.List.of(1, 2, 3, 4, 5)
.stream()
.filter(i -> i > 3)
.map(i -> i * 2)
.collect(Collectors.toList());
而且 Java 的集合库本身是可变的,显然违背了函数式编程的基本特性 - 不可变,为此 VAVR 设计了一套全新的集合库,使用体验无限接近于 Scala。
更简洁的 API
io.vavr.collection.List.of(1, 2, 3, 4, 5)
.filter(i -> i > 3)
.map(i -> i * 2);
往集合追加数据会产生新的集合,从而保证不可变
var list = io.vavr.collection.List.of(1, 2)
var list2 = list
.append(List.of(3, 4))
.append(List.of(5, 6))
.append(7);
// list = [1, 2]
// list2 = [1, 2, 3, 4, 5, 6]
强大的兼容性,可以非常方便的与 Java 标准集合库进行转换
var javaList = java.util.List.of(1, 2, 3);
java.util.List<Integer> javaList2 = io.vavr.collection.List.ofAll(javaList)
.filter(i -> i > 1)
.map(i -> i * 2)
.toJavaList();
再来看一个稍微复杂一点的例子:过滤一批用户中已成年的数据,按照年龄对其分组,每个分组只展示用户的姓名。
/**
* 用户信息
*/
@Data
class User {
private Long id;
private String name;
private Integer age;
}
先用 Java 标准集合库来实现这个需求,可以看见 collect(...) 这一长串嵌套是真的很难受
public Map<Integer, List<String>> userStatistic(List<User> users) {
return users.stream()
.filter(u -> u.getAge() >= 18)
.collect(Collectors.groupingBy(User::getAge, Collectors.mapping(User::getName, Collectors.toList())));
}
再来看看 VAVR 的实现,是不是更简洁,更直观?
public Map<Integer, List<String>> userStatistic(List<User> users) {
return users.filter(u -> u.getAge() >= 18)
.groupBy(User::getAge)
.mapValues(usersGroup -> usersGroup.map(User::getName));
}
VAVR 的集合库提供了更多 Functional 的 API,比如
take(Integer) 取前 n 个值
tail() 取除了头结点外的集合
zipWithIndex() 使得便利时可以拿到索引(不用 fori)
find(Predicate) 基于条件查询值,在 Java 标准库得使用 filter + findFirst 才能实现
.....
虽然代码实例都是用的 List,但是以上特性在 Queue、Set、Map 都可以使用,都支持与 Java 标准库的转换。
元组,Java 缺失的结构
熟悉 Haskell、Scala 的同学肯定对「元组」这个数据结构不陌生。
元组类似一个数组,可以存放不同类型的对象并维持其类型信息,这样在取值时就不用 cast 了。
// scala 的元组,用括号构建
val tup = (1, "ok", true)
// 按索引取值,执行对应类型的操作
val sum = tup._1 + 2 // int 加法
val world = "hello "+tup._2 // 字符串拼接
val res = !tup._3 // 布尔取反
当然,Java 并没有原生的语法支持创建元组,标准库也没有元组相关的类。
不过,VAVR 通过泛型实现了元组,通过 Tuple 的静态工厂,我们可以非常轻易的创建元组( 配合 Java10 的 var 语法简直不要太美好)
import io.vavr.Tuple;
public TupleTest {
@Test
public void testTuple() {
// 一元组
var oneTuple = Tuple.of("string");
String oneTuple_1 = oneTuple._1;
// 二元组
var twoTuple = Tuple.of("string", 1);
String twoTuple_1 = twoTuple._1;
Integer twoTuple_2 = twoTuple._2;
// 五元组
var threeTuple = Tuple.of("string", 2, 1.2F, 2.4D, 'c');
String threeTuple_1 = threeTu