前言
JAVA8是意义深远的一个新版本。随着大数据的兴起,函数式编程在处理大数据上的优势开始体现。JAVA8也紧跟时代,引入了函数式语言的特性
1.收集器简介的作用
优点:更易复合和重用,要是做多级分组指令式和函数式之间的区别就会更加明显。在我们java8之前,由于需要好多层嵌套循环和条件,指令式代码很快就变得更难阅读、更难维护、更难修改。相比之下,函数式版本只要加上一个收集器就可以轻松的实现功能了
收集器非常有用,因为用它可以简洁而灵活的定义collect用来生成集合的标准。更具体的说,对流调用collect方法将对流中的元素触发一个规约操作(由Collector来参数化)
// 这是一个苹果的集合
static List<Apple> newApplyList(){
List<Apple> inventory = new ArrayList<>();
Apple a1 = new Apple();
a1.setWeight(1);
a1.setCountry("中国");
a1.setColor("red");
inventory.add(a1);
Apple a2 = new Apple();
a2.setWeight(4);
a2.setCountry("奥大利亚");
a2.setColor("blue");
inventory.add(a2);
Apple a3 = new Apple();
a3.setWeight(3);
a3.setCountry("美国");
a3.setColor("green");
inventory.add(a3);
Apple a4 = new Apple();
a4.setWeight(1);
a4.setCountry("日本");
a4.setColor("yellor");
inventory.add(a4);
return inventory;
}
例如实现一个将苹果列表分成多组并且相同质量的为一组的map集合(返回一个Map<Integer,List<Apple>>)
Map<Integer, List<Apple>> collect = apples.stream() //先stream将apples转成流
.collect(Collectors.groupingBy(Apple::getWeight)); // groupingBy分组,条件为weight
// 运行结果为:{1=[Apple(weight=1, country=中国, color=red, carlories=0)], 3=[Apple(weight=3, country=中国1, color=green, carlories=0), Apple(weight=3, country=中国1, color=green, carlories=0)], 4=[Apple(weight=4, country=奥大利亚, color=red, carlories=0)]}
以上可见,原来需要一大串for循环与if判断才能实现的数据处理,只需要简短的一行代码便可以简单明了的实现,并且可读性高,也是其一大优点,下面我们来介绍一些日常中常用的一些流收集器中的函数。
首先我们假定已经导入Collectors类的所有静态工厂方法:这样用不着写:
Collectors.groupingBy(Apple::getWeight)之类的了
import static java.util.stream.Collectors.*;
2.查找流中的最大值和最小值
使用Collectors.maxBy和Collectors.minBy,来计算流中的最大值和最小值
例:获取质量最大的苹果
Optional<Apple> collect = apples.stream().collect(maxBy(Comparator.comparingInt(Apple::getWeight)));
System.out.println("质量最重的苹果质量为:" + collect.get().getWeight());
// 运行结果为:质量最重的苹果质量为:4
3.使用流进行数据汇总
Collectors类专门为汇总提供了一个工厂方法:
Collectors.summingInt(Collectors.summingDouble、 Collectors.summingLong)
它可接受一个把对象映射为求和所需int的函数,并返回一个收集器;该收集器在传递给普通collect方法后即执行我们需要的求和操作
例:使用summingInt计算苹果列表的总质量。
int totalWeight = apples.stream().collect(summingInt(Apple::getWeight));
// 运行结果为:11
但汇总不仅仅是求和还有Collectors.averageingInt,连同对应的averageingLong和averageingDouble可以计算数值的平均数
例:使用averageingDouble计算苹果列表的平均质量。
Double aveWeight= apples.stream().collect(averagingDouble(Apple::getWeight));
// 运行结果为:2.75
不过很多时候,你可能想要得到两个或更多这样的结果,而你希望只需要一次操作就可以完成。在这种情况下,你可以使用summarizingInt工厂函数方法返回的收集器
例:通过summarzing操作你就可以数出苹果列表元素的个数,并得到重量的总和、平均值、最大值和最小值。
IntSummaryStatistics intSummaryStatistics = apples.stream().collect(summarizingInt(Apple::getWeight));
// 运行结果为:IntSummaryStatistics{count=4, sum=11, min=1, average=2.750000, max=4}
打印intSummaryStatistics会得到以下输出:
IntSummaryStatistics{count=4, sum=9, min=1, average=2.250000, max=4}
它提供了(getter)方法来访问结果
4.连接字符串
Joining工厂方法返回的收集器会把对流中每一个对象应用toString方法得到的所有字符串连接成一个字符串
例:把苹果列表中所有产地的名称连接起来并用逗号隔开
String collect = apples.stream().map(Apple::getCountry).collect(joining(","));
// 运行结果为:中国,奥大利亚,中国1,中国1
并且在joining工厂方法中,还可以定义第二个和第三个参数。分别为结果的前缀和后缀
String collect = apples.stream().map(Apple::getCountry).collect(joining(",","{","}"));
// 运行结果为:{中国,奥大利亚,中国1,中国1}
5.广义上的规约汇总
事实上,我们前面讨论的所有收集器,都是一个可以用reduceing工厂方法定义的规约过程的特殊情况而已。可以说,先前讨论的案例仅仅是为了方便程序员而已。
例:可以用reduceing方法创建的收集器来计算你苹果列表的总质量
Integer totalWeight = apples.stream().collect(reducing(0, Apple::getWeight, (i, j) -> i + j));
// 运行结果为:11
同样,你可以使用下面这样单参数形式的reduceing来找到质量最大的苹果
Optional<Apple> collect2 = apples.stream().collect(reducing((t, j) -> t.getWeight() > j.getWeight() ? t : j));
// 运行结果为:Optional[Apple(weight=4, country=奥大利亚, color=red, carlories=0)]
你可能想知道Stream接口的collect和reduce有何不同,因为两种方法通常会获得相同的结果。下面我们通过一个例子来说明
例:你可以使用reduce方法来实现toListCollector所做的工作,把流转化成list
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5, 6).stream();
List<Integer> reduce = stream.reduce(new ArrayList<>(), (List<Integer> l, Integer e) -> {
l.add(e);
return l;
}, (List<Integer> l1, List<Integer> l2) -> {
l1.addAll(l2);
return l1;
});
这解决方案有两个问题一个语义问题和一个实际问题。语义问题在于,reduce方法旨在把两个值结合起来生成一个新值,它是一个不可变规约,与此相反,collect方法的设计就是要改变容器,从而累计要输出的结果,虽然以上代码实现了流至集合的转化,但你会发现,上面的代码片段是在滥用reduce方法,因为它在原地改变了作为累加器的List。以错误的语义使用reduce方法,还会造成一个实际问题:这个规约过程不能并行工作,因为由多个线程并发修改同一个数据结构,可能会破坏List本身。在这种情况下,如果你想要线程安全,就需要每次分配一个新的Lsit,而对象分配又会影响性能。这就是collect方法特别适合表达可变容器上规约的原因,更关键的是它适合并行操作。