1 Stream 是什么?
Stream 是对集合(Collection)对象功能的增强。
它专注于对集合对象各种高效的聚合操作(aggregate operation)[注: 和数据库中的聚合函数类似, 如求平均值、最大值、前几个等操作], 或者大批量数据操作 (bulk data operation)。
2 Stream 的特点
- Stream 不存储数据
Stream 不是集合元素, 它不是数据结构并不保存数据, 它是有关算法和计算。
如果硬要说的话, 它更像一个高级版本的 Iterator。 我们平时使用的 Iterator, 只能显式地一个一个遍历元素并对元素进行我们想要的操作。
但是 Stream, 只要给出对其包含的元素执行什么操作, 比如 “过滤掉长度大于 10 的字符串”, Stream 会隐式地在内部进行遍历, 做出相应的数据转换。
- Stream 不改变源数据
当我们将一个集合转为 Stream, 然后对 Stream 进行的任何操作, 我们的数据源不会受到任何影响。
- Stream 具有延迟执行的特性。
Stream 流的很多操作如 filter, map 等中间操作 (intermediate operations, 返回值还是一个 Stream, 因此可以通过链式调用将中间操作串联起来) 是延迟执行的, 只有到最终操作 (terminal operation, 只能返回 void 或者一个非stream 的结果) 才会将操作顺序执行。
public boolean filter(Student stu) {
System.out.println("开始过滤");
return stu.getScore() > 80;
}
public void test() {
List<Student> stuList = new ArrayList<>();
stuList.add(new Student(21));
stuList.add(new Student(21));
stuList.add(new Student(21));
// 此处的 filter 就是我们说的 中间操作
Stream<Student> stream = stuList.stream().filter(this::filter);
System.out.println("准备开始了");
// collect 就是我们说的终点操作
List<Student> stus = stream.collect(Collectors.toList());
}
// 通过日志可知:
// filter方法是在 collect 执行时才会执行
/**
* 输出日志:
* 准备开始了
* 开始过滤
* 开始过滤
* 开始过滤
*/
- 对于 Stream 的聚合、消费或收集操作只能进行一次, 再次操作会报错
// generate 不断生成字符串, Limit(20) 限制 20 条。
Stream<String> stream = Stream.generate(()->"user").limit(20);
// 正常执行
stream.forEach(System.out::println);
// 报错:同一个stream对象只能进行一次
stream.forEach(System.out::println);
- Steam 的数据源本身可以是无限的。
除了上面说的中间操作和最终操作, 其中还有一种短路操作 (short-circuiting)
- 对于一个中间操作, 如果它接受的是一个无限大的 Stream, 但返回一个有限的新 Stream, 比如 limit (限制前多少个)
- 对于一个最终操作, 如果它接受的是一个无限大的 Stream, 但能在有限的时间计算出结果, 比如 findFirst (找第一个)
3 Stream 的使用
3.1 Stream 的创建
3.1.1 List 转为 Stream
List<Student> stuList = new ArrayList<>();
// 普通的流
Stream<Student> stream = stuList.stream();
// 并行流
Stream<String> stream1 = strs.parallelStream();
3.1.2 通过数组创建
// 基本类型
int[] arr = new int[]{1, 2, 34, 5};
IntStream intStream = Arrays.stream(arr);
// 引用类型
Student[] studentArr = new Student[]{new Student("s1", 29), new Student("s2", 27)};
Stream<Student> studentStream = Arrays.stream(studentArr);
3.1.3 通过数据集合创建
Stream<Student> stream = Stream.of(new Student("小红", 20), new Student("小明", 10));
Student[] studentArr = new Student[]{new Student("s1", 29), new Student("s2", 27)};
Student[] studentArr2 = new Student[]{new Student("s1", 29), new Student("s2", 27)};
// Stream.of 里面的数据类型是什么, 那么返回的 Stream 的泛型就是什么
Stream<Student[]> stream01 = Stream.of(studentArr, studentArr2);
3.1.4 创建空的流
// 创建 空的流
Stream<Student> s = Stream.empty();
Stream<Integer> s = Stream.empty();
3.1.5 创建无限流
// 通过 limit 截取需要的个数
Stream.generate(()-> new Student("name", 10)).limit(20).forEach(System.out::println);
3.1.6 创建规律的无限流
// 0: 基点, 从 0 开始, 生成 0, 1, 2, 3 ... 顺序的 10 个
Stream.iterate(0, x->x+1).limit(10).forEach(System.out::println);
// 0: 基点, 从 0 开始, 生成 0, 0, 0 10 个数
Stream.iterate(0, x->x).limit(10).forEach(System.out::println);
3.2 Stream 转为集合
3.2.1 转为 List
List<Student> list = stuList.stream().collect(Collectors.toList());
3.2.2 转为 Set
Set<Student> set = Arrays.stream(students).collect(Collectors.toSet());
3.2.3 转为 Array
Student[] s = Arrays.stream(students).toArray(Student[]::new);
3.2.4 转为指定的 Collection 对象
HashSet<Student> s = Arrays.stream(students).collect(Collectors.toCollection(HashSet::new));
3.2.5 转为 Map
// 参数 1: key 参数2: value 参数三: 当 map 的 key 相同时, 如何处理, s --> 新的值(s 遍历的值), a --> 已有的和新的值 key 相同的对象 (已有的值)
Map<Integer, String> map = Arrays.stream(students).collect(toMap(Student::getScore, Student::getName, (s, a)->{
return s + a ;
}));
3.3 对流的操作
3.3.1 筛选
filter: 条件过滤
// filter 里面的 lambda 表达式 返回一个 Boolean 值, 如果为 true 将这个对象保留
stuList.stream().filter(x -> x.getScore() > 80).collect(Collectors.toList());
distinct: 去重
// stuList 中重复的项会被去掉
stuList.stream().distinct().collect(Collectors.toList());
3.3.2 截取
limit: 截取前几个
// 获取 stuList 中的前 3 个
stuList.stream().limit(3L).collect(Collectors.toList());
skip: 跳过前几个
// 去掉 stuList 中的前 3 个
stuList.stream().skip(3L).collect(Collectors.toList());
3.3.3 转换 (映射)
map: 对象转换
// 获取一个 Student List
List<Student> stus = stuList.stream().collect(Collectors.toList());
// 通过 map 将输出结果转为 List<String>, String 值为每个对象的 name 属性
List<String> strList = stuList.stream().map(item -> item.getName()).collect(Collectors.toList);
flatMap: 将每个值转换成另一个流, 然后将所有的流连起来
String[] arr1 = {"a", "b", "c", "d"};
String[] arr2 = {"e", "f", "c", "d"};
// 返回结果 List<String[]>
Stream.of(arr1, arr2).collect(Collectors.toList);
// 返回结果 List<String> 2 个数组合并成 1 个
Stream.of(arr1, arr2).flatMap(Arrays::stream).collect(Collectors.toList());
3.3.4 查找
findFirst: 查找第一个
// orElse(): 如果数据没有返回 nothing
String str = Stream.of(arr).parallel().filter(x -> x.length() > 3).findFirst().orElse("nothing");
findAny: 查找一个 (findFirst() 返回的是第一个, findAny() 不一定返回第一个)
Optional<String> optional = Stream.of(arr).parallel().filter(x -> x.length() > 3).findAny();
optional.ifPresent(System.out::println);
3.3.5.匹配
anyMatch: 是否包含匹配元素
// 有一个符合就 true
Boolean aBoolean = Stream.of(arr).anyMatch(x->x.startsWith("a"));
allMatch:是否全部符合
// 有一个不符合就 false
Boolean aBoolean = Stream.of(arr).allMatch(x->x.startsWith("a"));
noneMatch: 是否存在不符合条件
// 是否存在不大于 10 的, 有一个符合条件就返回 true
Boolean result = Stream.of(1, 2, 3, 4, 5).noneMatch( x -> x > 10);
3.3.6 遍历
forEach: 对流的数据进行遍历操作
stuList.stream().forEach(item -> System.out.println(item.getName()));
3.3.7 流数据操作
peek: 对流中的每个数据进行需要的操作
// 对数据里面的每一项减去 100
stuList.stream().peek(item -> item.setScore(item.getScore() - 100)).collect(toList());
3.3.8 流合并
concat: 2 个相同类型的流进行合并
Stream<String> stream1 = Stream.of(arr1);
Stream<String> stream2 = Stream.of(arr2);
// 合并后去重
Stream.concat(stream1, stream2).distinct().forEach(System.out::println);
3.3.9 规约(将流中的数据, 按照需要, 归并成一个值)
reduce: 归并操作的函数
// 求和 参数 1: 提供给归并操作的起始值, 参数 2: 数据的处理方式, 结果返回一个值
int sum1 = numbers.stream().reduce(0, (a, b) -> a + b);
// 效果同上
int sum2 = numbers.stream().reduce(0, Integer::sum);
3.3.10 数学操作
sorted: 排序
// 按照 comparing 里面的值进行顺序排序
stuList.stream().sorted(Comparator.comparing(Student::getScore)).collect(Collectors.toList);
// 逆序排序
stuList.stream().sorted(Comparator.comparing(Student::getScore).reversed()).collect(Collectors.toList);
max/min: 最大值/最小值
// max 返回了 Optional<T>
Stream.of(arr).max(Comparator.comparing(String::length)).ifPresent(System.out::println);
// min 返回了 Optional<T>
Stream.of(arr).min(Comparator.comparing(String::length)).ifPresent(System.out::println);
count: 统计
int count = Stream.of(arr).count();
3.3.11 分组
groupingBy: 按照条件进行分组
// 按照 相同的 score 为一组, 进行分组
Map<Integer, List<Student>> groups = list.stream().collect(Collectors.groupingBy(Student::getScore));
// 2 级分组
Map<Integer, Map<String, List<Student>>> twoGroup = list.stream()
.collect(Collectors.groupingBy(Student::getScore, Collectors.groupingBy(Student::getName)));
// 可以继续多级分组
// 第二个参数也可以传递其他参数
// 统计同一组的个数
Map<Integer, Long> groups = students.stream().collect(Collectors.groupingBy(Student::getScore, Collectors.counting()));
// 按照分数分值, 统计同一组的分数
Map<Integer, Integer> groups = students.stream().collect(Collectors.groupingBy(Student::getScore, summingInt(Student::getScore)));
// 按照分数分值, 统计同一组里面的最大值
Map<String, Optional<Student>> = students.stream().collect(Collectors.groupingBy(Student::getScore, maxBy(Comparator.comparing(Student::getScore))));
// 按照分数分值, 将一组内的分数转为 Set
Map<String, Set<Integer>> = students.stream().collect(Collectors.groupingBy(Student::getScore, mapping(Student::getScore, toSet())));
3.3.12 分区 (分组的特殊情况, k 值为 true/false)
partitioningBy: 按照条件进行分组
// 按照分数是否大于 90 分进行分区, true/false 2 个分区
Map<Boolean, List<Student> groups = list.stream().collect(Collectors.partitioningBy(student -> student.getScore()> 90));
3.3.13 统计 (IntSummaryStatistics)
IntSummaryStatistics: 数学操作
// 将传入的学生的分数转为一个 Int 的统计分析对象
IntSummaryStatistics summaryStatistics = Arrays.stream(students).collect(Collectors.summarizingInt(Student::getScore));
// 获取传入的学生的分数的平均分, 其他类似
System.out.println("getAverage->"+summaryStatistics.getAverage());
System.out.println("getMax->"+summaryStatistics.getMax());
System.out.println("getMin->"+summaryStatistics.getMin());
System.out.println("getCount->"+summaryStatistics.getCount());
System.out.println("getSum->"+summaryStatistics.getSum());
3.4 并行流
只需要调用顺序流的 parallel() 方法, 就可以将普通顺序执行的流转变为并行流
顺序流: 所有的顺序按照顺序进入流, 在这个数据被流过滤掉, 或者输出, 后下个数据才能进入流, 进行处理
并行流: 开启多个线程 (线程数由电脑决定), 并行的处理这些数据
并行流对 sorted(), distinct() 类似的元素操作 api 没有影响
并行流对 sorted(), distinct() 类似的元素操作 api 会比顺序流耗时
并行流对数据的数据的输出是无序的, 如果需要有序的输出请用:forEachOrdered
// 顺序流: 0, 1, 2 ...
Stream.iterate(0, x -> x + 1 ).limit(10).forEach(System.out::println);
// 并行流: 3, 1, 0 ...
Stream.iterate(0, x -> x + 1 ).limit(10).parallel().forEach(System.out::println);
// 并行流: 0, 1, 2
Stream.iterate(0, x -> x + 1 ).limit(10).parallel().forEachOrdered(System.out::println);
3.5 原始类型流
在数据量比较大的情况下, 将基本数据类型(int, double…)包装成相应对象流的做法是低效的。
因此, 我们也可以直接将数据初始化为原始类型流, 在原始类型流上的操作与对象流类似
3.5.1 原始流的生成
DoubleStream doubleStream = DoubleStream.of(0.1, 0.3, 0.4);
IntStream intStream = IntStream.of(1, 3, 4, 5);
// 生成 [0, 100]
IntStream intStream2 = IntStream.rangeClosed(0, 100);
// 生成 [0, 100)
IntStream intStream3 = IntStream.range(0, 100);
3.5.2 原始流和对象流的转换
Stream<Double> stream = doubleStream.boxed();
DoubleStream doubleStream = stream.mapToDouble(Double::new);
3.6 Optional
通常聚合操作会返回一个 Optional 类型, Optional 表示一个安全 (安全指的是避免调用直接返回 null 值而造成空指针异常) 的指定结果类型。
Optional 常用的操作:
Optional.isPresent(): 可以判断返回值是否为空
Optional.ifPresent(Consumer<? super T> consumer): 可以在结果为空时, 调用传入的 Conumer lambda 表达时
Optional.get(): 获取 Optional 内真实的返回值
// 创建了一个空的Optional
Optional<String> opt1 = Optional.empty();
// false
System.out.println(opt1.isPresent());
// 类似给予了一个初始值
Optional<String> opt = Optional.of("andy with u");
// true
System.out.println(opt.isPresent());
// 获取Optional的默认值
String result = opt.get();
// 如果Optional有值进行操作
opt.ifPresent(item -> System.out.println(item));
3.6.1 Optional 的创建
Optional<Student> studentOptional = Optional.of(new Student("user1", 21));
// 转换 返回值 --->Option<返回值>
Optional<String> optionalStr = studentOptional.map(Student::getName);
// 迭代 (后者覆盖前者), 方法 fn 返回值为 Option<String>, fn2 返回值为 Option<String>, 要求返回值为 Option<T>, 替代前者
Optional<String> opt = fn().flatMap(fn2());
3.6.2 给 Optional 指定没有值时的行为
// Optional 里面为 null, 返回 100
int a = Stream.of(1, 3, 4).filter(item -> item > 10).max(Comparator.naturalOrder()).orElse(100);
// Optional 里面为 null, 调用最后面的 lambda 表达示并返回其返回值
int a = Stream.of(1, 3, 4).filter(item -> item > 10).max(Comparator.naturalOrder()).orElseGet(()->-1);
// Optional 里面为 null, 抛出 RuntimeException 异常
int a = Stream.of(1, 3, 4).filter(item -> item > 10).max(Comparator.naturalOrder()).orElseThrow(RuntimeException::new);