Stream流式编程详解,让代码变得更优雅

1、流式编程的概念和作用

流式编程(Stream Programming)是一种编程范式,它强调使用流(Stream)来处理数据序列。在流式编程中,数据被看作是一系列连续的元素流,而不是离散的数据集合。流式编程的目标是通过使用串行或并行的操作链来处理数据,从而实现更简洁、更可读和更可维护的代码。

以下是流式编程的一些核心概念和作用:

  1. 链式操作:流式编程允许你通过链式操作对数据进行处理。你可以将多个操作链接在一起,形成一个操作链。每个操作都会对数据流进行处理,并将结果传递给下一个操作。这种链式操作的方式使得代码更加清晰、简洁,减少了临时变量和中间步骤的使用。

  2. 懒执行:流式编程支持懒执行(Lazy Evaluation)。这意味着在一个操作链中,每个操作只在需要时才会被执行,而不是立即对所有的数据进行处理。这种延迟执行的特性可以提高性能,避免不必要的计算。

  3. 并行处理:流式编程可以很容易地实现并行处理。通过使用并行流(Parallel Stream),你可以将数据分成多个部分,并在多个线程上并行处理这些部分。这对于处理大规模数据集或需要耗时的操作非常有用,可以显著提高程序的执行效率。

  4. 函数式编程:流式编程借鉴了函数式编程的思想。它鼓励使用函数式的方式来处理数据,如使用Lambda表达式、函数接口等。函数式编程的特点是无副作用、不可变性和纯函数,这些特性使得代码更加可靠、可测试和可扩展。

  5. 提高代码可读性:使用流式编程可以将复杂的数据处理逻辑以一种更直观、更易懂的方式表达出来。链式操作和函数式编程的特性使得代码更加简洁、易读,减少了冗余的代码和临时变量的使用。

流式编程通过使用流、链式操作和函数式编程的思想,提供了一种优雅、简洁的方式来处理数据。它可以提高代码的可读性和可维护性,同时还可以提高程序的性能和扩展性。

2、java中stream流

Java Stream是Java 8引入的一个功能强大的API,用于处理集合数据。它提供了一种函数式编程的方式来操作集合,使得代码更简洁、可读性更好。

Java Stream提供了一系列的操作,包括过滤、映射、排序、聚合等。你可以使用Stream对集合进行筛选、转换、排序等操作,而无需显式地使用循环和条件语句。

下面是一个简单的示例,展示了如何使用Java Stream对一个整数集合进行筛选和求和的操作:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int sum = numbers.stream()
                .filter(n -> n % 2 == 0) // 过滤偶数
                .mapToInt(n -> n)       // 转换为int类型
                .sum();                 // 求和

System.out.println(sum); // 输出:30

在这个例子中,我们首先将整数集合转换为Stream,然后使用filter方法筛选出偶数,接着使用mapToInt方法将Stream中的元素转换为int类型,最后使用sum方法求和。

Java Stream还提供了很多其他的操作方法,如forEachcollectreduce等,可以根据具体的需求选择合适的方法进行操作。使用Java Stream可以简化代码,提高代码的可读性和可维护性。

3、如何创建 Stream 对象

  1. 通过集合创建:你可以通过集合的stream()方法来创建一个Stream对象。例如:
List<String> list = Arrays.asList("apple", "banana", "orange");
Stream<String> stream = list.stream();
  1. 通过数组创建:你可以使用Arrays类的stream()方法来创建一个Stream对象。例如:
String[] array = {"apple", "banana", "orange"};
Stream<String> stream = Arrays.stream(array);
  1. 通过Stream.of()方法创建:你可以使用Stream类的of()方法来创建一个Stream对象,直接指定元素作为参数。例如:
Stream<String> stream = Stream.of("apple", "banana", "orange");
  1. 通过Stream.builder()创建:你可以使用Stream.Builder类来逐步构建一个Stream对象。例如:
Stream.Builder<String> builder = Stream.builder();
builder.add("apple");
builder.add("banana");
builder.add("orange");
Stream<String> stream = builder.build();
  1. 通过文件创建:你可以使用Files类的lines()方法来创建一个Stream对象,读取文件中的行作为Stream的元素。例如:
Path path = Paths.get("file.txt");
Stream<String> stream = Files.lines(path);

Java 8和之后的版本提供了更多的方法来创建Stream对象,如使用IntStream、LongStream、Stream.iterate()等。

4、常用的 Stream 操作方法

Java Stream提供了丰富的操作方法,可以对数据流进行各种处理和转换。下面是一些常用的Stream操作方法:

  1. filter(Predicate predicate):根据给定的条件(Predicate)过滤流中的元素,只保留满足条件的元素。

  2. map(Function mapper):对流中的每个元素应用给定的映射函数(Function),将元素转换为另一种类型。

  3. flatMap(Function mapper):对流中的每个元素应用给定的映射函数(Function),将元素转换为一个新的流,并将所有新流的元素合并为一个流。

  4. distinct():去除流中的重复元素,使得流中的元素保持唯一性。

  5. sorted():对流中的元素进行排序,默认按照自然顺序进行排序。也可以使用自定义的Comparator来指定排序规则。

  6. limit(long maxSize):限制流的大小,截取前面的指定数量的元素。

  7. skip(long n):跳过流的前面指定数量的元素,返回剩余的元素。

  8. forEach(Consumer action):对流中的每个元素执行指定的操作(Consumer)。

  9. collect(Collector collector):将流中的元素收集到一个容器中,如List、Set、Map等。

  10. reduce(BinaryOperator accumulator):将流中的元素按照指定的二元操作(BinaryOperator)进行归约,返回一个Optional对象。

  11. anyMatch(Predicate predicate):判断流中是否存在满足给定条件(Predicate)的元素,返回一个boolean值。

  12. allMatch(Predicate predicate):判断流中的所有元素是否都满足给定条件(Predicate),返回一个boolean值。

  13. noneMatch(Predicate predicate):判断流中是否没有满足给定条件(Predicate)的元素,返回一个boolean值。

5、Stream 的中间操作

5.1、过滤操作(filter)

过滤操作(filter)是Java Stream中的一种常用操作方法,它用于根据给定的条件来筛选流中的元素,只保留满足条件的元素。filter方法接受一个Predicate函数式接口作为参数,该接口定义了一个用于判断元素是否满足条件的方法。

下面是使用filter方法进行过滤操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> evenNumbers = numbers.stream()
                                   .filter(n -> n % 2 == 0)
                                   .collect(Collectors.toList());

System.out.println(evenNumbers);  // 输出:[2, 4, 6, 8, 10]

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用filter方法传入一个Lambda表达式作为参数,该Lambda表达式定义了一个判断元素是否为偶数的条件(n % 2 == 0)。最后,使用collect方法将过滤后的元素收集到一个新的List集合中。

需要注意的是,filter方法只是对流中的元素进行筛选,它并不会改变流本身。因此,你可以在filter操作之后继续对流进行其他操作,如映射、排序、归约等。filter方法可以根据具体的需求和条件来进行灵活的过滤操作。

5.2、映射操作(map)

映射操作(map)是Java Stream中的一种常用操作方法,它用于对流中的每个元素应用给定的映射函数,并将元素转换为另一种类型。map方法接受一个Function函数式接口作为参数,该接口定义了一个将输入元素转换为输出元素的方法。

下面是使用map方法进行映射操作的示例:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

List<Integer> nameLengths = names.stream()
                                 .map(String::length)
                                 .collect(Collectors.toList());

System.out.println(nameLengths);  // 输出:[5, 3, 7, 5]

在上面的示例中,我们创建了一个包含字符串的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用map方法传入一个方法引用(String::length)作为参数,该方法引用表示将输入字符串转换为其长度的操作。最后,使用collect方法将映射后的结果收集到一个新的List集合中。

需要注意的是,map方法只是对流中的元素进行映射,它并不会改变流本身。因此,你可以在map操作之后继续对流进行其他操作,如过滤、排序、归约等。map方法可以根据具体的需求和映射逻辑来进行灵活的转换操作。

5.3、排序操作(sorted)

排序操作(sorted)是Java Stream中的一种常用操作方法,它用于对流中的元素进行排序。sorted方法可以按照自然顺序或者通过自定义的Comparator来进行排序。

下面是使用sorted方法进行排序操作的示例:

List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3, 6, 4, 7);

List<Integer> sortedNumbers = numbers.stream()
                                      .sorted()
                                      .collect(Collectors.toList());

System.out.println(sortedNumbers);  // 输出:[1, 2, 3, 4, 5, 6, 7, 8, 9]

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用sorted方法对流中的元素进行排序。由于没有传入Comparator参数,所以使用默认的自然顺序进行排序。最后,使用collect方法将排序后的元素收集到一个新的List集合中。

通过这个示例,我们成功地对原始集合中的整数进行了升序排序,并将排序结果收集到了一个新的List集合中。

如果你想使用自定义的排序规则,可以通过传入Comparator参数给sorted方法来实现。例如,如果要按照元素的降序进行排序,可以使用如下代码:

List<Integer> sortedNumbers = numbers.stream()
                                      .sorted(Comparator.reverseOrder())
                                      .collect(Collectors.toList());

需要注意的是,sorted方法只是对流中的元素进行排序,它并不会改变流本身。因此,你可以在sorted操作之后继续对流进行其他操作,如过滤、映射、归约等。sorted方法可以根据具体的需求和排序规则来进行灵活的排序操作。

5.4、截断操作(limit 和 skip)

  1. limit(long maxSize):limit方法用于限制流的大小,截取前面指定数量的元素。它接受一个long类型的参数,表示要保留的最大元素数量。

下面是使用limit方法进行截断操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> limitedNumbers = numbers.stream()
                                      .limit(5)
                                      .collect(Collectors.toList());

System.out.println(limitedNumbers);  // 输出:[1, 2, 3, 4, 5]

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用limit方法传入一个参数5,表示要保留的最大元素数量为5。最后,使用collect方法将截断后的元素收集到一个新的List集合中。

  1. skip(long n):skip方法用于跳过流的前面指定数量的元素,返回剩余的元素。它接受一个long类型的参数,表示要跳过的元素数量。

下面是使用skip方法进行截断操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> skippedNumbers = numbers.stream()
                                      .skip(5)
                                      .collect(Collectors.toList());

System.out.println(skippedNumbers);  // 输出:[6, 7, 8, 9, 10]

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用skip方法传入一个参数5,表示要跳过的元素数量为5。最后,使用collect方法将剩余的元素收集到一个新的List集合中。

需要注意的是,limit和skip方法都是对流进行截断操作,它们并不会改变流本身。因此,你可以在截断操作之后继续对流进行其他操作,如过滤、映射、排序、归约等。limit和skip方法可以根据具体的需求来进行灵活的截断操作。

6、Stream 的终端操作

6.1、forEach 和 peek

forEach和peek都是Java Stream中用于对流中的元素进行遍历的操作方法,它们的作用类似,但有一些细微的差别。

  1. forEach(Consumer action):forEach方法接受一个Consumer函数式接口作为参数,对流中的每个元素执行指定的操作。

下面是使用forEach方法进行遍历操作的示例:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

names.stream()
     .forEach(System.out::println);

在上面的示例中,我们创建了一个包含字符串的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用forEach方法传入一个方法引用(System.out::println)作为参数,表示对每个元素执行打印操作。最终,流中的每个元素都会被打印出来。

  1. peek(Consumer action):peek方法也接受一个Consumer函数式接口作为参数,对流中的每个元素执行指定的操作,但不会消费流元素,而是返回一个新的流。

下面是使用peek方法进行遍历操作的示例:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

List<String> upperCaseNames = names.stream()
                                   .map(String::toUpperCase)
                                   .peek(System.out::println)
                                   .collect(Collectors.toList());

在上面的示例中,我们创建了一个包含字符串的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用map方法将每个字符串转换为大写形式。接着使用peek方法传入一个方法引用(System.out::println)作为参数,表示对每个元素执行打印操作。最后,使用collect方法将处理后的元素收集到一个新的List集合中。

需要注意的是,forEach方法是一个终端操作,它会立即执行指定的操作,并结束流的处理。而peek方法是一个中间操作,它会在执行指定的操作后返回一个新的流,可以继续对该流进行其他操作。peek方法通常用于调试和观察流中的元素,而不会对流进行最终的收集操作。

6.2、聚合操作(reduce 和 collect)

聚合操作是Java Stream中用于对流中的元素进行归约和收集的操作方法,其中reduce和collect是两种常用的聚合操作方法。

  1. reduce(BinaryOperator accumulator):reduce方法接受一个BinaryOperator函数式接口作为参数,对流中的元素进行归约操作,返回一个Optional对象。

下面是使用reduce方法进行归约操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Optional<Integer> sum = numbers.stream()
                               .reduce((a, b) -> a + b);

sum.ifPresent(System.out::println);  // 输出:15

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用reduce方法传入一个Lambda表达式((a, b) -> a + b)作为参数,表示对两个元素进行求和操作。最终,reduce方法返回一个Optional对象,表示归约的结果。

  1. collect(Collector collector):collect方法接受一个Collector对象作为参数,对流中的元素进行收集操作,返回一个最终结果。

下面是使用collect方法进行收集操作的示例:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

String concatenated = names.stream()
                           .collect(Collectors.joining(", "));

System.out.println(concatenated);  // 输出:Alice, Bob, Charlie

在上面的示例中,我们创建了一个包含字符串的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用collect方法传入一个Collector对象(Collectors.joining(", "))作为参数,表示将元素通过逗号和空格连接起来。最终,collect方法返回一个字符串,表示收集的结果。

需要注意的是,reduce方法和collect方法都是对流中的元素进行聚合操作,但它们的使用方式和返回结果有所不同。reduce方法返回一个Optional对象,可以处理没有初始值的情况,而collect方法返回一个最终结果,通常用于将流中的元素收集到一个集合或者其他数据结构中。

6.3、匹配操作(allMatch、anyMatch 和 noneMatch)

匹配操作是Java Stream中用于检查流中的元素是否满足指定条件的操作方法,常用的匹配操作包括allMatch、anyMatch和noneMatch。

  1. allMatch(Predicate predicate):allMatch方法接受一个Predicate函数式接口作为参数,检查流中的所有元素是否都满足指定的条件,返回一个boolean值。

下面是使用allMatch方法进行匹配操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

boolean allEven = numbers.stream()
                         .allMatch(n -> n % 2 == 0);

System.out.println(allEven);  // 输出:false

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用allMatch方法传入一个Lambda表达式(n -> n % 2 == 0)作为参数,表示判断元素是否为偶数。最终,allMatch方法返回一个boolean值,表示是否所有元素都满足条件。

通过这个示例,我们检查了流中的所有元素是否都是偶数,并将结果打印出来。

  1. anyMatch(Predicate predicate):anyMatch方法接受一个Predicate函数式接口作为参数,检查流中的任意一个元素是否满足指定的条件,返回一个boolean值。

下面是使用anyMatch方法进行匹配操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

boolean anyEven = numbers.stream()
                         .anyMatch(n -> n % 2 == 0);

System.out.println(anyEven);  // 输出:true

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用anyMatch方法传入一个Lambda表达式(n -> n % 2 == 0)作为参数,表示判断是否存在偶数元素。最终,anyMatch方法返回一个boolean值,表示是否存在满足条件的元素。

  1. noneMatch(Predicate predicate):noneMatch方法接受一个Predicate函数式接口作为参数,检查流中的所有元素是否都不满足指定的条件,返回一个boolean值。

下面是使用noneMatch方法进行匹配操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

boolean noneNegative = numbers.stream()
                              .noneMatch(n -> n < 0);

System.out.println(noneNegative);  // 输出:true

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用noneMatch方法传入一个Lambda表达式(n -> n < 0)作为参数,表示判断是否所有元素都不为负数。最终,noneMatch方法返回一个boolean值,表示是否所有元素都不满足条件。

需要注意的是,allMatch、anyMatch和noneMatch方法都是对流中的元素进行匹配操作,根据返回的boolean值来表示是否满足条件。这些方法可以根据具体的需求来进行灵活的匹配操作。

6.4、查找操作(findFirst 和 findAny)

查找操作是Java Stream中用于查找流中的元素的操作方法,常用的查找操作包括findFirst和findAny。

  1. findFirst():findFirst方法返回流中的第一个元素(按照流的遍历顺序),如果流为空,则返回一个空的Optional对象。

下面是使用findFirst方法进行查找操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Optional<Integer> firstEven = numbers.stream()
                                     .filter(n -> n % 2 == 0)
                                     .findFirst();

firstEven.ifPresent(System.out::println);  // 输出:2

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用filter方法过滤出偶数元素,接着使用findFirst方法获取第一个偶数元素。最终,findFirst方法返回一个Optional对象,表示第一个满足条件的元素。

  1. findAny():findAny方法返回流中的任意一个元素,如果流为空,则返回一个空的Optional对象。

下面是使用findAny方法进行查找操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Optional<Integer> anyEven = numbers.stream()
                                   .filter(n -> n % 2 == 0)
                                   .findAny();

anyEven.ifPresent(System.out::println);  // 输出:2 或者 4(取决于具体实现)

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用filter方法过滤出偶数元素,接着使用findAny方法获取任意一个偶数元素。最终,findAny方法返回一个Optional对象,表示满足条件的任意一个元素。

需要注意的是,findFirst和findAny方法都是对流中的元素进行查找操作,返回一个Optional对象。如果流为空,那么这两个方法都会返回一个空的Optional对象。这些方法可以根据具体的需求来进行灵活的查找操作。

6.5、统计操作(count、max 和 min)

统计操作是Java Stream中用于获取流中元素统计信息的操作方法,常用的统计操作包括count、max和min。

  1. count():count方法返回流中的元素个数。

下面是使用count方法进行统计操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

long count = numbers.stream()
                    .count();

System.out.println(count);  // 输出:5

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用count方法获取流中元素的个数。最终,count方法返回一个long类型的值,表示元素的个数。

通过这个示例,我们成功地获取了流中元素的个数,并将其打印出来。

  1. max(Comparator comparator):max方法接受一个Comparator函数式接口作为参数,返回流中的最大元素。

下面是使用max方法进行统计操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Optional<Integer> max = numbers.stream()
                               .max(Comparator.naturalOrder());

max.ifPresent(System.out::println);  // 输出:5

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用max方法传入一个Comparator.naturalOrder()作为参数,表示按照自然顺序比较元素大小。最终,max方法返回一个Optional对象,表示最大的元素。

  1. min(Comparator comparator):min方法接受一个Comparator函数式接口作为参数,返回流中的最小元素。

下面是使用min方法进行统计操作的示例:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Optional<Integer> min = numbers.stream()
                               .min(Comparator.naturalOrder());

min.ifPresent(System.out::println);  // 输出:1

在上面的示例中,我们创建了一个包含整数的List集合。通过调用stream方法,我们将List转换为一个Stream对象。然后使用min方法传入一个Comparator.naturalOrder()作为参数,表示按照自然顺序比较元素大小。最终,min方法返回一个Optional对象,表示最小的元素。

需要注意的是,count、max和min方法都是对流中的元素进行统计操作,返回一个表示统计结果的值或Optional对象。这些方法可以根据具体的需求来获取流中元素的统计信息。

你可能感兴趣的:(java)