Java8 Stream详细用法介绍
- 一、Stream概述
- 1.1、流的定义
- 1.2、流与集合
- 1.3、流的特性
- 1.4、Stream的创建
- 1.5、Stream操作分类
- 二、Stream API 使用
- 2.1 中间操作
- 2.1.1、filter() 过滤
- 2.1.2、map与flatMap 映射
- 2.1.3、sorted() 排序
- 2.2 终止操作
- 2.2.1、forEach() 遍历
- 2.2.2、collect() 收集
- 2.2.2.1、partitioningBy/groupingBy 分区/分组
- 2.2.2.2、counting 计数
- 2.2.2.3、averagingDouble 平均值
- 2.2.2.4、maxBy minBy 最值
- 2.2.2.5、summarizingDouble 统计
- 2.2.3、match() 匹配
- 2.2.4、count() max() sum() 聚合
- 2.2.5、reduce() 规约
一、Stream概述
Stream流是JDK8新增的成员,允许以声明性方式处理数据集合,可以把Stream流看作是遍历数据集合的一个高级迭代器。Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找/筛选/过滤、排序、聚合和映射数据等操作。Stream API 提供了一种高效且易于使用的处理数据的方式。
1.1、流的定义
从支持数据处理操作的源生成的元素序列。数据源可以是集合,数组或IO资源。
1.2、流与集合
stream | Collection | |
---|---|---|
主要用途 | 主要用于描述对数据的计算 | 主要为了存储和访问数据 |
计算时间 | 在有需求的时候才计算 | 每个元素都是先计算出来的 |
遍历方式 | 内部迭代 | 外部迭代 |
1.3、流的特性
- 不是数据结构,不会保存数据,stream的目的是处理数据。
- stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
- 惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。
1.4、Stream的创建
1、通过Stream.empty()创建空流
Stream<String> streamEmpty = Stream.empty();
2、通过 java.util.Collection.stream() 方法用集合创建流
List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
3、使用 java.util.Arrays.stream(T[]array)方法用数组创建流
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
4、通过构造器 Stream.builder() 创建
使用构建器时,应在语句的右侧部分另外指定所需类型,否则build()方法将创建Stream 的实例
Stream<String> streamBuilder =
Stream.<String>builder().add("a").add("b").add("c").build();
5、使用 Stream的静态方法:of()、iterate()、generate()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
6、通过 File 创建
Java NIO类Files允许通过lines()方法生成文本文件的Stream 。 文本的每一行都成为流的一个元素:
Path path = Paths.get("C:\\file.txt");
Stream<String> streamOfFile = Files.lines(path);
Stream<String> streamWithCharset = Files.lines(path, Charset.forName("UTF-8"));
1.5、Stream操作分类
Stream操作分为两种:
-
中间操作,每次返回一个新的流,可以有多个。
- 无状态:指元素的处理不受之前元素的影响;
- 有状态:指该操作只有拿到所有元素之后才能继续下去。
-
终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。
- 非短路操作:指必须处理所有元素才能得到最终结果;
- 短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
二、Stream API 使用
2.1 中间操作
2.1.1、filter() 过滤
过滤器根据给定的谓词过滤流的所有元素,并返回一个新的流。
数据准备:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Fruit {
//id
private Integer id;
//名称
private String name;
//颜色
private String color;
//数量
private Integer num;
//重量
private Double weight;
}
List<Fruit> fruitList = new ArrayList<>();
fruitList.add(new Fruit(1, "apple", "green", 4, 300.0));
fruitList.add(new Fruit(2, "apple", "red", 14, 325.0));
fruitList.add(new Fruit(3, "apple", "yellow", 27, 450.0));
fruitList.add(new Fruit(4, "orange", "yellow", 10, 100.0));
fruitList.add(new Fruit(5, "banana", "yellow", 22, 250.0));
//1.查找重量大于400的苹果 filter
fruitList.stream().filter(p -> p.getName().equals("apple"))
.filter(p -> p.getWeight() > 400.0)
.forEach(System.out::println);
//结果输出
Fruit(id=3, name=apple, color=yellow, num=27, weight=450.0)
2.1.2、map与flatMap 映射
map:对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素,一对一映射
//2.将每种颜色苹果的数量增加5 map
fruitList.stream().filter(p -> p.getName().equals("apple"))
.map(m -> m.getNum() + 5)
.forEach(System.out::println);
//输出结果,新生成的Stream只包含苹果的数量
9
19
32
flatMap:对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素,一对多映射
//3.输出各种颜色苹果的数量 flatMap
fruitList.stream().filter(p -> p.getName().equals("apple"))
.flatMap(m -> Stream.of(m.getColor() + ": " + m.getNum()))
.forEach(System.out::println);
//输出结果
green: 4
red: 14
yellow: 27
2.1.3、sorted() 排序
对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。
//4.按重量由大到小对苹果进行排序 sorted倒序排
fruitList.stream().filter(p -> p.getName().equals("apple"))
.sorted(Comparator.comparing(Fruit::getWeight).reversed())
.forEach(System.out::println);
//输出结果
Fruit(id=3, name=apple, color=yellow, num=27, weight=450.0)
Fruit(id=2, name=apple, color=red, num=14, weight=325.0)
Fruit(id=1, name=apple, color=green, num=4, weight=300.0)
2.2 终止操作
2.2.1、forEach() 遍历
//5.forEach遍历
fruitList.stream().forEach(System.out::println);
2.2.2、collect() 收集
collect()方法用于从steam 收集元素并将它们存储在集合中。
//6.筛选出颜色为黄色的水果 collect收集
fruitList.stream().filter(f -> f.getColor().equals("yellow"))
.collect(Collectors.toList())
.forEach(System.out::println);
//输出结果
Fruit(id=3, name=apple, color=yellow, num=27, weight=450.0)
Fruit(id=4, name=orange, color=yellow, num=10, weight=100.0)
Fruit(id=5, name=banana, color=yellow, num=22, weight=250.0)
2.2.2.1、partitioningBy/groupingBy 分区/分组
partitioningBy 分区:将stream按条件分为两个 Map,比如重量是否高于300g分为两部分。
groupingBy 分组:将集合分为多个Map,比如水果按类型分组。有单级分组和多级分组。
//8.分组
//按重量是否高于300g分组 partitioningBy分区
Map<Boolean, List<Fruit>> collect = fruitList.stream().collect(Collectors.partitioningBy(p -> p.getWeight() > 300.0));
System.out.println("按重量是否高于300g分组:" + collect);
//按水果类型分组
Map<String, List<Fruit>> collect1 = fruitList.stream().collect(Collectors.groupingBy(Fruit::getName));
System.out.println("按水果类型分组:" + collect1);
//按水果类型、颜色分组
Map<String, Map<String, List<Fruit>>> collect2 =
fruitList.stream().collect(Collectors.groupingBy(Fruit::getName, Collectors.groupingBy(Fruit::getColor)));
System.out.println("按水果类型、颜色分组:" + collect2);
2.2.2.2、counting 计数
可使用count()聚合函数代替
2.2.2.3、averagingDouble 平均值
Double avgWeight = fruitList.stream().collect(Collectors.averagingDouble(Fruit::getWeight));
System.out.println("平均重量 = " + avgWeight); //平均重量 = 285.0
2.2.2.4、maxBy minBy 最值
可使用 max()、min()聚合函数代替
2.2.2.5、summarizingDouble 统计
DoubleSummaryStatistics statistics = fruitList.stream().collect(Collectors.summarizingDouble(Fruit::getWeight));
//重量统计 = DoubleSummaryStatistics{count=5, sum=1425.000000, min=100.000000, average=285.000000, max=450.000000}
System.out.println("重量统计 = " + statistics);
2.2.3、match() 匹配
Stream 有三个 match 方法:
- allMatch:Stream 中全部元素符合传入的 predicate,返回 true
- anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
- noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
//7.匹配match
//anyMatch 任意一个匹配
fruitList.stream().anyMatch(p->p.getColor().contains("green")); //true
//noneMatch 全部不匹配
fruitList.stream().noneMatch(p->p.getColor().contains("green")); //false
//allMatch 全部匹配
fruitList.stream().allMatch(p->p.getColor().contains("green")); //false
2.2.4、count() max() sum() 聚合
// 8.聚合
//计算水果总数 sum
int sum = fruitList.stream().mapToInt(Fruit::getNum).sum();
System.out.println("sum = " + sum); //sum = 77
//计算水果种类数量 count
long count = fruitList.stream().map(Fruit::getName).distinct().count();
System.out.println("水果种类数量 = " + count); //水果种类数量 = 3
//计算不同类型的水果数量 先分组再求和
Map<String, Integer> tSum = fruitList.stream().collect(Collectors.groupingBy(Fruit::getName, Collectors.summingInt(Fruit::getNum)));
System.out.println("tSum = " + tSum); //tSum = {banana=22, orange=10, apple=45}
//最大重量 max
Optional<Fruit> max = fruitList.stream().max(Comparator.comparing(Fruit::getWeight));
System.out.println("最大重量 = " + max); //最大重量 = Optional[Fruit(id=3, name=apple, color=yellow, num=27, weight=450.0)]
2.2.5、reduce() 规约
归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
List<Integer> list = Arrays.asList(1, 2, 3, 4);
//求和
Optional<Integer> reduce = list.stream().reduce((x, y) -> x + y);
System.out.println("求和:" + reduce.get()); //10
//求积
Optional<Integer> reduce2 = list.stream().reduce((x, y) -> x * y);
System.out.println("求积:" + reduce2.get()); //24
//求最大值
Optional<Integer> reduce3 = list.stream().reduce((x, y) -> x > y ? x : y);
System.out.println("求最大值:" + reduce3.get()); //4
//计算水果总数
Optional<Integer> sum = fruitList.stream().map(Fruit::getNum).reduce(Integer::sum);
System.out.println("sum = " + sum.get()); //sum = 77
//计算最大重量
Optional<Double> maxWeight = fruitList.stream().map(Fruit::getWeight).reduce(Double::max);
System.out.println("maxWeight = " + maxWeight.get()); //maxWeight = 450.0