Stream流原理与用法详解



前言

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
本文通过提供一些入门级的案例供初学者学习,让程序员写出高效率、干净、简洁的代码。

一、stream是什么?

Stream是Java 8引入的一个用于处理集合数据的API。它是一种数据处理流,可以用于对集合进行高效的函数式操作。Stream提供了一组丰富的方法,可以进行过滤、映射、聚合、排序等操作,以及支持并行处理。

Stream流的主要特点包括:

序列性:Stream是一系列元素的集合视图,可以对这些元素进行各种操作。与集合不同的是,Stream并不存储数据,而是按需计算和处理数据。

不可变性:Stream的操作通常是非破坏性的,即对原始数据不产生修改。每个Stream操作都会返回一个新的Stream作为结果,原始数据保持不变。

延迟执行:Stream操作是延迟执行的,只有在终止操作(如forEach、collect等)被调用时才会实际触发计算。这种延迟执行机制使得流操作可以进行优化,只计算实际需要的数据。

函数式编程:Stream提供了一组函数式编程的方法,可以以声明式的方式操作数据,避免了显式的迭代和条件判断,使代码更简洁、易读。

通过使用Stream,可以以一种更简洁、更表达性的方式处理集合数据,同时还能够利用并行处理来提高性能。Stream提供了诸多方法,例如filter、map、reduce、collect等,可以根据需求对数据进行筛选、转换、汇总等操作,大大简化了集合数据的处理过程。

二、stream流高效的原因分析

Stream流在处理数据时可以提供高效性的原因主要有以下几点:

延迟执行(Lazy Evaluation):Stream流的操作是延迟执行的,只有在终止操作被调用时才会进行实际的计算。这意味着在中间操作过程中,数据并没有被实际处理,而是形成了一个操作链。这种延迟执行的机制可以优化性能,只处理实际需要的数据,避免了不必要的计算。

并行处理(Parallel Processing):Stream流提供了并行处理的能力,可以充分利用多核处理器的优势,将数据划分为多个子任务并行处理。通过使用parallel()方法,可以将流转换为并行流,从而在处理大量数据时提升效率。

内部迭代(Internal Iteration):使用Stream流进行数据处理时,迭代过程由Stream库自动完成,无需手动编写迭代代码。Stream库通过内部迭代方式实现了数据的并行处理和优化,可以更好地利用底层的硬件资源。

代码简洁性和可读性:Stream流提供了一套丰富的操作方法,能够以一种更简洁、更声明式的方式处理数据。相比传统的循环和条件语句,Stream流代码更易读、易维护,减少了代码的复杂性,提高了开发效率。

三、stream流的创建

  • stream():将集合转换为顺序流。
  • 从集合创建流:
    stream() 方法可用于从集合创建一个流。它是 Collection 接口的默认方法,可以直接在实现了 Collection 接口的类上调用。
List<String> names = List.of("Alice", "Bob", "Charlie");
Stream<String> stream = names.stream();
  • 从数组创建流
    Arrays 类提供了一个重载的 stream() 方法,可用于从数组创建一个流
int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);

  • parallelStream():将集合转换为并行流。
    并行流使用多个线程同时处理流中的元素,以加速处理过程。通过并行流,可以在一些计算密集型任务或需要并行处理的大型数据集上获得更好的性能
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
long count = numbers.parallelStream()
                   .filter(n -> n % 2 == 0)
                   .count();

三、stream用法

1.遍历

  • forEach():遍历集合,对流中的每个元素执行指定的操作
List<String> list = Arrays.asList("a", "b", "c");
// 遍历集合中,打印输出集合的每一项
list.stream()
    .forEach(s-> System.out.println(s));

  • peek():与 forEach()一样,也用于遍历集合,对流中的每个元素执行指定的操作。不同点在于peek方法是一个中间操作,它接受一个Consumer函数作为参数,并对流中的每个元素执行指定的操作。与forEach不同,peek并不会消耗流或结束流的处理。它允许在流的处理过程中查看每个元素,并对其进行某些操作,然后将流继续传递给下一个操作。peek方法通常用于调试和观察流中的元素。
List<String> list = Arrays.asList("a", "b", "c");

list.stream()
    .peek(s-> System.out.println("Peek: " + s))
    .map(String::toUpperCase)
    .forEach(System.out::println);

2.过滤和筛选

  • filter(Predicate):根据指定的条件过滤流中的元素
List<String> list = Arrays.asList("apple", "banana", "orange");

        // 创建流
        Stream<String> stream = list.stream();

        // 筛选出长度大于 5 的元素
        Stream<String> filteredStream = stream.filter(s -> s.length() > 5);

        // 遍历筛选后的元素
        filteredStream.forEach(System.out::println);

  • distinct(): 去除流中重复的元素。 distinct() 方法可以直接调用在一个流上,它将返回一个包含去重后元素的新流,保留了第一次出现的元素,后续出现的相同元素会被过滤掉。
List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 3, 5, 1);
        Stream<Integer> distinctNumbers = numbers.stream().distinct();
        distinctNumbers.forEach(System.out::println);

输出结果:1,2,3,4,5


  • limit(long): 限制流中元素的数量。该方法可以直接调用在一个流上,参数 long 指定了要限制的元素数量。返回的新流将包含最多指定数量的元素,如果原始流中的元素数量少于或等于指定数量,则新流将包含全部元素。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        Stream<Integer> limitedStream = numbers.stream().limit(5);
        limitedStream.forEach(System.out::println);

        List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape", "mango");
        Stream<String> limitedFruits = fruits.stream()
                                            .filter(fruit -> fruit.length() > 5)
                                            .limit(2);
        limitedFruits.forEach(System.out::println);

以上代码输出结果

1
2
3
4
5
banana
orange

  • anyMatch(Predicate): 。它接受一个 Predicate 函数作为参数,返回一个布尔值,表示流中是否有任意一个元素满足条件。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 检查是否存在大于 3 的元素
boolean anyMatch = numbers.stream().anyMatch(n -> n > 3);
//输出结果:true

结合其他流操作使用:

List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape");

// 检查是否存在以字母 "b" 开头的水果
boolean anyMatch = fruits.stream()
                         .filter(fruit -> fruit.startsWith("b"))
                         .anyMatch(fruit -> true);
//输出结果:true

  • allMatch(Predicate): 。该方法可以直接调用在一个流上,参数 Predicate 是用来指定条件的函数。返回的布尔值表示流中的所有元素是否都满足该条件。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 检查是否所有元素都小于等于 5
boolean allMatch = numbers.stream().allMatch(n -> n <= 5);
//输出结果:true

结合其他流操作使用:

List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape");

// 检查是否所有水果的长度都大于 3
boolean allMatch = fruits.stream()
                         .filter(fruit -> fruit.startsWith("b"))
                         .allMatch(fruit -> fruit.length() > 3);
//输出结果:false

  • noneMatch(Predicate): 该方法可以直接调用在一个流上,参数 Predicate 是用来指定条件的函数。返回的布尔值表示流中是否没有任何元素满足该条件。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 检查是否不存在大于 5 的元素
boolean noneMatch = numbers.stream().noneMatch(n -> n > 5);
// 输出结果:true

结合其他流操作使用:

List<String> fruits = Arrays.asList("apple", "banana", "orange", "grape");

// 检查是否不存在以字母 "b" 开头的水果
boolean noneMatch = fruits.stream()
                          .filter(fruit -> fruit.startsWith("b"))
                          .noneMatch(fruit -> true);
// 输出结果:true

3.映射

  • map(Function):用于对流中的每个元素应用指定的函数,并将函数的结果映射到一个新的流中。
// 过滤出长度大于 4 的名字,然后将它们转换为大写,打印出最后的结果。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        names.stream()
                      .filter(name -> name.length() > 4)
                      .map(String::toUpperCase)
                      .forEach(System.out::println);
// 输出结果:CHARLIE

4.排序

  • sorted():对流中的元素进行自然排序。
List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 3);
        numbers.stream().sorted()forEach(System.out::println);

// 输出结果:
// 1
// 2
// 3
// 5
// 8

  • sorted(Comparator):自定义的比较器(Comparator)来指定排序规则。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 根据字符串长度对流中的元素进行排序
List<String> result = names.stream().sorted(Comparator.comparing(String::length)).toList();


// 输出结果:
// Bob
// Alice
// Charlie

5.聚合和收集

  • toList():直接调用在一个流上,它会将流中的元素收集到一个 List 集合中。
// 将集合中的元素进行排序后再输出。
 List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 3);
        List<Integer> result = numbers.stream().sorted().toList();
        System.out.println(result );

// 输出结果: [1, 2, 3, 5, 8]

  • collect(Collector)::用于将流中的元素根据指定的收集器(Collector)进行聚合操作,将结果收集到一个容器中,如列表(List)、集合(Set)、映射(Map)等。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

List<Integer> names1 = names.stream()
                              .collect(Collectors.toList());
System.out.println(names1);
Set<String> names2 = names.stream()
                          .collect(Collectors.toSet());
System.out.println(names2);


// 输出结果:
// [Alice, Bob, Charlie]
// [Alice, Bob, Charlie]