相关链接:
java8函数式编程(一)
java8函数式编程(二)
java8函数式编程(三)
其中(一)是在没有开始了解java8新特性之前实际遇到的一个问题,使用for循环实现的代码很繁琐,于是尝试使用新的编码风格,最后很不完美的实现了需求。在进一步学习后,在(二)中完美实现,并对List常用的操作做了一个总结。最后在(三)中对并行化流与并行化数组的操作简单归纳。
本节主要是对java8流处理一个总结以及一些细节的补充。另外,学习资料主要参考《Java8实战》与《Java8函数式编程》两本书籍。
Stream API
优点:
1 声明性 – 简洁、易读
2 可复合 – 灵活组合
3 可并行 – 高性能
常用操作:
1 筛选
filter 过滤
distinct 去重
limit 截断
skip 跳过
2 映射
map 转换
flatMap 扁平化转换
3 查找与匹配
anyMatch 至少有一个满足
allMatch 所有都满足
noneMatch 没有一个满足
findAny 返回任意一个
findFrist 返回第一个
4 归约
reduce 计算
count 元素个数
5 数值流
mapToInt IntStream
mapToDouble DoubleStream
max 最大
min 最小
average 平均
boxed 数值流装箱
range 范围内(不包括end)
reageClosed 范围内(包括end)
6 构建流
of
iterate
generate
7 收集器
collect
数值操作
从1到20间的奇数
IntStream.rangeClosed(1, 20)
.filter(n -> n%2 != 0)
.forEach(System.out::println);
100以内奇数的和
int sum = IntStream.range(1, 100)
.filter(n -> n % 2 != 0)
.sum();
斐波那契数列(前20位)
Stream.iterate(new int[]{0,1}, n -> new int[]{n[1], n[0] + n[1]})
.limit(20)
.map(n -> n[0])
.forEach(System.out::println);
collect收集器
准备数据依然使用List<Frog>,frog的属性有name名称,age年龄,color颜色,size体积。
List<Frog> list = new ArrayList<>();
list.add(new Frog("one", 2 , "red", 5.1));
list.add(new Frog("two", 3 , "red", 5.0));
list.add(new Frog("three", 2 , "blue", 5.5));
list.add(new Frog("four", 1 , "blue", 5.3));
list.add(new Frog("five", 5 , "yellow", 5.1));
list.add(new Frog("six", 3 , "yellow", 5.2));
collect方法参数Collector。Collector接口中方法实现对流执行的操作,Collectors实现类提供很多静态方法,下面是对这些静态方法的说明。
为了代码简洁,导入Collectors类中所有方法:
import static java.util.stream.Collectors.*;
summaryStatistics
该方法可以一次性获取到数量、总和、最大值、最小值和平均数
DoubleSummaryStatistics summaryStatistics = frogs.stream()
.collect(summarizingDouble(Frog::getSize));
System.out.println(summaryStatistics);
打印结果
DoubleSummaryStatistics{count=6, sum=31.200000, min=5.000000, average=5.200000, max=5.500000}
也可以直接通过summaryStatistics 的getter方法获取某一项。
joining
字符串的连接,方法重载
String names = frogs.stream()
.map(Frog::getName)
.collect(joining(" ", "[", "]"));
打印结果
[one two three four five six]
groupingBy
1、分组与多级分组
Map<String, List<Frog>> map = frogs.stream()
.collect(groupingBy(Frog::getColor));
Map<String, Map<Volume, List<Frog>>> map1 = frogs.stream()
.collect(groupingBy(Frog::getColor,
groupingBy(frog -> {
if (frog.getSize() < 5.2) {
return Volume.SMALL;
} else {
return Volume.BIG;
}
})));
public enum Volume {
BIG,
SMALL
}
打印结果
{
red=[Frog{name='one', age=2, color='red', size=5.1}, Frog{name='two', age=3, color='red', size=5.0}],
blue=[Frog{name='three', age=2, color='blue', size=5.5}, Frog{name='four', age=1, color='blue', size=5.3}],
yellow=[Frog{name='five', age=5, color='yellow', size=5.1}, Frog{name='six', age=3, color='yellow', size=5.2}]
}
{
red={
SMALL=[Frog{name='one', age=2, color='red', size=5.1}, Frog{name='two', age=3, color='red', size=5.0}]
},
blue={
BIG=[Frog{name='three', age=2, color='blue', size=5.5}, Frog{name='four', age=1, color='blue', size=5.3}]
},
yellow={
BIG=[Frog{name='six', age=3, color='yellow', size=5.2}],
SMALL=[Frog{name='five', age=5, color='yellow', size=5.1}]
}
}
groupingBy第二个参数可以使用mapping方法。例:
Map<String, HashSet<Volume>> map4 = frogs.stream()
.collect(groupingBy(Frog::getColor,
mapping(frog -> {
if (frog.getSize() < 5.2) {
return Volume.SMALL;
} else {
return Volume.BIG;
}
}, toCollection(HashSet::new))));
2、分组后的数据操作,比如按颜色分组,求每组中size最大的元素
Map<String, Frog> map2 = frogs.stream()
.collect(groupingBy(Frog::getColor,
collectingAndThen(minBy(Comparator.comparingDouble(Frog::getSize)), Optional::get)));
等价的写法
Map<String, Frog> map3 = frogs.stream()
.collect(toMap(Frog::getColor,
Function.identity(),
BinaryOperator.minBy(Comparator.comparingDouble(Frog::getSize))));
Function.identity():返回一个总是返回其输入参数的函数
partitioningBy
分区,分组的特殊情况,将数据分为true和false两组
Map<Boolean, Long> map = frogs.stream()
.collect(partitioningBy(t -> t.getAge() > 1, counting()));
打印结果
{false=1, true=5}
实践
项目中遇到一个两级分组的问题,需要先按名称分组,然后再按标志(0/1)分组,对分组后的数量求和(库里String类型,实际需要按double类型求和)。代码如下:
Map<String, Map<String, Double>> map = list.stream()
.collect(Collectors.groupingBy(Frog::getMc,
Collectors.groupingBy(Frog::getBz,
Collectors.summingDouble(t -> Double.parseDouble(t.getSl())))));
上面写法有一个计算精度问题,于是改成了一下方式:
Map<String, Map<String, BigDecimal>> map = list.stream()
.collect(Collectors.groupingBy(Frog::getMc,
Collectors.groupingBy(Frog::getBz,
Collectors.reducing(new BigDecimal(0),
t -> new BigDecimal(t.getSl()),
(BigDecimal a, BigDecimal b) -> a.add(b)))));
用BigDecimal类型求和后精度是没有问题的,初除此之外还能对结果进行一些处理,比如保留两位小数(四舍五入)
BigDecimal number = new BigDecimal();
number.setScale(2,BigDecimal.ROUND_HALF_UP);
BigDecimal更多用法可以参考:
博主:星朝
链接:BigDecimal的用法详解(保留两位小数,四舍五入,数字格式化,科学计数法转数字,数字里的逗号处理)