一、导言

流式编程的概念和效果

Java 流(Stream)是一连串的元素序列,能够进行各种操作以完成数据的转化和处理。流式编程的概念依据函数式编程的思维,旨在简化代码,进步可读性和可保护性。

Java Stream 的首要效果有以下几个方面:

  1. 简化调集操作:运用传统的 for 循环或迭代器来处理调集数据或许会导致冗长而杂乱的代码。而运用流式编程,能够用更直观、更简练的办法对调集进行过滤、映射、排序、聚合等操作,使代码变得愈加明晰易懂。

  2. 推迟核算:流式操作答应你在处理数据之前界说一系列的操作过程,但只在需求成果时才会实践履行。这种推迟核算的特性意味着能够依据需求动态调整数据处理的操作流程,进步功率。

  3. 并行处理:Java Stream 供给了并行流的支撑,能够将数据分红多个块进行并行处理,然后充分运用多核处理器的功用优势,进步代码的履行速度。

  4. 函数式编程风格:流式编程鼓励运用函数式编程的思维,经过传递函数作为参数或运用 Lambda 表达式来完成代码的简化和灵敏性。这种函数式的编程办法有助于削减副效果,并使代码更易测验和调试。

为什么运用流式编程能够进步代码可读性和简练性

  1. 声明式编程风格:流式编程选用了一种声明式的编程风格,你只需描绘你想要对数据履行的操作,而不需求显式地编写迭代和控制流句子。这使得代码愈加直观和易于了解,因为你能够更专心地表达你的意图,而无需关注怎么完成。

  2. 链式调用:流式编程运用办法链式调用的办法,将多个操作链接在一起。每个办法都回来一个新的流目标,这样你能够像“流水线”一样在代码中次序地写下各种操作,使代码逻辑明晰明晰。这种链式调用的办法使得代码看起来愈加流畅,削减了中心变量和暂时调集的运用。

  3. 操作的组合:流式编程供给了一系列的操作办法,如过滤、映射、排序、聚合等,这些办法能够按照需求进行组合运用。你能够依据具体的事务需求将这些操作串联起来,构成一个杂乱的处理流程,而不需求编写许多的循环和条件句子。这种组合操作的办法使得代码愈加模块化和可保护。

  4. 削减中心状况:传统的迭代办法一般需求引进中心变量来保存中心成果,这样会增加代码的杂乱度和保护成本。而流式编程将多个操作链接在一起,经过流目标自身来传递数据,防止了中心状况的引进。这种办法使得代码愈加简练,削减了暂时变量的运用。

  5. 削减循环和条件:流式编程能够替代传统的循环和条件句子的运用。例如,能够运用 filter() 办法进行元素的挑选,运用 map() 办法进行元素的转化,运用 reduce() 办法进行聚合操作等。这些办法能够用一行代码完成相应的操作,防止了繁琐的循环和条件逻辑,使得代码愈加简练明晰。

二、Stream 基础知识

什么是 Stream

Stream(流)是 Java 8 引进的一个新的抽象概念,它代表着一种处理数据的序列。简略来说,Stream 是一系列元素的调集,这些元素能够是调集、数组、I/O 资源或许其他数据源。

Stream API 供给了丰厚的操作办法,能够对 Stream 中的元素进行各种转化、过滤、映射、聚合等操作,然后完成对数据的处理和操作。Stream API 的规划目标是供给一种高效、可扩展和易于运用的办法来处理许多的数据。

Stream 具有以下几个关键特色:

  1. 数据源:Stream 能够依据不同类型的数据源创立,如调集、数组、I/O 资源等。你能够经过调用调集或数组的 stream() 办法来创立一个流。

  2. 数据处理:Stream 供给了丰厚的操作办法,能够对流中的元素进行处理。这些操作能够按需求组合起来,构成一个流水线式的操作流程。常见的操作包括过滤(filter)、映射(map)、排序(sorted)、聚合(reduce)等。

  3. 慵懒求值:Stream 的操作是慵懒求值的,也就是说在界说操作流程时,不会当即履行实践核算。只要当停止操作(如搜集成果或遍历元素)被调用时,才会触发实践的核算过程。

  4. 不行变性:Stream 是不行变的,它不会修改原始数据源,也不会发生中心状况或副效果。每个操作都会回来一个新的流目标,以保证数据的不行变性。

  5. 并行处理:Stream 支撑并行处理,能够经过 parallel() 办法将流通化为并行流,运用多核处理器的优势来进步处理速度。在某些状况下,运用并行流能够极大地进步程序的功用。

经过运用 Stream,咱们能够运用简练、函数式的办法处理数据。比较传统的循环和条件句子,Stream 供给了更高层次的抽象,使代码更具可读性、简练性和可保护性。它是一种强大的东西,能够帮助咱们更有效地处理和操作调集数据。

Stream 的特性和优势

  1. 简化的编程模型:Stream 供给了一种更简练、更声明式的编程模型,使代码更易于了解和保护。经过运用 Stream API,咱们能够用更少的代码完成杂乱的数据操作,将关注点从怎么完成转移到了更关注咱们想要做什么。

  2. 函数式编程风格:Stream 是依据函数式编程思维规划的,它鼓励运用不行变的数据和纯函数的办法进行操作。这种风格防止了副效果,使代码愈加模块化、可测验和可保护。此外,Stream 还支撑 Lambda 表达式,使得代码愈加简练和灵敏。

  3. 慵懒求值:Stream 的操作是慵懒求值的,也就是说在界说操作流程时并不会当即履行核算。只要当停止操作被调用时,才会触发实践的核算过程。这种特功用够防止对整个数据集进行不必要的核算,进步了功率。

  4. 并行处理才能:Stream 支撑并行处理,在某些状况下能够经过 parallel() 办法将流通化为并行流,运用多核处理器的优势来进步处理速度。并行流能够主动将数据划分为多个子任务,并在多个线程上同时履行,进步了处理许多数据的功率。

  5. 优化的功用:Stream API 内部运用了优化技术,如推迟履行、短路操作等,以进步核算功用。Stream 操作是经过内部迭代器完成的,能够更好地运用硬件资源,并习惯数据规划的改动。

  6. 支撑丰厚的操作办法:Stream API 供给了许多丰厚的操作办法,如过滤、映射、排序、聚合等。这些办法能够按需求组合起来构成一个操作流程。在组合多个操作时,Stream 供给了链式调用的办法,使代码愈加简练和可读性更强。

  7. 能够操作各种数据源:Stream 不只能够操作调集类数据,还能够操作其他数据源,如数组、I/O 资源甚至无限序列。这使得咱们能够运用相同的编程模型来处理各种类型的数据。

怎么创立 Stream 目标

  1. 从调集创立:咱们能够经过调用调集的 stream() 办法来创立一个 Stream 目标。例如:

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> stream = numbers.stream();
    
  2. 从数组创立:Java 8 引进了 Arrays 类的 stream() 办法,咱们能够运用它来创立一个 Stream 目标。例如:

    String[] names = {"Alice", "Bob", "Carol"};
    Stream<String> stream = Arrays.stream(names);
    
  3. 经过 Stream.of() 创立:咱们能够运用 Stream.of() 办法直接将一组元素转化为 Stream 目标。例如:

    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
    
  4. 经过 Stream.builder() 创立:假如咱们不确定要增加多少个元素到 Stream 中,能够运用 Stream.builder() 创立一个 Stream.Builder 目标,并运用其 add() 办法来逐一增加元素,最终调用 build() 办法生成 Stream 目标。例如:

    Stream.Builder<String> builder = Stream.builder();
    builder.add("Apple");
    builder.add("Banana");
    builder.add("Cherry");
    Stream<String> stream = builder.build();
    
  5. 从 I/O 资源创立:Java 8 引进了一些新的 I/O 类(如 BufferedReaderFiles 等),它们供给了许多办法来读取文件、网络流等数据。这些办法一般回来一个 Stream 目标,能够直接运用。例如:

    Path path = Paths.get("data.txt");
    try (Stream<String> stream = Files.lines(path)) {
        // 运用 stream 处理数据
    } catch (IOException e) {
        e.printStackTrace();
    }
    
  6. 经过生成器创立:除了从现有的数据源创立 Stream,咱们还能够运用生成器来生成元素。Java 8 中供给了 Stream.generate() 办法和 Stream.iterate() 办法来创立无限 Stream。例如:

    Stream<Integer> stream = Stream.generate(() -> 0); // 创立一个无限流,每个元素都是 0
    Stream<Integer> stream = Stream.iterate(0, n -> n + 1); // 创立一个无限流,从 0 开始递增
    

需求留意的是,Stream 目标是一种一次性运用的目标,它只能被消费一次。一旦对 Stream 履行了停止操作(如搜集成果、遍历元素),Stream 就会被封闭,后续无法再运用。因而,在运用 Stream 时,需求依据需求重新创立新的 Stream 目标。

常用的 Stream 操作办法

  1. 过滤(Filter):filter() 办法承受一个 Predicate 函数作为参数,用于过滤 Stream 中的元素。只要满意 Predicate 条件的元素会被保存下来。例如:

    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
    Stream<Integer> filteredStream = stream.filter(n -> n % 2 == 0); // 过滤出偶数
    
  2. 映射(Map):map() 办法承受一个 Function 函数作为参数,用于对 Stream 中的元素进行映射转化。对每个元素运用函数后的成果会构成一个新的 Stream。例如:

    Stream<String> stream = Stream.of("apple", "banana", "cherry");
    Stream<Integer> mappedStream = stream.map(s -> s.length()); // 映射为单词长度
    
  3. 扁平映射(FlatMap):flatMap() 办法类似于 map() 办法,不同之处在于它能够将每个元素映射为一个流,并将一切流衔接成一个流。这首要用于处理嵌套调集的状况。例如:

    List<List<Integer>> nestedList = Arrays.asList(
        Arrays.asList(1, 2),
        Arrays.asList(3, 4),
        Arrays.asList(5, 6)
    );
    Stream<Integer> flattenedStream = nestedList.stream().flatMap(List::stream); // 扁平化为一个流
    
  4. 切断(Limit):limit() 办法能够约束 Stream 的巨细,只保存前 n 个元素。例如:

    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
    Stream<Integer> limitedStream = stream.limit(3); // 只保存前 3 个元素
    
  5. 越过(Skip):skip() 办法能够越过 Stream 中的前 n 个元素,回来剩余的元素组成的新 Stream。例如:

    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
    Stream<Integer> skippedStream = stream.skip(2); // 越过前 2 个元素
    
  6. 排序(Sorted):sorted() 办法用于对 Stream 中的元素进行排序,默认是天然次序排序。还能够供给自界说的 Comparator 参数来指定排序规则。例如:

    Stream<Integer> stream = Stream.of(5, 2, 4, 1, 3);
    Stream<Integer> sortedStream = stream.sorted(); // 天然次序排序
    
  7. 去重(Distinct):distinct() 办法用于去除 Stream 中的重复元素,依据元素的 equals()hashCode() 办法来判别是否重复。例如:

    Stream<Integer> stream = Stream.of(1, 2, 2, 3, 3, 3);
    Stream<Integer> distinctStream = stream.distinct(); // 去重
    
  8. 汇总(Collect):collect() 办法用于将 Stream 中的元素搜集到成果容器中,如 List、Set、Map 等。能够运用预界说的 Collectors 类供给的工厂办法来创立搜集器,也能够自界说搜集器。例如:

    Stream<String> stream = Stream.of("apple", "banana", "cherry");
    List<String> collectedList = stream.collect(Collectors.toList()); // 搜集为 List
    
  9. 归约(Reduce):reduce() 办法用于将 Stream 中的元素顺次进行二元操作,得到一个终究的成果。它承受一个初始值和一个 BinaryOperator 函数作为参数。例如:

    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
    Optional<Integer> sum = stream.reduce((a, b) -> a + b); // 对一切元素求和
    
  10. 核算(Summary Statistics):summaryStatistics() 办法能够从 Stream 中获取一些常用的核算信息,如元素个数、最小值、最大值、总和平和均值。例如:

    IntStream stream = IntStream.of(1, 2, 3, 4, 5);
    IntSummaryStatistics stats = stream.summaryStatistics();
    System.out.println("Count: " + stats.getCount());
    System.out.println("Min: " + stats.getMin());
    System.out.println("Max: " + stats.getMax());
    System.out.println("Sum: " + stats.getSum());
    System.out.println("Average: " + stats.getAverage());
    

以上只是 Stream API 供给的一部分常用操作办法,还有许多其他操作办法,如匹配(Match)、查找(Find)、遍历(ForEach)等

三、Stream 的中心操作

过滤操作(filter)

过滤操作(filter)是 Stream API 中的一种常用操作办法,它承受一个 Predicate 函数作为参数,用于过滤 Stream 中的元素。只要满意 Predicate 条件的元素会被保存下来,而不满意条件的元素将被过滤掉。

过滤操作的语法如下:

Stream<T> filter(Predicate<? super T> predicate)

其间,T 表明 Stream 元素的类型,predicate 是一个函数式接口 Predicate 的实例,它的泛型参数和 Stream 元素类型一致。

运用过滤操作能够依据自界说的条件来挑选出符合要求的元素,然后对 Stream 进行精确的数据过滤。

下面是一个示例,演示怎么运用过滤操作挑选出一个整数流中的偶数:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> filteredStream = stream.filter(n -> n % 2 == 0);
filteredStream.forEach(System.out::println); // 输出成果: 2 4

在这个示例中,咱们首要创立了一个包括整数的 Stream,并调用 filter() 办法传入一个 Lambda 表达式 n -> n % 2 == 0,表明要挑选出偶数。然后经过 forEach() 办法遍历输出成果。

需求留意的是,过滤操作回来的是一个新的 Stream 实例,原始的 Stream 不会遭到改动。这也是 Stream 操作办法的一个重要特色,它们一般回来一个新的 Stream 实例,以便进行链式调用和组合多个操作过程。

在实践运用中,过滤操作能够与其他操作办法结合运用,如映射(map)、排序(sorted)、归约(reduce)等,以完成更杂乱的数据处理和转化。而过滤操作自身的长处在于,能够高效地对大型数据流进行挑选,然后进步程序的功用和功率。

映射操作(map)

映射操作(map)是 Stream API 中的一种常用操作办法,它承受一个 Function 函数作为参数,用于对 Stream 中的每个元素进行映射转化,生成一个新的 Stream。

映射操作的语法如下:

<R> Stream<R> map(Function<? super T, ? extends R> mapper)

其间,T 表明原始 Stream 的元素类型,R 表明映射后的 Stream 的元素类型,mapper 是一个函数式接口 Function 的实例,它的泛型参数别离是原始 Stream 元素的类型和映射后的元素类型。

运用映射操作能够对 Stream 中的元素逐一进行处理或转化,然后取得一个新的 Stream。这个过程一般触及对每个元素运用传入的函数,依据函数的回来值来构建新的元素。

下面是一个示例,演示怎么运用映射操作将一个字符串流中的每个字符串转化为其长度:

Stream<String> stream = Stream.of("apple", "banana", "cherry");
Stream<Integer> mappedStream = stream.map(s -> s.length());
mappedStream.forEach(System.out::println); // 输出成果: 5 6 6

在这个示例中,咱们首要创立了一个包括字符串的 Stream,并调用 map() 办法传入一个 Lambda 表达式 s -> s.length(),表明要将每个字符串转化为其长度。然后经过 forEach() 办法遍历输出成果。

需求留意的是,映射操作回来的是一个新的 Stream 实例,原始的 Stream 不会遭到改动。这也是 Stream 操作办法的一个重要特色,它们一般回来一个新的 Stream 实例,以便进行链式调用和组合多个操作过程。

在实践运用中,映射操作能够与其他操作办法结合运用,如过滤(filter)、排序(sorted)、归约(reduce)等,以完成更杂乱的数据处理和转化。而映射操作自身的长处在于,能够经过简略的函数变换完成对原始数据的转化,削减了繁琐的循环操作,进步了代码的可读性和保护性。

需求留意的是,映射操作或许引发空指针反常(NullPointerException),因而在履行映射操作时,应保证原始 Stream 中不包括空值,并依据具体状况进行空值处理。

排序操作(sorted)

排序操作(sorted)是 Stream API 中的一种常用操作办法,它用于对 Stream 中的元素进行排序。排序操作能够按照天然次序或许运用自界说的比较器进行排序。

排序操作的语法如下:

Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)

第一种语法办法中,sorted() 办法会依据元素的天然次序进行排序。假如元素完成了 Comparable 接口并且具有天然次序,那么能够直接调用该办法进行排序。

第二种语法办法中,sorted(Comparator<? super T> comparator) 办法承受一个比较器(Comparator)作为参数,用于指定元素的排序规则。经过自界说比较器,能够对非 Comparable 类型的目标进行排序。

下面是一个示例,演示怎么运用排序操作对一个字符串流进行排序:

Stream<String> stream = Stream.of("banana", "apple", "cherry");
Stream<String> sortedStream = stream.sorted();
sortedStream.forEach(System.out::println); // 输出成果: apple banana cherry

在这个示例中,咱们首要创立了一个包括字符串的 Stream,并直接调用 sorted() 办法进行排序。然后经过 forEach() 办法遍历输出成果。

需求留意的是,排序操作回来的是一个新的 Stream 实例,原始的 Stream 不会遭到改动。这也是 Stream 操作办法的一个重要特色,它们一般回来一个新的 Stream 实例,以便进行链式调用和组合多个操作过程。

在实践运用中,排序操作能够与其他操作办法结合运用,如过滤(filter)、映射(map)、归约(reduce)等,以完成更杂乱的数据处理和转化。排序操作自身的长处在于,能够将数据按照特定的次序排列,便于查找、比较和分析。

需求留意的是,排序操作或许会影响程序的功用,特别是关于大型数据流或许杂乱的排序规则。因而,在实践运用中,需求依据具体状况进行权衡和优化,挑选适宜的算法和数据结构来进步排序的功率。

切断操作(limit 和 skip)

切断操作(limit和skip)是 Stream API 中常用的操作办法,用于在处理流的过程中对元素进行切断。

  1. limit(n):保存流中的前n个元素,回来一个包括最多n个元素的新流。假如流中元素少于n个,则回来原始流。
  2. skip(n):越过流中的前n个元素,回来一个包括剩余元素的新流。假如流中元素少于n个,则回来一个空流。

下面别离具体介绍这两个办法的运用。

limit(n) 办法示例:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> limitedStream = stream.limit(3);
limitedStream.forEach(System.out::println); // 输出成果: 1 2 3

在这个示例中,咱们创立了一个包括整数的 Stream,并调用 limit(3) 办法来保存前三个元素。然后运用 forEach() 办法遍历输出成果。

skip(n) 办法示例:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> skippedStream = stream.skip(2);
skippedStream.forEach(System.out::println); // 输出成果: 3 4 5

在这个示例中,咱们创立了一个包括整数的 Stream,并调用 skip(2) 办法来越过前两个元素。然后运用 forEach() 办法遍历输出成果。

需求留意的是,切断操作回来的是一个新的 Stream 实例,原始的 Stream 不会遭到改动。这也是 Stream 操作办法的一个重要特色,它们一般回来一个新的 Stream 实例,以便进行链式调用和组合多个操作过程。

切断操作在处理大型数据流或需求对数据进行切分和分页显现的场景中非常有用。经过约束或越过指定数量的元素,能够控制数据的巨细和范围,进步程序的功用并削减不必要的核算。

需求留意的是,在运用切断操作时需求留意流的有界性。假如流是无界的(例如 Stream.generate()),那么运用 limit() 办法或许导致程序堕入无限循环,而运用 skip() 办法则没有意义。

四、Stream 的终端操作

forEach 和 peek

forEach和peek都是Stream API中用于遍历流中元素的操作办法,它们在处理流的过程中供给了不同的功用和运用场景。

  1. forEach:
    forEach是一个终端操作办法,它承受一个Consumer函数作为参数,对流中的每个元素履行该函数。它没有回来值,因而无法将操作成果传递给后续操作。forEach会遍历整个流,对每个元素履行相同的操作。

示例代码:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
     .forEach(System.out::println);

这个示例中,咱们创立了一个包括字符串的List,并经过stream()办法将其转化为流。然后运用forEach办法遍历输出每个元素的值。

  1. peek:
    peek是一个中心操作办法,它承受一个Consumer函数作为参数,对流中的每个元素履行该函数。与forEach不同的是,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转化为流,并经过map办法将每个元素转化为大写字母。然后运用peek办法在转化之前输出每个元素的值。最终经过collect办法将元素搜集到一个新的List中。

需求留意的是,无论是forEach仍是peek,它们都是用于在流的处理过程中履行操作。差异在于forEach是终端操作,不回来任何成果,而peek是中心操作,能够和其他操作办法进行组合和链式调用。

依据运用场景和需求,挑选运用forEach或peek来遍历流中的元素。假如只是需求遍历输出元素,不需求操作成果,则运用forEach。假如需求在遍历过程中履行一些其他操作,并将元素传递给后续操作,则运用peek。

聚合操作(reduce 和 collect)

reduce和collect都是Stream API中用于聚合操作的办法,它们能够将流中的元素进行汇总、核算和搜集。

  1. reduce:
    reduce是一个终端操作办法,它承受一个BinaryOperator函数作为参数,对流中的元素逐一进行兼并操作,终究得到一个成果。该办法会将流中的第一个元素作为初始值,然后将初始值与下一个元素传递给BinaryOperator函数进行核算,得到的成果再与下一个元素进行核算,以此类推,直到遍历完一切元素。

示例代码:

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()办法将其转化为流。然后运用reduce办法对流中的元素进行求和操作,将每个元素顺次相加,得到成果15。

  1. collect:
    collect是一个终端操作办法,它承受一个Collector接口的完成作为参数,对流中的元素进行搜集和汇总的操作。Collector接口界说了一系列用于聚合操作的办法,例如搜集元素到List、Set、Map等容器中,或进行字符串衔接、分组、计数等操作。

示例代码:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
String joinedNames = names.stream()
                          .collect(Collectors.joining(", "));
System.out.println(joinedNames); // 输出成果: Alice, Bob, Charlie

在这个示例中,咱们创立了一个包括字符串的List,并经过stream()办法将其转化为流。然后运用collect办法将流中的元素衔接成一个字符串,每个元素之间运用逗号和空格分隔。

需求留意的是,reduce和collect都是终端操作,它们都会触发流的遍历和处理。不同的是,reduce办法用于对流中的元素进行累积核算,得到一个终究成果;而collect办法用于对流中的元素进行搜集和汇总,得到一个容器或其他自界说的成果。

在挑选运用reduce仍是collect时,能够依据具体需求和操作类型来决议。假如需求对流中的元素进行某种核算和兼并操作,得到一个成果,则运用reduce。假如需求将流中的元素搜集到一个容器中,进行汇总、分组、计数等操作,则运用collect。

匹配操作(allMatch、anyMatch 和 noneMatch)

在 Stream API 中,allMatch、anyMatch 和 noneMatch 是用于进行匹配操作的办法,它们能够用来查看流中的元素是否满意特定的条件。

  1. allMatch:
    allMatch 办法用于判别流中的一切元素是否都满意给定的条件。当流中的一切元素都满意条件时,回来 true;假如存在一个元素不满意条件,则回来 false。

示例代码:

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() 办法将其转化为流。然后运用 allMatch 办法判别流中的元素是否都是偶数。因为列表中存在奇数,所以回来 false。

  1. anyMatch:
    anyMatch 办法用于判别流中是否存在至少一个元素满意给定的条件。当流中至少有一个元素满意条件时,回来 true;假如没有元素满意条件,则回来 false。

示例代码:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
boolean hasEven = numbers.stream()
                         .anyMatch(n -> n % 2 == 0);
System.out.println(hasEven); // 输出成果: true

在这个示例中,咱们创立了一个包括整数的 List,并经过 stream() 办法将其转化为流。然后运用 anyMatch 办法判别流中是否存在偶数。因为列表中存在偶数,所以回来 true。

  1. noneMatch:
    noneMatch 办法用于判别流中的一切元素是否都不满意给定的条件。当流中没有元素满意条件时,回来 true;假如存在一个元素满意条件,则回来 false。

示例代码:

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() 办法将其转化为流。然后运用 noneMatch 办法判别流中的元素是否都对错负数。因为列表中的元素都对错负数,所以回来 true。

需求留意的是,allMatch、anyMatch 和 noneMatch 都是终端操作,它们会遍历流中的元素直到满意条件或处理完一切元素。在功用上,allMatch 和 noneMatch 在第一个不匹配的元素处能够当即回来成果,而 anyMatch 在找到第一个匹配的元素时就能够回来成果。

查找操作(findFirst 和 findAny)

在 Stream API 中,findFirst 和 findAny 是用于查找操作的办法,它们能够用来从流中获取满意特定条件的元素。

  1. findFirst:
    findFirst 办法用于回来流中的第一个元素。它回来一个 Optional 目标,假如流为空,则回来一个空的 Optional;假如流非空,则回来流中的第一个元素的 Optional。

示例代码:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Optional<String> first = names.stream()
                              .findFirst();
first.ifPresent(System.out::println); // 输出成果: Alice

在这个示例中,咱们创立了一个包括字符串的 List,并经过 stream() 办法将其转化为流。然后运用 findFirst 办法获取流中的第一个元素,并运用 ifPresent 办法判别 Optional 是否包括值,并进行相应的处理。

  1. findAny:
    findAny 办法用于回来流中的恣意一个元素。它回来一个 Optional 目标,假如流为空,则回来一个空的 Optional;假如流非空,则回来流中的恣意一个元素的 Optional。在次序流中,一般会回来第一个元素;而在并行流中,因为多线程的处理,或许回来不同的元素。

示例代码:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> any = numbers.stream()
                               .filter(n -> n % 2 == 0)
                               .findAny();
any.ifPresent(System.out::println); // 输出成果: 2 或 4(取决于并行处理的成果)

在这个示例中,咱们创立了一个包括整数的 List,并经过 stream() 办法将其转化为流。然后运用 filter 办法挑选出偶数,再运用 findAny 办法获取恣意一个偶数,最终运用 ifPresent 办法判别 Optional 是否包括值,并进行相应的处理。

需求留意的是,findAny 在并行流中会更有优势,因为在多线程处理时,能够回来最早找到的元素,进步功率。而在次序流中,findAny 的功用与 findFirst 恰当。

核算操作(count、max 和 min)

在 Stream API 中,count、max 和 min 是用于核算操作的办法,它们能够用来获取流中元素的数量、最大值和最小值。

  1. count:
    count 办法用于回来流中元素的数量。它回来一个 long 类型的值,表明流中的元素个数。

示例代码:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
long count = numbers.stream()
                    .count();
System.out.println(count); // 输出成果: 5

在这个示例中,咱们创立了一个包括整数的 List,并经过 stream() 办法将其转化为流。然后运用 count 办法获取流中元素的数量,并将成果输出。

  1. max:
    max 办法用于回来流中的最大值。它回来一个 Optional 目标,假如流为空,则回来一个空的 Optional;假如流非空,则回来流中的最大值的 Optional。

示例代码:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> max = numbers.stream()
                               .max(Integer::compareTo);
max.ifPresent(System.out::println); // 输出成果: 5

在这个示例中,咱们创立了一个包括整数的 List,并经过 stream() 办法将其转化为流。然后运用 max 办法获取流中的最大值,并运用 ifPresent 办法判别 Optional 是否包括值,并进行相应的处理。

  1. min:
    min 办法用于回来流中的最小值。它回来一个 Optional 目标,假如流为空,则回来一个空的 Optional;假如流非空,则回来流中的最小值的 Optional。

示例代码:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> min = numbers.stream()
                               .min(Integer::compareTo);
min.ifPresent(System.out::println); // 输出成果: 1

在这个示例中,咱们创立了一个包括整数的 List,并经过 stream() 办法将其转化为流。然后运用 min 办法获取流中的最小值,并运用 ifPresent 办法判别 Optional 是否包括值,并进行相应的处理。

这些核算操作办法供给了一种便捷的办法来对流中的元素进行数量、最大值和最小值的核算。经过回来 Optional 目标,能够防止空指针反常。

五、并行流

什么是并行流

并行流是 Java 8 Stream API 中的一个特性。它能够将一个流的操作在多个线程上并行履行,以进步处理许多数据时的功用。

在传统的次序流中,一切的操作都是在单个线程上按照次序履行的。而并行流则会将流的元素分红多个小块,并在多个线程上并行处理这些小块,最终将成果兼并起来。这样能够充分运用多核处理器的优势,加快数据处理的速度。

要将一个次序流通化为并行流,只需调用流的 parallel() 办法即可。示例代码如下所示:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
       .parallel()
       .forEach(System.out::println);

在这个示例中,咱们创立了一个包括整数的 List,并经过 stream() 办法将其转化为流。接着调用 parallel() 办法将流通化为并行流,然后运用 forEach 办法遍历流中的元素并输出。

需求留意的是,并行流的运用并不总是适合一切状况。并行流的优势首要体现在数据量较大、处理时间较长的场景下。关于小规划数据和简略的操作,次序流或许愈加高效。在挑选运用并行流时,需求依据具体状况进行评价和测验,以保证取得最佳的功用。

此外,还需求留意并行流在某些状况下或许引进线程安全的问题。假如多个线程同时访问同享的可变状况,或许会导致数据竞赛和不确定的成果。因而,在处理并行流时,应当防止同享可变状况,或选用恰当的同步办法来保证线程安全。

怎么运用并行流进步功用

运用并行流能够经过运用多线程并行处理数据,然后进步程序的履行功用。下面是一些运用并行流进步功用的常见办法:

  1. 创立并行流:要创立一个并行流,只需在普通流上调用 parallel() 办法。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> parallelStream = numbers.parallelStream();
    
  2. 运用任务并行性:并行流会将数据分红多个小块,并在多个线程上并行处理这些小块。这样能够充分运用多核处理器的优势。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    numbers.parallelStream()
           .map(n -> compute(n)) // 在多个线程上并行处理核算
           .forEach(System.out::println);
    

    在这个示例中,运用 map 办法对流中的每个元素进行核算。因为并行流的特性,核算操作会在多个线程上并行履行,进步了核算的功率。

  3. 防止同享可变状况:在并行流中,多个线程会同时操作数据。假如同享可变状况(如全局变量)或许导致数据竞赛和不确定的成果。因而,防止在并行流中运用同享可变状况,或许采纳恰当的同步办法来保证线程安全。

  4. 运用适宜的操作:一些操作在并行流中的功用体现更好,而另一些操作则或许导致功用下降。一般来说,在并行流中运用依据聚合的操作(如 reducecollect)和无状况转化操作(如 mapfilter)的功用较好,而有状况转化操作(如 sorted)或许会导致功用下降。

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    // good performance
    int sum = numbers.parallelStream()
                     .reduce(0, Integer::sum);
    // good performance
    List<Integer> evenNumbers = numbers.parallelStream()
                                       .filter(n -> n % 2 == 0)
                                       .collect(Collectors.toList());
    // potential performance degradation
    List<Integer> sortedNumbers = numbers.parallelStream()
                                         .sorted()
                                         .collect(Collectors.toList());
    

    在这个示例中,reducefilter 的操作在并行流中具有杰出的功用,而 sorted 操作或许导致功用下降。

除了上述办法,还应依据具体状况进行评价和测验,并行流是否能够进步功用。有时候,并行流的开支(如线程的创立和毁掉、数据切开和兼并等)或许超过了其带来的功用进步。因而,在挑选运用并行流时,应该依据数据量和操作杂乱度等因素进行综合考虑,以保证取得最佳的功用进步。

并行流的适用场景和留意事项

  1. 大规划数据集:当需求处理大规划数据集时,运用并行流能够充分运用多核处理器的优势,进步程序的履行功率。并行流将数据切分红多个小块,并在多个线程上并行处理这些小块,然后缩短了处理时间。

  2. 杂乱的核算操作:关于杂乱的核算操作,运用并行流能够加快核算过程。因为并行流能够将核算操作分配到多个线程上并行履行,因而能够有效地运用多核处理器的核算才能,进步核算的速度。

  3. 无状况转化操作:并行流在履行无状况转化操作(如 mapfilter)时体现较好。这类操作不依赖于其他元素的状况,每个元素的处理是相互独立的,能够很容易地进行并行处理。

并行流的留意事项包括:

  1. 线程安全问题:并行流的操作是在多个线程上并行履行的,因而需求留意线程安全问题。假如多个线程同时访问同享的可变状况,或许会导致数据竞赛和不确定的成果。在处理并行流时,应防止同享可变状况,或许选用恰当的同步办法来保证线程安全。

  2. 功用评价和测验:并行流的功用进步并不总是显着的。在挑选运用并行流时,应依据具体状况进行评价和测验,以保证取得最佳的功用进步。有时,并行流的开支(如线程的创立和毁掉、数据切开和兼并等)或许超过了其带来的功用进步。

  3. 并发操作约束:某些操作在并行流中的功用体现或许较差,或许或许导致成果呈现错误。例如,在并行流中运用有状况转化操作(如 sorted)或许导致功用下降或成果呈现错误。在运用并行流时,应留意防止这类操作,或许在需求时采纳恰当的处理办法。

  4. 内存消耗:并行流需求将数据分红多个小块进行并行处理,这或许导致额外的内存消耗。在处理大规划数据集时,应保证体系有满足的内存来支撑并行流的履行,以防止内存溢出等问题。

六、实践运用示例

运用 Stream 处理调集数据

  1. 挑选出长度大于等于5的字符串,并打印输出:
List<String> list = Arrays.asList("apple", "banana", "orange", "grapefruit", "kiwi");
list.stream()
    .filter(s -> s.length() >= 5)
    .forEach(System.out::println);

输出成果:

banana
orange
grapefruit
  1. 将调集中的每个字符串转化为大写,并搜集到新的列表中:
List<String> list = Arrays.asList("apple", "banana", "orange", "grapefruit", "kiwi");
List<String> resultList = list.stream()
                              .map(String::toUpperCase)
                              .collect(Collectors.toList());
System.out.println(resultList);

输出成果:

[APPLE, BANANA, ORANGE, GRAPEFRUIT, KIWI]
  1. 核算调集中以字母”a”开头的字符串的数量:
List<String> list = Arrays.asList("apple", "banana", "orange", "grapefruit", "kiwi");
long count = list.stream()
                 .filter(s -> s.startsWith("a"))
                 .count();
System.out.println(count);

输出成果:

1
  1. 运用并行流来进步处理速度,挑选出长度小于等于5的字符串,并打印输出:
List<String> list = Arrays.asList("apple", "banana", "orange", "grapefruit", "kiwi");
list.parallelStream()
    .filter(s -> s.length() <= 5)
    .forEach(System.out::println);

输出成果:

apple
kiwi
  1. 运用 Stream 对调集中的整数求和:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                 .mapToInt(Integer::intValue)
                 .sum();
System.out.println(sum);

输出成果:

15

以上示例展现了怎么运用 Stream 对调集数据进行挑选、转化、核算等操作。经过链式调用 Stream 的中心操作和终端操作。

运用 Stream 进行文件操作

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class FileStreamExample {
    public static void main(String[] args) {
        String fileName = "file.txt";
        // 读取文件内容并创立 Stream
        try (Stream<String> stream = Files.lines(Paths.get(fileName))) {
            // 打印文件的每一行内容
            stream.forEach(System.out::println);
            // 核算文件的行数
            long count = stream.count();
            System.out.println("总行数:" + count);
            // 挑选包括关键词的行并打印输出
            stream.filter(line -> line.contains("keyword"))
                .forEach(System.out::println);
            // 将文件内容转化为大写并打印输出
            stream.map(String::toUpperCase)
                .forEach(System.out::println);
            // 将文件内容搜集到 List 中
            List<String> lines = stream.collect(Collectors.toList());
            System.out.println(lines);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上面的代码中,首要指定了要读取的文件名 file.txt。然后运用 Files.lines() 办法读取文件的每一行内容,并创立一个 Stream 目标。接下来,咱们对 Stream 进行一些操作:

  • 运用 forEach() 办法打印文件的每一行内容。
  • 运用 count() 办法核算文件的行数。
  • 运用 filter() 办法挑选出包括关键词的行,并打印输出。
  • 运用 map() 办法将文件内容转化为大写,并打印输出。
  • 运用 collect() 办法将文件内容搜集到 List 中。

请依据实践需求修改代码中的文件名、操作内容和成果处理办法。需求留意的是,在运用完 Stream 后,应及时封闭文件资源,能够运用 try-with-resources 句子块来主动封闭文件。别的,请处理或许呈现的 IOException 反常。

运用 Stream 完成数据转化和挑选

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Amy", "Bob", "Charlie", "David", "Eva");
        // 转化为大写并挑选出长度大于3的名称
        List<String> result = names.stream()
                                   .map(String::toUpperCase)
                                   .filter(name -> name.length() > 3)
                                   .collect(Collectors.toList());
        // 打印成果
        result.forEach(System.out::println);
    }
}

在上述代码中,咱们首要创立了一个包括一些姓名的列表。然后运用 Stream 对列表进行操作:

  • 运用 stream() 办法将列表转化为一个 Stream。
  • 运用 map() 办法将每个名称转化为大写。
  • 运用 filter() 办法挑选出长度大于3的名称。
  • 运用 collect() 办法将挑选后的成果搜集到一个新的列表中。

最终,咱们运用 forEach() 办法打印成果列表中的每个名称。