Java Stream


1 Stream 是什么?

Stream 是对集合(Collection)对象功能的增强。
它专注于对集合对象各种高效的聚合操作(aggregate operation)[注: 和数据库中的聚合函数类似, 如求平均值、最大值、前几个等操作], 或者大批量数据操作 (bulk data operation)。

2 Stream 的特点

  1. Stream 不存储数据
    Stream 不是集合元素, 它不是数据结构并不保存数据, 它是有关算法和计算。
    如果硬要说的话, 它更像一个高级版本的 Iterator。 我们平时使用的 Iterator, 只能显式地一个一个遍历元素并对元素进行我们想要的操作。
    但是 Stream, 只要给出对其包含的元素执行什么操作, 比如 “过滤掉长度大于 10 的字符串”, Stream 会隐式地在内部进行遍历, 做出相应的数据转换。
  1. Stream 不改变源数据
    当我们将一个集合转为 Stream, 然后对 Stream 进行的任何操作, 我们的数据源不会受到任何影响。
  1. 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 执行时才会执行

/** 
 *	输出日志:   
 *  准备开始了
 *  开始过滤
 *	开始过滤	
 * 	开始过滤
 */
  1. 对于 Stream 的聚合、消费或收集操作只能进行一次, 再次操作会报错

 // generate 不断生成字符串, Limit(20) 限制 20 条。
Stream<String> stream = Stream.generate(()->"user").limit(20);

// 正常执行
stream.forEach(System.out::println);

// 报错:同一个stream对象只能进行一次
stream.forEach(System.out::println);
  1. 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() 方法, 就可以将普通顺序执行的流转变为并行流

  1. 顺序流: 所有的顺序按照顺序进入流, 在这个数据被流过滤掉, 或者输出, 后下个数据才能进入流, 进行处理

  2. 并行流: 开启多个线程 (线程数由电脑决定), 并行的处理这些数据

  3. 并行流对 sorted(), distinct() 类似的元素操作 api 没有影响

  4. 并行流对 sorted(), distinct() 类似的元素操作 api 会比顺序流耗时

  5. 并行流对数据的数据的输出是无序的, 如果需要有序的输出请用: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);

4 参考

Java 8 中的 Streams API 详解


  目录