【Java8实战】使用流收集数据

在上一节中,我们了解到终端操作collect方法用于收集流中的元素,并放到不同类型的结果中,比如List、Set或者Map。其实collect方法可以接受各种Collectors接口的静态方法作为参数来实现更为强大的规约操作,比如查找最大值最小值,汇总,分区和分组等等。

准备工作

为了演示Collectors接口中的静态方法使用,这里创建一个Dish类(菜谱类):

public class Dish {    public enum Type {MEAT, FISH, OTHER}?    /** 食物名称 */    private final String name;    /** 是否是素食 */    private final boolean vegetarian;    /** 卡路里 */    private final int calories;    /** 类型:肉,海鲜,其他 */    private final Type type;?    public Dish(String name, boolean vegetarian, int calories, Type type) {        this.name = name;        this.vegetarian = vegetarian;        this.calories = calories;        this.type = type;    }?     @Override    public String toString() {       return this.getName();    }    // get,set略}

然后创建一个List,包含各种食材:

List<Dish> list = Arrays.asList(        new Dish("pork", false, 800, Dish.Type.MEAT),        new Dish("beef", false, 700, Dish.Type.MEAT),        new Dish("chicken", false, 400, Dish.Type.MEAT),        new Dish("french fries", true, 530, Dish.Type.OTHER),        new Dish("rice", true, 350, Dish.Type.OTHER),        new Dish("season fruit", true, 120, Dish.Type.OTHER),        new Dish("pizza", true, 550, Dish.Type.OTHER),        new Dish("prawns", false, 300, Dish.Type.FISH),        new Dish("salmon", false, 450, Dish.Type.FISH) );

在测试类中导入所有Collectors接口的静态方法:

import static java.util.stream.Collectors.*;

规约与汇总

最大最小值

Collectors.maxBy和Collectors.minBy用来计算流中的最大或最小值,比如按卡路里的大小来筛选出卡路里最高的食材:

list.stream()    .collect(maxBy(Comparator.comparingInt(Dish::getCalories)))    .ifPresent(System.out::println);

输出pork。

汇总

Collectors.summingInt可以用于求和,参数类型为int类型。相应的基本类型对应的方法还有Collectors.summingLong和Collectors.summingDouble。比如求所有食材的卡路里:

list.stream().collect(summingInt(Dish::getCalories)); // 4200

Collectors.averagingInt方法用于求平均值,参数类型为int类型。相应的基本类型对应的方法还有Collectors.averagingLong和Collectors.averagingDouble。比如求所有食材的平均卡路里:

list.stream().collect(averagingInt(Dish::getCalories)); // 466.6666666666667

Collectors.summarizingInt方法可以一次性返回元素个数,最大值,最小值,平均值和总和:

IntSummaryStatistics iss = list.stream().collect(summarizingInt(Dish::getCalories));System.out.println(iss); // IntSummaryStatistics{count=9, sum=4200, min=120, average=466.666667, max=800}

同样,相应的summarizingLong和summarizingDouble方法有相关的LongSummaryStatistics和DoubleSummaryStatistics类型,适用于收集的属性是原始类型long或double的情况。

拼接

Collectors.joining方法会把流中每一个对象应用toString方法得到的所有字符串连接成一个字符串。如:

list.stream().map(Dish::getName).collect(joining()); // porkbeefchickenfrench friesriceseason fruitpizzaprawnssalmon

内部拼接采用了StringBuilder。除此之外,也可以指定拼接符:

list.stream().map(Dish::getName).collect(joining(","));// pork,beef,chicken,french fries,rice,season fruit,pizza,prawns,salmon

reducing

Collectors.reducing方法可以实现求和,最大值最小值筛选,拼接等操作。上面介绍的方法在编程上更方便快捷,但reducing的可读性更高,实际使用哪种我觉得还是看个人喜好。举个使用reducing求最大值的例子:

list.stream().collect(reducing(0, Dish::getCalories, Integer::max)); // 800

或者:

list.stream().map(Dish::getCalories).collect(reducing(0, Integer::max)); // 800

分组

分组功能类似于SQL里的group by,可以对流中的元素按照指定分组规则进行分组。

普通分组

Collectors.groupingBy方法可以轻松的完成分组操作。比如现在对List中的食材按照类型进行分组:

Map<Dish.Type, List<Dish>> dishesByType = list.stream().collect(groupingBy(Dish::getType));System.out.println(dishesByType);

输出结果{OTHER=[french fries, rice, season fruit, pizza], FISH=[prawns, salmon], MEAT=[pork, beef, chicken]}。

我们也可以自定义分组规则,比如按照卡路里的高低分为高热量,正常和低热量:

首先定义一个卡路里高低的枚举类型

public enum CaloricLevel { DIET, NORMAL, FAT };

然后编写分组规则:

Map<CaloricLevel, List<Dish>> dishesByCalories = list.stream().collect(        groupingBy(d -> {            if (d.getCalories() <= 400) return CaloricLevel.DIET;            else if (d.getCalories() <= 700) return CaloricLevel.NORMAL;            else return CaloricLevel.FAT;        }));System.out.println(dishesByCalories);

输出结果:{DIET=[chicken, rice, season fruit, prawns], NORMAL=[beef, french fries, pizza, salmon], FAT=[pork]}。

多级分组

Collectors.groupingBy支持嵌套实现多级分组,比如将食材按照类型分类,然后再按照卡路里的高低分类:

 Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishesGroup = list.stream().collect(        groupingBy(Dish::getType, groupingBy(d -> {                    if (d.getCalories() <= 400) return CaloricLevel.DIET;                    else if (d.getCalories() <= 700) return CaloricLevel.NORMAL;                    else return CaloricLevel.FAT;                })        ));System.out.println(dishesGroup);

返回结果是一个二级Map,输出结果{FISH={DIET=[prawns], NORMAL=[salmon]}, OTHER={DIET=[rice, season fruit], NORMAL=[french fries, pizza]}, MEAT={DIET=[chicken], FAT=[pork], NORMAL=[beef]}}。

实际上,第二个参数除了Collectors.groupingBy外,也可以传递其他规约操作,规约的结果类型对应Map里的第二个泛型。举些例子,将食材按照类型分,然后统计各个类型对应的数量:

Map<Dish.Type, Long> dishesCountByType = list.stream().collect(groupingBy(Dish::getType,counting()));System.out.println(dishesCountByType);

因为Collectors.counting方法返回Long类型,所以Map第二个泛型也必须指定为Long。输出结果:{OTHER=4, FISH=2, MEAT=3}。

或者对食材按照类型分,然后选出卡路里最高的食物:

Map<Dish.Type, Optional<Dish>> map = list.stream().collect(groupingBy(        Dish::getType, maxBy(Comparator.comparing(Dish::getCalories))));System.out.println(map);

输出结果:{OTHER=Optional[pizza], MEAT=Optional[pork], FISH=Optional[salmon]}。如果不希望输出结果包含Optional,可以使用Collectors.collectingAndThen方法:

Map<Dish.Type, Dish> map = list.stream().collect(groupingBy(
        Dish::getType, collectingAndThen(maxBy(Comparator.comparing(Dish::getCalories)), Optional::get)
));
System.out.println(map);

输出结果:{OTHER=pizza, FISH=salmon, MEAT=pork}。

常与Collectors.groupingBy组合使用的方法还有Collectors.mapping。Collectors.mapping方法接受两个参数:一个函数对流中的元素做变换,另一个则将变换的结果对象收集起来,比如对食材按照类型分类,然后输出各种类型食材下卡路里等级情况:

Map<Dish.Type, HashSet<CaloricLevel>> map = list.stream().collect(groupingBy(        Dish::getType, mapping(                d -> {                    if (d.getCalories() <= 400) return CaloricLevel.DIET;                    else if (d.getCalories() <= 700) return CaloricLevel.NORMAL;                    else return CaloricLevel.FAT;                }, toCollection(HashSet::new)        )));System.out.println(map);

Collectors.toCollection方法可以方便的构造各种类型的集合。输出结果:{FISH=[DIET, NORMAL], MEAT=[DIET, NORMAL, FAT], OTHER=[DIET, NORMAL]}。

分区

分区类似于分组,只不过分区最多两种结果。Collectors.partitioningBy方法用于分区操作,接收一个Predicate<T>类型的Lambda表达式作为参数。比如将食材按照素食与否分类:

Map<Boolean, List<Dish>> map = list.stream().collect(partitioningBy(Dish::isVegetarian));System.out.println(map);

输出结果:{false=[pork, beef, chicken, prawns, salmon], true=[french fries, rice, season fruit, pizza]}。

Collectors.partitioningBy方法还支持传入分组函数或者其他规约操作,比如将食材按照素食与否分类,然后按照食材类型进行分类:

Map<Boolean, Map<Dish.Type, List<Dish>>> map = list.stream().collect(        partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));System.out.println(map);

输出结果:{false={MEAT=[pork, beef, chicken], FISH=[prawns, salmon]}, true={OTHER=[french fries, rice, season fruit, pizza]}}。

再如将食材按照素食与否分类,然后筛选出各自类型中卡路里含量最低的食材:

Map<Boolean, Dish> map = list.stream().collect(        partitioningBy(Dish::isVegetarian, collectingAndThen(                minBy(Comparator.comparing(Dish::getCalories)), Optional::get        )));System.out.println(map);

输出结果:{false=prawns, true=season fruit}。

原文地址:https://www.cnblogs.com/7788IT/p/11625555.html

时间: 2024-11-09 10:20:36

【Java8实战】使用流收集数据的相关文章

Java 8 (5) Stream 流 - 收集数据

在前面已经使用过collect终端操作了,主要是用来把Stream中的所有元素结合成一个List,在本章中,你会发现collect是一个归约操作,就像reduce一样可以接受各种做法作为参数,将流中的元素累计成一个汇总结果. 看这个例子:按照菜类进行分组 List<Dish> menu = Arrays.asList( new Dish("猪肉炖粉条", false, 800, Type.MEAT), new Dish("小炒牛肉", false, 70

Java8-用流收集数据(1)

在这之前,我们用过了collect终端操作了,当时主要是用来把Stream中所有的元素结合成一个List.在这里,你会发现collect是一个归约操作,就像reduce一样可以接收各种做法作为参数,将流中的累积成汇总结果.具体的做法是通过定义新的Collector接口来定义的,因此区分Collection.Collector和collect是很重要的. 激动吗?很好,我们先来看一个例利用收集器的例子.想象一下,你有一个Transaction的构成的List,并且想按照货币种类进行分组.在没有La

【Java8实战】使用并行流

除了顺序流外,Java 8中也可以对集合对象调用parallelStream方法或者对顺序流调用parallel方法来生成并行流.并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的流.这样在使用流处理数据规模较大的集合对象时可以充分的利用多核CPU来提高处理效率.不过在一些情况下,并行流未必会比顺序流快,甚至会慢很多,所以了解如何高效的使用并行流也至关重要.此外,我们也可以调用流的sequential方法,将并行流转换为顺序流. 测试并行流的性能 举个例子,对1到1000的整

电子书 Java8实战.pdf

本书全面介绍了Java 8 这个里程碑版本的新特性,包括Lambdas.流和函数式编程.有了函数式的编程特性,可以让代码更简洁,同时也能自动化地利用多核硬件.全书分四个部分:基础知识.函数式数据处理.高效Java 8 编程和超YUEJava 8,清晰明了地向读者展现了一幅Java 与时俱进的现代化画卷. 限个人学习使用,不得用于商业用途,请在下载后24小时内删除.备注:资源来自网络,如有不合理可私信我,秒删.电子书 Java8实战.pdf 免费下载https://page55.ctfile.co

下载Java8实战视频教程

1.15套java架构师,高并发,分布式,集群,大型分布式综合项目实战详情:https://my.oschina.net/java168/blog/863547 2.36套精品Java高级课及架构课,亿级流量,P2P金融,第三方支付,设计模式实战,程序调优,系统设计:https://my.oschina.net/java168/blog/1539323 下载Java8实战视频教程

使用对象流将数据以对象形式进行读写

1 import java.io.*; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 7 File f = new File("a.txt"); 8 try { 9 if(!f.exists()) 10 f.createNewFile(); 11 } catch (IOException e1) { 12 e1.printStackTrace(); 13 } 14 15 try { 16 O

流式数据中的数学统计量计算

在科技飞速发展的今天,每天都会产生大量新数据,例如银行交易记录,卫星飞行记录,网页点击信息,用户日志等.为了充分利用这些数据,我们需要对数据进行分析.在数据分析领域,很重要的一块内容是流式数据分析.流式数据,也即数据是实时到达的,无法一次性获得所有数据.通常情况下我们需要对其进行分批处理或者以滑动窗口的形式进行处理.分批处理也即每次处理的数据之间没有交集,此时需要考虑的问题是吞吐量和批处理的大小.滑动窗口计算表示处理的数据每次向前移N个单位,N小于要处理数据的长度.例如,在语音识别中,每个包处理

IO流--字符流写数据

IO流是用来处理设备之间的数据传输的,诸如:文件的复制,上传下载文件 Java中的流可以从不同的角度进行分类: - 按照流的方向不同:分为输入流和输出流. - 按照处理数据单位的不同:分为字节流和字符流. - 按照功能不同:分为节点流和处理流 要区分字符流和字节流,我们可以从类名来区分 类名中包含Reader:字符流  输入流 类名中包含Writer:字符流  输出流 类名中包含Input:字节流  输入流 类名中包含Output:字节流  输出流 包含Stream:字节流 今天着重来看下字符流

java 每日习题(六)从文件收集数据记录到mysql

1. mysql建库,建表 2. 准备数据文件cpu.txt 1447836374319,169,0,01447836375346,498,0,01447836376346,250,0,01447836377347,0,0,01447836378347,497,0,01447836379347,0,0,01447836380347,498,0,01447836381348,0,0,01447836382348,498,0,01447836383348,0,0,01447836384348,248