Java 8 Stream(1)-流的使用及技巧

1. Stream 简介

集合数组是经常会用到的数据结构,通常会对它们做增、删、改、查、聚合、统计、过滤等操作。在较早的 Java 版本中对集合和数组的处理并不是很便捷,不过Java 8添加了一个新的抽象Stream,可以以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供对 Java 集合运算和表达的支持,这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等

Java 源码中有文档介绍流相关信息,可参考 java/util/stream/package-info.java,读者如对 Stream 原理感兴趣可参考 Java 8 Stream(2)-原理解析

Stream 的特性如下

  • 无存储
    Stream不是一种数据结构,只是数据源的一个视图,数据源可以是一个数组,Java容器或 I/O channel 等
  • 为函数式编程提供支持
    对 Stream 的任何修改都不会修改真实的数据源,比如 Stream 的过滤操作并不会删除过滤掉的元素,而是产生一个不包含被过滤元素的新 Stream
  • 懒加载思想
    Stream 的中间操作并不会立即执行,只有等到用户真正需要结果(最终操作触发)的时候才会执行
  • 可消费性
    Stream只能被最终操作操作一次,之后就会失效,类似容器的迭代器,想要再次操作必须重新生成。在最终操作之后,再次使用流将抛出异常:
    java.lang.IllegalStateException: stream has already been operated upon or closed
    

对于流的处理主要有三种关键性操作:流的创建、中间操作(intermediate operation)以及最终操作(terminal operation)

2. Stream 创建

创建 Stream流通常有以下几种方法:

  1. 使用已有集合创建流
    Java 8中对集合类做了增强,在其中增加了stream方法,可以将一个集合类转换成流。以下代码通过一个已有的List调用其 stream() 方法创建一个流,这种创建流的方式也是最常用的一种

    除此以外还有一个parallelStream()方法可以为集合创建一个并行流parallelStream()采用多线程来计算,底层使用 ForkJoinPool 线程池,默认的并发线程数要比CPU处理器的数量少1个,因为最优的策略是每个 CPU 处理器分配一个线程,然而主线程也算一个线程

    // 1. 集合类转化
    Arrays.asList("Nathan", "Hello", "HelloWorld");
    Stream<String> stream = strings.stream();
    
    // 2. 数组转化
    Stream<String> stream = Arrays.stream(new String[]{"Nathan", "Hello", "HelloWorld"})
    
  2. 通过 Stream 创建流
    使用Stream提供的方法可以直接返回一个由指定元素组成的流

    Stream<String> stream = Stream.of("Nathan", "Hello", "HelloWorld");
    

3. Stream 常用操作

Stream 的操作主要分为两类:中间操作终结操作中间操作得到的结果还是一个Stream,我们需要借助终结操作将一个 Stream 转换成想要的类型

3.1 Stream 常用中间操作

操作方法功能
filter将符合指定条件的元素过滤出来
map以指定的方式将元素映射为另一种类型的数据,其 lambda 表达式必须有返回值
flatMapmap 类似,但当其操作的元素是仍然可迭代的数组或集合时flatMap 会将可迭代数据结构中的元素取出,以指定方式映射后合并为一个流
distinct对流中的元素去重
sorted对流进行排序
limit返回 Stream 的前面 n 个元素
skip扔掉前 n 个元素

3.2 Stream 常用终结操作

操作方法功能
foreach遍历流中的每个元素
count统计流中的元素个数
collect归约操作,可将流中的元素组成一个汇总结果,常用参数Collectors.toList()
max统计流中最大对元素,但需要指定元素比较的规则 Comparator

4. 使用示例

以下所有示例基于 People 对象,其定义如下

public class People {
    // 姓名
    private String name;
    
    // 年龄
    private Integer age; 
}

4.1 分组并映射获取对象指定字段

假设有一个 People 对象集合,需要将其按照年龄分组,并且只保留分组后每个年龄分组中的姓名,参考示例如下:

// 1. 使用  Collectors.collectingAndThen()
Map<Integer, List<String>> ageNameMap = peopleList.stream()
                .collect(Collectors.groupingBy(People::getAge,
                        Collectors.collectingAndThen(Collectors.toList(),
                                peoples -> peoples.stream().map(People::getName).collect(Collectors.toList()))));

// 2. 使用  Collectors.mapping()
Map<Integer, List<String>> ageNameMap = peopleList.stream()
                .collect(Collectors.groupingBy(People::getAge,
                        Collectors.mapping(People::getName, Collectors.toList())));                        

4.2 分组取符合指定条件的最大值

假设需要将 People 对象集合按照年龄分组,并且取该分组下最长的名字,一个参考示例如下:

 Map<Integer, String> ageNameMap = peopleList.stream()
                .collect(groupingBy(People::getAge,
                        Collectors.reducing("", People::getName, BinaryOperator.maxBy(Comparator.comparing(String::length)))));                      
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值