java8新特性(五):Stream数据收集

收集器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
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接口,提供对应的实现方法就好了。











    原文作者:剑侠情猿
    原文地址: https://blog.csdn.net/sunjin9418/article/details/53118949
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞