学习Java函数式编程(三):收集器入门

本篇将入门Java函数式编程中的收集器。

收集器相关操作

之前提到过及早求值方法和惰性求值法,而它们的区别通常就和收集器的存在有关,简单的说,收集器可以从流生成复杂值。

在开始收集器各操作之前,先构造一个用于测试的实例,在该实例中,最重要的就是创建了一个HashMap对象,该对象放入了5组键值对,后续很多用收集器操作的数据都来自于此:

1
2
3
4
5
6
Map<String, String> nameAndArt = new HashMap<>();
nameAndArt.put("cg01", "010101");
nameAndArt.put("cg001", "001001001");
nameAndArt.put("cg002", "002002002");
nameAndArt.put("cg003", "003003003");
nameAndArt.put(null, "nullnullnull");

转换成其它集合

首先HashMap对象不能直接流化,nameAndArt.stream()报错。要想使nameAndArt中的成员对应单个流元素,可以采用entrySet():

1
System.out.println(nameAndArt.entrySet().stream().collect(Collectors.toSet()));

运行结果:

1
2
3
[cg003=003003003, null=nullnullnull, cg002=002002002, cg01=010101, cg001=001001001]
Process finished with exit code 0

在具体的实操中,要注意区分Stream.ofstream()的区别,看下面的代码:

1
2
3
4
5
6
7
8
nameAndArt02.putAll(nameAndArt);
List l = new ArrayList();
l.add(nameAndArt);
l.add(nameAndArt02);
System.out.println(l.stream().collect(Collectors.toList()));
System.out.println(Stream.of(nameAndArt, nameAndArt02).collect(Collectors.toList()));

这段代码打印出的结果是:

1
2
[{null=nullnullnull, cg01=010101, cg002=002002002, cg003=003003003, cg001=001001001}, {null=nullnullnull, cg01=010101, cg002=002002002, cg003=003003003, cg001=001001001}]
[{null=nullnullnull, cg01=010101, cg002=002002002, cg003=003003003, cg001=001001001}, {null=nullnullnull, cg01=010101, cg002=002002002, cg003=003003003, cg001=001001001}]

结果上它们完全相同,但是第二个打印语句中可以调用HashMap的方法:

1
System.out.println(Stream.of(nameAndArt, nameAndArt02).map(x ->x.entrySet()).collect(Collectors.toList()));

第一句不能完成x.entrySet()的调用,但是第一句可以通过filter方法进行条件过滤,所以每个HashMap对象仍对应一个流元素。

转换成List的方法就是将toSet改成toList,如果要指定转换的具体类型,可以使用下面这种方式:

1
2
3
4
5
List list = nameAndArt.entrySet()
.stream()
.collect(Collectors.toCollection(ArrayList::new));
System.out.println(list);

需要转换成什么类型就将toCollection中的参数进行指定即可。

Lambda表达式可以赋给Function接口,然后再作为参数进行传递。比如:

1
Function<HashMap.Entry<String, String>, Integer> re = x -> x.getValue().toString().length();

接下来就以这个函数为比较逻辑,求出一个minBy:

1
2
3
4
5
Optional<Map.Entry<String, String>> min = nameAndArt.entrySet()
.stream()
.collect(Collectors.minBy(Comparator.comparing(re)));
System.out.println(min);

最后输出为:

1
2
3
Optional[cg01=010101]
Process finished with exit code 0

数据分类

这里的分类,包含分块和分组。所谓分块,就是将流分解成两个集合。通过收集器返回的是一个Map,键为true和false,直接看代码就明白了:

1
2
3
4
5
System.out.println(nameAndArt.entrySet().stream()
.collect(Collectors.partitioningBy(x -> x.getKey() == null)));
System.out.println((nameAndArt.entrySet().stream()
.collect(Collectors.partitioningBy(x -> x.getKey() == null)) instanceof Map));

输出:

1
2
{false=[cg01=010101, cg002=002002002, cg003=003003003, cg001=001001001], true=[null=nullnullnull]}
true

所谓分组,比前面提到的分块更自由和灵活,生成的Map对象的键不局限于true和false,下面用代码说明:

1
2
System.out.println(nameAndArt.entrySet().stream()
.collect(Collectors.groupingBy(x -> x.getValue())));

输出:

1
{010101=[cg01=010101], 001001001=[cg001=001001001], nullnullnull=[null=nullnullnull], 002002002=[cg002=002002002], 003003003=[cg003=003003003]}

仍然是返回一个Map对象,但这里被要求以x.getValue()为键进行分组。

Collectors的功能很强大,还提供joining方法,处理字符串流,也举一个简单的例子说明:

1
2
System.out.println(Stream.of("asdad", "safaf", "adasd")
.collect(Collectors.joining("*", "%", "%")));

输出:

1
%asdad*safaf*adasd%

"*", "%", "%"分别代表分隔符,前缀和后缀。

组合收集器

之前分组的例子中,以值为键并以键值对为值创建Map对象打印输出,能否在原有代码基础上,将输出的格式改变为以值为键以键作为组成列表的元素的形式呢?

采用组合收集器的方式,代码:

1
2
System.out.println(nameAndArt.entrySet().stream()
.collect(Collectors.groupingBy(x -> x.getValue(), Collectors.mapping(x -> x.getKey(), Collectors.toList()))));

groupingBy函数内嵌入mapping函数,mapping内嵌入toList函数,这种组合的方式相较于传统的循环语句,逻辑更加清晰。

输出为:

1
{010101=[cg01], 001001001=[cg001], nullnullnull=[null], 002002002=[cg002], 003003003=[cg003]}

小结

本篇主要专注于收集器,罗列了它的一些主要的操作,也了解了点这方面的编程细节。

参考

《Java 8函数式编程》