上篇文章在介绍Stream中的相关API时,有两个collect方法没有讲解到,是因为与它们相关的Collector用处很多并且相对于其他的stream操作比较复杂,所以单独来介绍。其实除了Collector,还有Collectors,我们会放到一起来看。
我们先来看一下Collector的使用,看看我们能用它实现什么。
Collector的定义
我们先来看之前没有讲到的Stream中的collect方法,Stream中是有两个collect方法的,我们重点来看下面这个:
<R, A> R collect(Collector<? super T, A, R> collector);
它接收的是一个Collector接口类型的参数,与Stream中很多其他方法的参数不同,Collector不是一个函数式接口,但是它内部却有几个函数式,这几个函数式接口以方法形式存在,这几个函数式接口在Collector中有不同的作用,我们这里来看下这几个返回函数式接口的方法:
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
这几个函数式接口我们应该已经很熟悉了,Stream中的很多方法会用到它们,例如BinaryOperator就在reduce方法中用到了:
T reduce(T identity, BinaryOperator<T> accumulator);
在Collector中,这几个函数式接口是有依赖关系的,这里我们就先大致知道Collector是有这四个函数式接口,对这几个函数式接口提供不同的实现就能实现不同的功能,至于这几个接口执行的顺序和依赖关系,我们在看完Collector的使用后再来研究。
将Stream转换成其他集合
转成List
我们可以使用Collector将Stream转换为其他集合,例如List,看下面的例子:
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
List<String> strings = stringStream.collect(Collectors.toList());
System.out.println(strings);
}
输出:
这样就将Stream
我们这篇文章涉及到的很多Collector都是Collectors提供的。
转成Set
除了转换成List,还能转成Set,只需要使用toSet方法即可:
Stream<String> stringStream = Stream.of("one","two","three","four","five");
Set<String> strings = stringStream.collect(Collectors.toSet());
转成指定集合类型
toList和toSet方法都不需要我们指定任何参数,就能转成我们想要的集合。如果我们想要指定集合类型,
可以使用toCollection方法:
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
HashSet<String> collect = stringStream.collect(Collectors.toCollection(HashSet::new));
}
将元素分组
默认分组
我们可以使用groupby方法对Stream中的元素进行分类:
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
Map<Integer, List<String>> collect = stringStream.collect(Collectors.groupingBy(s -> s.length()));
System.out.println(JSON.toJSONString(collect));
}
输出:
它的意思是按照流中的元素,这里是String,的长度进行分类。
它会生成一个Map<>类型,Map的key的类型是我们参数中给定的,这里是s.length(),即Integer,map的value的类型是List。
指定分组集合和分组元素
上面的默认分类会根据我们的分类依据(例如字符串的长度)将流中的元素分类为一个一个的list。还看上面的例子,如果我们想分类之后不是一个一个的list,而是其他集合,例如set,还想每个集合里面保存的不是string,而是其他类型,这个其他类型我们可以通过string获取,可以使用另外一个groupingBy方法:
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
Map<Integer, Set<Integer>> result = stringStream.collect(Collectors.groupingBy(s -> s.length(), Collectors.mapping(s -> s.indexOf("o"), Collectors.toSet())));
System.out.println(JSON.toJSONString(result));
}
在这个例子里,我们还是根据string的长度对Stream中的元素进行分类,只不过分类后的集合不再是默认的list,而是set,并且集合中的元素不是String了,而是通过string.indexOf方法得到的int。
这个用到了Collectors.mapping,它也会产生一个Collector,这里groupBy使用它作为下游的collector,我们稍后会详细讲解它。
将元素分块
默认分块
分块与分组类似,但是也有不同。分块是将元素分为两部分,按照一个返回值为true和false的条件来进行,而分组是将元素按照某个条件(不一定是boolean)分为多组。
partitioningBy方法可以完成分组。
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
Map<Boolean, List<String>> result = stringStream.collect(Collectors.partitioningBy(s -> s.length() > 3));
System.out.println(JSON.toJSONString(result));
}
在这个示例中,我们将字符串Stream按照元素的长度是否大于3进行分类。输出:
这样返回的Map的key是Boolean类型,value是List类型。默认情况下,分块会将每块的元素放到一个List中。
指定分块集合
与groupingBy一样,我们也可以通过一个下游的Collector指定分块元素的集合类型,另外一个partitioningBy方法可以达到这个目的:
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
Map<Boolean, Set<String>> result = stringStream.collect(Collectors.partitioningBy(s -> s.length() > 3,Collectors.toSet()));
System.out.println(JSON.toJSONString(result));
}
对字符串的处理
直接拼接
Collectors提供了joining方法来处理字符串,主要是对字符串进行拼接操作,我们来看一个最简单的:
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
String result = stringStream.collect(Collectors.joining());
System.out.println(JSON.toJSONString(result));
}
增加分隔符
我们还可以在拼接时增加一个分隔符,用到的是一个参数的joining方法:
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
String result = stringStream.collect(Collectors.joining("-"));
System.out.println(JSON.toJSONString(result));
}
增加前缀和后缀
最后一个joining方法可以指定拼接时的前缀和后缀:
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
String result = stringStream.collect(Collectors.joining("-","[","]"));
System.out.println(JSON.toJSONString(result));
}
对数字的处理
对数字的处理,Collectors提供了求和、求平均值等方法,并且对于int、long、double都有对应的函数。我们这里主要看int类型的。
求和
summingInt方法可以用来对数据进行求和:
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
Integer result = stringStream.collect(Collectors.summingInt(s -> s.length()));
System.out.println(result);
}
它接收一个
ToIntFunction<? super T> mapper
求平均值
averagingInt可以用来求平均值,用法与summingInt一致。
public static void main(String[] args) {
Stream<String> stringStream = Stream.of("one","two","three","four","five");
Double aDouble = stringStream.collect(Collectors.averagingInt(s -> s.length()));
System.out.println(aDouble);
}