收集器Collector
collect方法接受的参数 函数称为 收集器,也就是实现数据收集的策略。 一般来说,收集器collector会对元素应用一个转换函数,并将结果累积在一个数据结构中,从而产生最终输出。
假设一个需求要将交易数据根据货币类型分组
java7 之前需要这么写 List<Transaction> transactions =
new ArrayList<>();
Map<Currency,List<Transaction>> resultMap =
new HashMap<>();
for(Transaction transaction : transactions){
Currency currency = transaction.getCurrency();
List<Transaction> subList = resultMap.get(currency);
if(subList ==
null){
subList =
new ArrayList<>();
resultMap.put(currency, subList);
}
subList.add(transaction);
}
在java8 中 只需要这么写
Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream().collect(groupingBy(Transaction::getCurrency));
groupingBy就是一个生成收集器的工厂方法
java8中预定义了很多收集器:
求最大值
Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);
Optional<Dish> mostCalorieDish = menu.stream()
.collect(maxBy(dishCaloriesComparator));
求和
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));
平均值
double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories));
统计全部参数: 总数、最大值、最小值、平均值
IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}
字符串拼接
String shortMenu = menu.stream().map(Dish::getName).collect(joining(“, “));
pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon
求和还可以通过reduce 方法获得
int totalCalories = menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();
int totalCalories = menu.stream().mapToInt(Dish::getCalories).sum();
分组
Map<Dish.Type, List<Dish>> dishesByType = menu.stream().collect(groupingBy(Dish::getType));
{FISH=[prawns, salmon], OTHER=[french fries, rice, season fruit, pizza], MEAT=[pork, beef, chicken]}
多级分组 Map<String, Map<String, List<Dish>>> multiLevelDishes = menu.stream().collect(
groupingBy(Dish::getType,
groupingBy(dish -> {
if(dish.getColories() <=
400)
return
“DIET”;
else if(dish.getColories() <=
700)
return
“NORMAL”;
else
return
“FAT”;
}))); 结果是一个两级的Map:
{MEAT={DIET=[chicken], NORMAL=[beef], FAT=[pork]}, FISH={DIET=[prawns], NORMAL=[salmon]},
OTHER={DIET=[rice, seasonal fruit], NORMAL=[french fries, pizza]}}
分组统计
Map<Dish.Type, Long> typesCount = menu.stream().collect( groupingBy(Dish::getType, counting()));
{MEAT=3, FISH=2, OTHER=4}
分组求最大值
Map<Dish.Type, Optional<Dish>> mostCaloricByType = menu.stream()
.collect(groupingBy(Dish::getType, maxBy(comparingInt(Dish::getCalories))));
{FISH=Optional[salmon], OTHER=Optional[pizza], MEAT=Optional[pork]}
分组求和
Map<Dish.Type, Integer> totalCaloriesByType = menu.stream().collect(groupingBy(Dish::getType,
summingInt(Dish::getCalories)));
分区是 分组的一个特殊类型。
Map<Boolean, List<Dish>> partitionMenu = menu.stream().collect(partitioningBy(Dish::isVegetarian)); 结果:
{false=[pork, beef, chicken, prawns, salmon], true=[french fries, rice, season fruit, pizza]}
假设一个需求要将质数和非质数分区
要实现这个需求首先知道质数的定义:
质数定义为只能被1或者它本身整除的自然数。
我们写一个方法,它接收一个参数int n, 并且将前n个自然数分为质数和非质数。
首先要写一个测试摸个数是否质数的方法:
//除了1和n本身不能被其他自然数整除,那么n就是一个质数
public boolean isPrime(
int n){
return IntStream.
range(
2, n).noneMatch(i -> n % i ==
0);
}
这个方法还可以优化
//如果n不能被2到它的平方根之间的自然数整除,那么n也不能被大于它的平方根在自然数整除。
public boolean isPrime(
int n){
int root = (
int) Math.
sqrt((
double) n);
return IntStream.
range(
2, root).noneMatch(i -> n % i ==
0);
}
将质数和非质数分区
public Map<Boolean, List<Integer>> partitionPrimes(
int n){
return IntStream.
range(
2, n).boxed().collect(
partitioningBy(i -> isPrime(i)));
}
收集器类的静态工厂方法:
工厂方法 | 返回类型 | 使用场景 | 举例 |
toList | List<T> | Gather all the stream’s items in a List. | List<Dish> dishes = menuStream.collect(toList()); |
toSet | Set<T> | Gather all the stream’s items in a Set , eliminating duplicates. | Set<Dish> dishes = menuStream.collect(toSet()); |
toCollection | Collection<T> | Gather all the stream’s items in the collection created by the provided supplier. | Collection<Dish> dishes = menuStream.collect(toCollection(), ArrayList::new); |
counting | Long | Count the number of items in the stream. | long howManyDishes = menuStream.collect(counting()); |
summingInt | Integer | Sum the values of an Integer property of the items in the stream. | int totalCalories = menuStream.collect(summingInt(Dish::getCalories)); |
averagingInt | Double | Calculate the average value of an Integer property of the items in the stream. | double avgCalories = menuStream.collect(averagingInt(Dish::getCalories)); |
summarizingInt | IntSummary-Statistics | Collect statistics regarding an Integer property of the items in the stream, such as the maximum, minimum, total, and average. | IntSummaryStatistics menuStatistics = menuStream.collect(summarizingInt(Dish::getCalories)); |
joining | String | Concatenate the strings resulting from the invocation of the toString method on each item of the stream. | String shortMenu = menuStream.map(Dish::getName).collect(joining(“, “)); |
maxBy | Optional<T> | An Optional wrapping the maximal element in this stream according to the given comparator or Optional.empty() if the stream is empty. | Optional<Dish> fattest = menuStream.collect(maxBy(comparingInt(Dish::getCalories))); |
minBy | Optional<T> | An Optional wrapping the minimal element in this stream according to the given comparator or Optional.empty() if the stream is empty. | Optional<Dish> lightest = menuStream.collect(minBy(comparingInt(Dish::getCalories))); |
reducing | The type produced by the reduction operation | Reduce the stream to a single value starting from an initial value used as accumulator and iteratively combining it with each item of the stream using a BinaryOperator. | int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum)); |
collectingAndThen | The type returned by the transforming function | Wrap another collector and apply a transformation function to its result. | int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size)); |
groupingBy | Map<K, List<T>> | Group the items in the stream based on the value of one of their properties and use those values as keys in the resulting Map. | Map<Dish.Type,List<Dish>> dishesByType = menuStream.collect(groupingBy(Dish::getType)); |
partitioningBy | Map<Boolean, List<T>> | Partition the items in the stream based on the result of the application of a predicate to each of them. | Map<Boolean, List<Dish>> vegetarianDishes = menuStream.collect(partitioningBy(Dish::isVegetarian)); |
收集器接口
Collector
collect方法参数类型是Collector接口, <
R,
A>
R collect(Collector<?
super
T,
A,
R> collector);
也就是说所有的收集器都是Collector接口的实现类型。所以我们如果想要开发自己的收集器就需要实现这个接口。
那么就先来看看这个接口有哪些方法。
Supplier<
A> supplier();
这个方法返回一个函数,这个函数用来创建并返回一个可变的容器用来存放收集结果。
BiConsumer<
A,
T> accumulator();
这个函数是累积器
,它将元素添加到
supplier 接口创建的结果容器
BinaryOperator<
A> combiner();
这个方法返回一个函数,这个函数接收两个部分结果集,然后将这两个部分结果集合并成一个结果集。主要在并发计算的时候会用到子集合合并的操作。
Function<
A,
R> finisher();
执行最终的转换,将中间计算累积的结果转换成最终的结果
Set<Characteristics> characteristics();
这个方法返回一个枚举类型
Characteristics
的集合,他定义了收集器的行为。
enum Characteristics {
CONCURRENT, accumulator可以被多个线程同时调用,也就是可以并行执行。但是如果数据源是有序的,那么必须同时标注
UNORDERED才可以并行执行。
UNORDERED,
归约结果不受流中的元素的遍历和累积顺序影响。
IDENTITY_FINISH
这个表明accumulator累积器计算的中间结果类型和最终类型是一致的,finisher完成器中的A和R是相同的类型
,不需要转换。
}
开发自己的收集器
实现collector接口,提供对应的实现方法就好了。