Collection
Collection是Java集合的祖先接口。
Collections
Collections是java.util包下的一个工具类,内涵各种处理集合的静态方法。
collect
java.util.stream.Stream#collect(java.util.stream.Collector<? super T,A,R>)是Stream的一个函数,负责收集流。
Collector
java.util.stream.Collector 是一个收集函数的接口, 声明了一个收集器的功能。
Collectos
java.util.Comparators则是一个收集器的工具类,内置了一系列收集器实现。
e.g
. filter
, map
)和终端操作(如count
, findFirst
, forEach
, reduce
). 中间操作可以连接起来,将一个流转换为另一个流。这些操作不会消耗流,其目的是建立一个流水线。与此相反,终端操作会消耗类,产生一个最终结果。collect
就是一个归约操作,就像reduce
一样可以接受各种做法作为参数,将流中的元素累积成一个汇总结果。具体的做法是通过定义新的Collector
接口来定义的。下面简单演示基本的内置收集器。模拟数据源如下:
final ArrayList<Dish> dishes = wArrayList(new Dish("pork", false, 800, Type.MEAT),new Dish("beef", false, 700, Type.MEAT),new Dish("chicken", false, 400, Type.MEAT),new Dish("french fries", true, 530, Type.OTHER),new Dish("rice", true, 350, Type.OTHER),new Dish("season fruit", true, 120, Type.OTHER),new Dish("pizza", true, 550, Type.OTHER),new Dish("prawns", false, 300, Type.FISH),new Dish("salmon", false, 450, Type.FISH)
);
// 为啥返回Optional? 如果stream为null怎么办, 这时候Optinal就很有意义了
Optional<Dish> mostCalorieDish = dishes.stream().max(ComparatorparingInt(Dish::getCalories));
Optional<Dish> minCalorieDish = dishes.stream().min(ComparatorparingInt(Dish::getCalories));
Double avgCalories = dishes.stream().collect(Collectors.averagingInt(Dish::getCalories));
IntSummaryStatistics summaryStatistics = dishes.stream().collect(Collectors.summarizingInt(Dish::getCalories));
double average = Average();
long count = Count();
int max = Max();
int min = Min();
long sum = Sum();
这几个简单的统计指标都有Collectors内置的收集器函数,尤其是针对数字类型拆箱函数,将会比直接操作包装类型开销小很多。
想要把Stream的元素拼起来?
//直接连接
String join1 = dishes.stream().map(Dish::getName).collect(Collectors.joining());
//逗号
String join2 = dishes.stream().map(Dish::getName).collect(Collectors.joining(", "));
List<String> names = dishes.stream().map(Dish::getName).collect(toList());将原来的Stream映射为一个单元素流,然后收集为List。
Set<Type> types = dishes.stream().map(Dish::getType).Set());将Type收集为一个set,可以去重复。
Map<Type, Dish> byType = dishes.stream().collect(toMap(Dish::getType, d -> d));
有时候可能需要将一个数组转为map,做缓存,方便多次计算获取。toMap提供的方法k和v的生成函数。(注意,上述demo是一个坑,不可以这样用!!!, 请使用toMap(Function, Function, BinaryOperator))
上面几个几乎是最常用的收集器了,也基本够用了。但作为初学者来说,理解需要时间。想要真正明白为什么这样可以做到收集,就必须查看内部实现,可以看到,这几个收集器都是基于java.util.stream.Collectors.CollectorImpl,也就是开头提到过了Collector的一个实现类。后面自定义收集器会学习具体用法。
Integer totalCalories = dishes.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));
//使用内置函数代替箭头函数
Integer totalCalories2 = dishes.stream().collect(reducing(0, Dish::getCalories, Intege
Optional<Integer> totalCalories3 = dishes.stream().map(Dish::getCalories).reduce(Integer::sum);
int sum = dishes.stream().mapToInt(Dish::getCalories).sum();
上面的demo说明,函数式编程通常提供了多种方法来执行同一个操作,使用收集器collect比直接使用stream的api用起来更加复杂,好处是collect能提供更高水平的抽象和概括,也更容易重用和自定义。
我们的建议是,尽可能为手头的问题探索不同的解决方案,始终选择最专业的一个,无论从可读性还是性能来看,这一般都是最好的决定。
Optional<Dish> mostCalorieDish = dishes.stream().collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2))
public static <T, U>Collector<T, ?, U> reducing(U identity,Function<? super T, ? extends U> mapper,BinaryOperator<U> op)
首先看到3个泛型
关于参数:
identity是返回值类型的初始值,可以理解为累加器的起点。
mapper则是map的作用,意义在于将Stream流转换成你想要的类型流。
op则是核心函数,作用是如何处理两个变量。其中,第一个变量是累积值,可以理解为sum,第二个变量则是下一个要计算的元素。从而实现了累加。
reducing还有一个重载的方法,可以省略第一个参数,意义在于把Stream里的第一个参数当做初始值
public static <T> Collector<T, ?, Optional<T>>reducing(BinaryOperator<T> op)
先看返回值的区别,T表示输入值和返回值类型,即输入值类型和输出值类型相同。还有不同的就是Optional了。这是因为没有初始值,而第一个参数有可能是null,当Stream的元素是null的时候,返回Optional就很意义了。
再看参数列表,只剩下BinaryOperator。BinaryOperator是一个三元组函数接口,目标是将两个同类型参数做计算后返回同类型的值。可以按照1>2?
1:2来理解,即求两个数的最大值。求最大值是比较好理解的一种说法,你可以自定义lambda表达式来选择返回值。那么,在这里,就是接收两个Stream的元素类型T,返回T类型的返回值。用sum累加来理解也可以。
上述的demo中发现reduce和collect的作用几乎一样,都是返回一个最终的结果,比如,我们可以使用reduce实现toList效果:
//手动实现toListCollector --- 滥用reduce, 不可变的规约---不可以并行
List<Integer> calories = dishes.stream().map(Dish::getCalories).reduce(new ArrayList<Integer>(),(List<Integer> l, Integer e) -> {l.add(e);return l;},(List<Integer> l1, List<Integer> l2) -> {l1.addAll(l2);return l1;});
关于上述做法解释一下。
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);
U是返回值类型,这里就是List
BiFunction<U, ? super T, U>
accumulator是是累加器,目标在于累加值和单个元素的计算规则。这里就是List和元素做运算,最终返回List。即,添加一个元素到list。BinaryOperator< U > combiner是组合器,目标在于把两个返回值类型的变量合并成一个。这里就是两个list合并。
这个解决方案有两个问题:一个是语义问题,一个是实际问题。语义问题在于,reduce方法旨在把两个值结合起来生成一个新值,它是一个不可变归约。相反,collect方法的设计就是要改变容器,从而累积要输出的结果。这意味着,上面的代码片段是在滥用reduce方法,因为它在原地改变了作为累加器的List。错误的语义来使用reduce方法还会造成一个实际问题:这个归约不能并行工作,因为由多个线程并发修改同一个数据结构可能会破坏List本身。在这种情况下,如果你想要线程安全,就需要每次分配一个新的List,而对象分配又会影响性能。这就是collect适合表达可变容器上的归约的原因,更关键的是它适合并行操作。总结:reduce适合不可变容器归约,collect适合可变容器归约。collect适合并行。
数据库中经常遇到分组求和的需求,提供了group by原语。在Java里, 如果按照指令式风格(手动写循环)的方式,将会非常繁琐,容易出错。而Java8则提供了函数式解法。
比如,将dish按照type分组。和前面的toMap类似,但分组的value却不是一个dish,而是一个List。
Map<Type, List<Dish>> dishesByType = dishes.stream().collect(groupingBy(Dish::getType))
.htm
本文发布于:2024-02-02 00:15:58,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170680982240118.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |