Java8 Stream API全面解析——高效流式编程的秘诀

文章目录

  • 什么是 Stream Api?
  • 快速入门
  • 流的操作
    • 创建流
    • 中间操作
      • filter 过滤
      • map 数据转换
      • flatMap 合并流
      • distinct 去重
      • sorted 排序
      • limit 限流
      • skip 跳过
      • peek 操作
    • 终结操作
      • forEach 遍历
      • forEachOrdered 有序遍历
      • count 统计数量
      • min 最小值
      • max 最大值
      • reduce 聚合
      • collect 收集
      • anyMatch 任意匹配
      • allMatch 全匹配
      • noneMatch 全不匹配
      • findFirst 查找第一个
      • findAny 查找任意一个

什么是 Stream Api?

Stream流的由来可以追溯到函数式编程语言,特别是Haskell语言的概念。函数式编程强调以函数为基本构建块,并通过组合和转换函数来操作数据。

Java 8引入了Lambda表达式,使得函数式编程在Java中更加方便和实用。为了能够更好地支持函数式编程的思想,Java 8也引入了Stream流这个概念。

Stream流的设计目标是提供一种高效且易于使用的方式来对集合数据进行处理。它的设计灵感来源于Unix Shell和函数式编程语言中的管道操作符(|)。Stream流可以看作是对集合数据进行流式操作的抽象,它将数据的处理过程抽象成一系列的操作步骤,可以链式地进行操作。

通过使用Stream流,我们可以以一种声明式的方式来描述对数据的操作,而无需关心底层的实现细节。这样的好处是我们可以编写更简洁、可读性更高的代码,并且Stream API还可以自动优化并行执行,提高运行效率。

因此,Stream流的引入使得Java语言更加接近函数式编程的理念,提供了一种更现代化、高效的数据处理方式。它的出现大大简化了对集合数据的处理,使得代码更加简洁、易读,并且提供了更好的性能。

Stream 流可以让我们以一种声明式的方式对集合数据进行操作,从而简化代码并提高代码可读性和可维护性。同时,在使用流时还可以结合Lambda表达式,进一步简化代码。

Stream流的优点:

  1. 代码简洁:使用Stream API可以用更少的代码实现相同的功能,使代码更加简洁、易读。

  2. 并行支持:Stream可以自动优化并行执行,可以利用多核CPU提高运行效率。

  3. 延迟执行:Stream支持延迟执行,只有在需要输出结果的时候才会进行计算,可以减少一部分不必要的计算。

Lambda表达式的优点:

  1. 简洁高效:Lambda表达式可以让我们写出更加简洁高效的代码。

  2. 可读性好:Lambda表达式可以让代码变得更加易读,减少了冗余代码。

  3. 面向函数编程:Lambda表达式可以让Java开发者更加容易地采用函数式编程的思想。

例如,假设我们有一个整数列表,要求将其中所有大于10的数加倍,然后将结果存储在另一个列表中。

使用传统的方法,可能需要写出以下代码:

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

List<Integer> newList = new ArrayList<>();

for (Integer i : list) {
    if (i > 10) {
        newList.add(i * 2);
    }
}

使用Stream和Lambda表达式则可以写出更为简洁的代码:

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

List<Integer> newList = list.stream()
							.filter(i -> i > 10)
							.map(i -> i * 2)
							.collect(Collectors.toList());

这段代码使用了Stream的filter和map方法,以及Lambda表达式,可以一行代码实现要求。

快速入门

我们先通过一个简单的快速入门案例,体验以下Stream流的强大功能。

题目: 假设我们有一个整数列表,需要筛选出其中所有大于5的数,并将它们加倍后输出。

  • 使用 for 循环的方式实现:

    public class StreamExample {
        public static void main(String[] args) {
            // 创建一个整数集合
            List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
            // 创建一个结果集合
            List<Integer> result = new ArrayList<>();
    
            //循环遍历
            for (Integer n : numbers) {
                if (n > 5) {
                    result.add(n * 2);
                }
            }
    
            // 输出结果
            System.out.println(result);
        }
    }
    
  • 使用 Stream 流的方式实现:

    public class StreamExample {
        public static void main(String[] args) {
            // 创建一个整数集合
            List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    
            // 使用Stream流对列表进行筛选和转换操作
            List<Integer> result = numbers.stream()
                    .filter(n -> n > 5) // 筛选出大于5的数
                    .map(n -> n * 2) // 将选中的数加倍
                    .collect(Collectors.toList()); // 将结果转换为新的列表
    
            // 输出结果
            System.out.println(result);
        }
    }
    

    在代码中,我们首先创建了一个整数列表 numbers,包含了1到10的整数。

    接下来,我们使用 stream() 方法将列表转换为一个流对象。然后,通过调用 filter() 方法来筛选出大于5的数,使用 map() 方法将选中的数加倍。最后使用 collect() 方法将结果转换为一个新的列表。

    最终,通过 System.out.println() 将结果输出到控制台。

流的操作

创建流

在Java 8中提供了多种方式去完成Stream流的创建,常用方式如下:

  1. 通过单列集合创建:对于实现了 java.util.Collection 接口的集合,比如 ListSet 等,可以直接调用 stream() 方法来创建流对象。

    List<String> list = Arrays.asList("apple", "banana", "orange");
    Stream<String> stream = list.stream();
    
  2. 通过数组创建:可以通过调用 Arrays.stream() 方法来创建一个数组的流对象。

    Integer[] array = {1, 2, 3, 4, 5};
    Stream<Integer> stream = Arrays.stream(array);
    
  3. 通过双列集合创建:

    • 使用 entrySet().stream() 方法:对于 Map 类型的双列集合,可以先通过 entrySet() 方法获取键值对的 Set 集合,然后再调用 stream() 方法创建流对象。

      Map<String, Integer> map = new HashMap<>();
      // map.put() 添加元素
      Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
      Stream<Map.Entry<String, Integer>> stream = entrySet.stream();
      
    • 使用 values().stream() 方法:对于 Map 类型的双列集合,也可以只获取值的集合,然后再调用 stream() 方法创建流对象。

      Map<String, Integer> map = new HashMap<>();
      // map.put() 添加元素
      Stream<Integer> stream = map.values().stream();
      
  4. 通过静态方法创建:可以通过调用 Stream.of()Stream.iterate() 来创建一个包含指定元素或无限元素的流对象。

    • Stream.of()方法:

      Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
      
    • Stream.iterate()方法:

      Stream<Integer> stream2 = Stream.iterate(0, n -> n + 2).limit(5);
      

      使用 Stream.iterate() 方法创建了一个包含无限元素的流对象,每个元素都是由前一个元素应用函数生成的。在这个例子中,流对象的第一个元素为0,然后每次通过应用lambda表达式(n -> n + 2)来生成下一个元素,即将前一个元素加上2。

      接着,使用 limit() 方法来限制流对象的元素数量,使其只包含前5个元素。最终返回一个含有5个整数的Stream流对象。

      注:由于 Stream.iterate() 方法创建的是一个无限流对象,如果不调用 limit() 方法或者其他的限制操作,那么该流对象将一直产生新的元素,直到程序耗尽内存空间并抛出异常。因此,在使用 Stream.iterate() 方法创建流对象时,一定要注意对其进行限制,以避免程序崩溃。

  5. 通过文件创建:可以通过调用 Files.lines() 方法来创建一个文件的流对象。

    Path path = Paths.get("file.txt");
    Stream<String> stream = Files.lines(path);
    

中间操作

Stream 流的中间操作是指那些对流进行转换、筛选、映射等操作,并返回一个新的流的操作。Stream 对象是惰性求值的,也就是说,在我们对 Stream 对象应用终端操作之前,中间操作并不会立即执行,只有等到终端操作触发时才会执行。这样可以提高性能,避免不必要的计算。

filter 过滤

通过使用 filter() 方法,我们可以根据自定义的条件对流进行过滤操作,只保留符合条件的元素,从而得到一个新的流。

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

filter() 方法接受一个 Predicate(谓词:对主语动作状态或特征的陈述或说明)作为参数,并返回一个包含满足条件的元素的新流。

具体来说,filter() 方法会对流中的每个元素应用给定的谓词,如果谓词返回 true,则该元素被包含在新流中;如果谓词返回 false,则该元素被过滤掉:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Predicate接口被声明为@FunctionalInterface,这意味着它可以用作 Lambda 表达式或方法引用的目标。

Predicate接口代表一个断言,用于对给定的输入进行判断。它只有一个抽象方法 test,接受一个参数并返回一个boolean值,表示输入是否满足谓词条件。

同时也意味着任何实现了Predicate接口的类或 Lambda 表达式都必须实现 test 方法,并且该方法可以在任何地方被调用或覆盖。

以下是一个示例,演示如何使用 filter() 进行过滤操作:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream()
    .filter(n->n%2==0)
    .forEach(System.out::println);

在上述示例中,我们首先创建了一个包含整数元素的列表 numbers。然后,使用 stream() 方法将其转换为流对象。接下来,我们调用 filter() 方法,并传入一个谓词 n -> n % 2 == 0用于筛选出偶数。最后,使用 forEach() 终端操作遍历过滤后的流,并打印每个元素。

执行 filter 过滤操作后,流内的元素变化如下:

image-20231103232435592

map 数据转换

Stream 接口定义的map方法的声明如下:

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

map() 方法接受一个Function(函数)类型的参数,并将流中的每个元素按照指定的映射规则进行转换,返回一个新的Stream流。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

Function接口中只有一个抽象方法apply(T t),它将一个类型为T的参数作为输入,并返回一个类型为R的结果。

具体来说,map方法将对流中的每个元素应用提供的映射函数,并将其转换为另一种类型。最后将新类型的结果组合成一个新的流对象并返回。

注:map方法只会对流中的每个元素应用映射操作,不会改变流的大小或顺序。它返回的是一个新的流,因此可以链式调用其他的流操作方法。

以下是一个示例,演示如何使用 map() 进行转换操作:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream()
    .map(n->"Number:"+n)
    .forEach(System.out::println);

在上述示例中,我们创建了一个包含整数元素的列表 numbers。然后,使用 stream() 方法将其转换为流对象。接下来,我们调用 map() 方法,并传入一个函数 n -> "Number: " + n,该函数用于将每个整数元素转换为以 "Number: " 开头的字符串。最后,使用 forEach() 终端操作遍历转换后的流,并打印每个元素。

执行 map 转换操作后,流的元素变化如下:

image-20231103233635463

flatMap 合并流

flatMap可以将一个流中的每个元素映射为另一个流,并将这些流合并成一个单独的流。

具体来说,flatMap方法接受一个将每个元素转换为流的函数,然后将所有转换后的流合并成一个单独的流。因此,它可以用于将嵌套的流平铺开来。

方法签名如下:

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

示例用法如下:

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

nestedList.stream()
    .flatMap(Collection::stream)
    .forEach(System.out::println);

在上述示例中,我们有一个嵌套列表nestedList,其中包含三个子列表。我们首先将其转换为流,然后调用flatMap方法,传递一个方法引用Collection::stream作为映射函数。该方法引用将每个子列表转换为一个流,并将所有的流合并成一个单独的流。最终,我们得到了一个包含所有元素的扁平化流flattenedStream

执行flatMap转换操作后,流的元素变化如下:

image-20231103235818392

同时flatMap方法支持数据转换:

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

nestedList.stream()
    .flatMap(ns-> ns.stream().map(n->"Number:"+n))
    .forEach(System.out::println);

distinct 去重

distinct() 返回一个包含流中不重复元素的新流。新流中的元素顺序与原始流中的元素顺序相同。

具体来说,distinct() 方法会基于元素的 equals() 方法判断元素是否重复。如果流中有多个元素与当前元素相等,则只保留其中的一个元素。其他重复元素将被过滤掉。在去重过程中,保留的是第一次出现的元素,后续重复出现的元素将被忽略。

方法签名如下:

Stream<T> distinct();

以下是一个示例,演示如何使用 distinct() 方法进行去重操作:

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

numbers.stream()
    .distinct()
    .forEach(System.out::println);

在上述示例中,我们创建了一个包含整数元素的列表 numbers,其中存在重复的元素。然后,使用 stream() 方法将其转换为流对象。接下来,我们调用 distinct() 方法,该方法会返回一个新的流,其中只包含不重复的元素。最后,使用 forEach() 终端操作遍历去重后的流,并打印每个元素。

使用 distinct() 方法后,流的元素变化如下:

image-20231104001527002

注:distinct() 方法会基于元素的 equals() 方法判断元素是否重复,因此必须保证元素的 equals() 方法正确实现,才能准确判断元素是否重复。

sorted 排序

通过调用 sorted() 方法,我们可以对流中的元素进行排序操作。并返回一个包含按自然顺序或指定比较器排序的元素的新流。

sorted() 方法有两种重载形式:

//默认排序规则
Stream<T> sorted();
//指定排序规则
Stream<T> sorted(Comparator<? super T> comparator);
  • 若调用时不传入任何参数,则会根据元素的自然顺序(即调用元素的 compareTo() 方法进行比较)完成排序。如果流中的元素不支持自然排序(即元素类型未实现 Comparable 接口或者实现了该接口但未正确实现 compareTo() 方法),则会抛出 ClassCastException 异常。
  • 若调用时传入一个比较器(Comparator)作为参数,则会使用指定的比较器对元素进行排序。

以下是两个示例,分别演示了使用自然顺序和比较器进行排序的情况:

  1. 使用自然顺序进行排序:

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

    在上述示例中,我们创建了一个包含整数元素的列表 numbers。然后,使用 stream() 方法将其转换为流对象。接下来,我们调用 sorted() 方法,该方法会返回一个新的流,其中的元素按照自然顺序进行排序。

    最后,使用 forEach() 终端操作遍历排序后的流,并打印每个元素。

    经过 sorted() 方法后,流的元素变化如下:

    image-20231104110823136

  2. 使用比较器进行排序:

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

    在上述示例中,我们创建了一个包含整数元素的列表 numbers。然后,使用 stream() 方法将其转换为流对象。接下来,我们创建了一个比较器 reverseOrder,该比较器会按逆序对元素进行排序。

    最后,我们调用 sorted() 方法,并传入比较器作为参数,返回一个新的流,其中的元素按照指定的比较器进行排序。

    经过 sorted() 方法后,流的元素变化如下:

    image-20231104111320361

注:使用sorted() 方法对流中的元素进行排序时,元素类型必须实现 Comparable 接口,或者提供比较器来指定排序规则。

limit 限流

limit() 方法用于截取流中的前 n 个元素,并返回一个新的流。该方法的语法如下:

Stream<T> limit(long maxSize)

其中,maxSize 参数指定了要截取的元素个数。

注:如果输入的流中元素的数量不足 maxSize,则返回的新流中只包含所有元素。此外,如果 maxSize 小于等于 0,或者输入的流为空,则返回的新流也将为空。

以下是一个示例,演示了如何使用 limit() 方法对流进行截取:

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

numbers.stream()
    .limit(3)
    .forEach(System.out::println);

在上述示例中,我们创建了一个包含整数元素的列表 numbers。然后,使用 stream() 方法将其转换为流对象。接下来,我们调用 limit(3) 方法,截取流中的前三个元素。

最后,使用 forEach() 终端操作遍历截取后的流,并打印每个元素。

使用 limit() 方法后,流的元素变化如下:

image-20231104111834081

skip 跳过

skip() 方法用于跳过流中的前 n 个元素,并返回一个新的流。该方法的语法如下:

Stream<T> skip(long n)

其中,n 参数指定了要跳过的元素个数。

注:如果输入的流中元素的数量不足 n,则返回的新流将为空。此外,如果 n 小于等于 0,或者输入的流为空,则返回的新流将包含原始流中的所有元素。

以下是一个示例,演示了如何使用 skip() 方法跳过流中的元素:

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

numbers.stream()
    .skip(3)
    .forEach(System.out::println);

根据你提供的代码,创建了一个包含整数元素的列表 numbers。然后,使用 stream() 方法将其转换为流对象。接下来,我们调用 skip(3) 方法,跳过流中的前三个元素。

最后,使用 forEach() 终端操作遍历跳过后的流,并打印每个元素。

使用 skip() 方法后,流的元素变化如下:

image-20231104112715970

peek 操作

peek() 方法提供了一种在流元素处理过程中插入非终端操作的机制,即在每个元素的处理过程中执行某些操作,例如调试、日志记录、统计等。

该方法的语法如下:

Stream<T> peek(Consumer<? super T> action)

其中,action 参数是一个 Consumer 函数式接口,用于定义要在流元素处理过程中执行的操作。对于每个元素,peek() 方法都会调用 action 函数,并传递该元素作为参数。

以下是一个示例,演示了如何使用 peek() 方法:

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

numbers.stream()
    .filter(n -> n % 2 == 0)
    .peek(n -> System.out.println("Found even number: " + n))
    .map(n -> n * 2)
    .forEach(System.out::println);

在上述示例中,我们创建了一个包含整数元素的列表 numbers。然后,我们使用 stream() 方法将其转换为流对象。接下来,我们使用 filter() 方法过滤出所有偶数,并使用 peek() 方法插入一条打印语句,以便在每个偶数被消耗时显示一条消息。

然后,我们使用 map() 方法对每个偶数进行乘法运算,并最终使用 forEach() 终端操作打印每个结果。

控制台打印结果:

Found even number: 2
4
Found even number: 4
8

使用 peek() 方法时,流的元素变化如下:

image-20231104113322402

终结操作

终结操作(Terminal Operation)是 Stream 流的最后一个操作,用于触发流的处理并产生最终的结果或副作用。执行终结操作后,流将会被关闭,因此在调用终结操作之后,流将不再可用。

forEach 遍历

forEach() 是 Stream 流的一个终端操作方法,用于对流中的每个元素执行指定的操作。它接受一个 Consumer 函数式接口作为参数,并将该操作应用于流中的每个元素。

forEach() 方法没有返回值,因此它只用于执行一些针对每个元素的操作,并不能产生新的流或结果。

以下是 forEach() 方法的语法:

void forEach(Consumer<? super T> action)

其中,action表示要对每个元素执行的操作,它是一个接受一个参数并且没有返回值的函数式接口。在Lambda表达式中,可以使用该参数执行自定义的操作。

以下是一个示例,演示了如何使用 forEach() 方法对流中的每个元素执行操作:

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

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

首先,我们创建了一个包含整数元素的列表 numbers。接下来,我们通过调用 stream() 方法将该列表转换为一个流对象。然后,我们使用 forEach() 方法对每个元素执行一条打印语句。

在打印语句中,我们使用了方法引用(Method Reference)的方式,即 System.out::println,它代表了一个输出流操作,将流中的每个元素输出到控制台上。也可以使用 lambda 表达式的方式,即 (x) -> System.out.println(x)

运行上述代码,控制台输出结果如下:

1
2
3
4
5

forEachOrdered 有序遍历

forEachOrdered() 方法与 forEach() 方法相似,用于对流中的每个元素执行指定的操作。但与 forEach() 不同的是,forEachOrdered() 方法能够保证操作按照流中元素的顺序依次执行。

以下是 forEachOrdered() 方法的语法:

void forEachOrdered(Consumer<? super T> action)

其中,action 参数是一个 Consumer 函数式接口,用于定义要在每个元素上执行的操作。对于流中的每个元素,forEachOrdered() 方法都会调用 action 函数,并将该元素作为参数传递给它。这些操作将按照流中元素的顺序依次执行,而不是在并行流中产生竞争条件。

以下是一个示例,演示了如何使用 forEachOrdered() 方法对流中的每个元素执行操作:

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

numbers.parallelStream()
    .forEachOrdered(System.out::println);

在上述示例中,我们创建了一个包含整数元素的列表 numbers。然后,我们使用 parallelStream() 方法将该列表转换为一个并行流对象。并行流允许并行处理流中的元素,以提高处理速度。

接下来,我们使用 forEachOrdered() 方法将并行流中的每个元素输出到控制台。

控制台输出结果如下:

1
2
3
4
5

由于我们在这里使用了并行流,而并行流的处理顺序可能会受到多线程的调度和执行时间的影响,因此如果使用 forEach() 方法,则打印顺序可能是随机的。但是,由于我们使用了 forEachOrdered() 方法,所以不会受到并行处理的影响,最终的输出顺序将与原始列表中的顺序一致。

parallelStream() 并行流中的元素变化如下:

image-20231104114959597

count 统计数量

count 用于统计 Stream 流中元素的个数。该方法返回一个 long 类型的值,表示流中元素的数量。

count的语法如下:

long count()

它返回一个long类型的值,表示流中元素的个数。

以下是一个示例,演示了如何使用 count() 方法计算一个整数流中的元素数量:

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

// 统计水果的个数
long count = fruits.stream()
                   .count();

System.out.println("水果的个数为:" + count);

首先我们创建了一个 Integer 类型的列表 numbers。然后,通过调用 stream() 方法将列表转换为顺序流。接着,使用 count() 方法来计算顺序流中的元素数量,并将结果赋值给变量 count

最后,使用 System.out.println() 方法将计算出的元素数量输出到控制台。

控制台输出结果如下:

5

注:count() 方法只能用于非并行流或无限流。如果在并行流或无限流中调用该方法,则可能会导致程序挂起或陷入死循环等不良情况。

min 最小值

min() 用于获取流中的最小值。它可以用于处理基本类型和对象类型的流。以下是 min() 方法的语法:

Optional<T> min(Comparator<? super T> comparator)

它接收一个 Comparator 对象作为参数,用于确定最小值的比较方式。返回一个 Optional 对象,表示流中的最小值(如果存在)。

下面是一个示例代码,演示了如何使用 min() 方法找到整数流中的最小值:

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

// 找到最小值
Optional<Integer> min = numbers.stream()
    .min(Integer::compareTo);

System.out.println("最小值为:" + min.get());

上述代码中,我们将一个包含五个整数的 List 转换成了 Stream 流,并使用 min 方法找到流中的最小值。通过传入 Integer 类的 compareTo 方法作为比较器,可以实现对整数的比较。最后,我们将结果输出到控制台。

经过 min() 方法后流中的元素变化如下:

image-20231104121503272

max 最大值

在 Java 中,max() 是一个流的终端操作,用于获取流中的最大值。它可以用于处理基本类型和对象类型的流。

以下是 max() 方法的语法:

Optional<T> max(Comparator<? super T> comparator)

它接收一个 Comparator 对象作为参数,用于确定最大值的比较方式。返回一个 Optional 对象,表示流中的最大值(如果存在)。

下面是一个示例代码,演示了如何使用 max() 方法找到整数流中的最大值:

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

// 找到最大值
Optional<Integer> max = numbers.stream()
    .max(Integer::compareTo);

System.out.println("最大值为:" + max.get());

上述代码中,我们将一个包含五个整数的 List 转换成了 Stream 流,并使用 max 方法找到流中的最大值。通过传入 Integer 类的 compareTo 方法作为比较器,可以实现对整数的比较。最后,我们将结果输出到控制台。

经过 min() 方法后流中的元素变化如下:

image-20231104121951037

reduce 聚合

reduce() 用于将流中的元素进行聚合操作,生成一个最终的结果。它可以用于处理基本类型和对象类型的流。

以下是 reduce() 方法的三种语法:

  • 单个参数:

    Optional<T> reduce(BinaryOperator<T> accumulator)
    

    参数含义如下:

    • accumulator 是一个函数接口 BinaryOperator<T> 的实例,定义了一个二元操作符,用于将流中的元素逐个进行操作。

    使用这种形式的 reduce() 方法时,它会将流中的元素依次与累加器进行操作,最终将所有元素聚合成一个结果。

    返回值类型是 Optional<T>,因为如果流为空,没有元素可以进行聚合操作,此时返回的是一个空的 Optional 对象。

    下面是一个示例代码,演示了如何使用带有一个参数的 reduce() 方法对整数流进行求和操作:

    List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
    
    Optional<Integer> reduce = numbers.stream()
        .reduce((a, b) -> a + b);
    
    System.out.println("Sum:" + reduce.get());
    

    在上述示例中,我们首先创建了一个整数流 numbers。然后,我们调用 reduce() 方法来对流中的元素进行求和操作。二元操作符使用 lambda 表达式 (a, b) -> a + b 进行相加操作。

    最后,打印求和结果:

    Sum:25
    

    注:reduce() 方法默认将流中的第一个元素作为初始化值,依次与后面的元素进行累加器操作。

  • 两个参数:

    T reduce(T identity, BinaryOperator<T> accumulator)
    

    参数含义如下:

    • identity 是初始值,用于处理空流的情况。
    • accumulator 是一个函数接口 BinaryOperator<T> 的实例,定义了一个二元操作符,用于将流中的元素逐个与累加器进行操作。

    使用这种形式的 reduce() 方法时,如果流中有元素,则将第一个参数作为初始值,然后依次将流中的元素与初始值进行操作。如果流为空,则直接返回初始值。

    下面是一个示例代码,演示了如何使用带有两个参数的 reduce() 方法对整数流进行求和操作:

    List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
    
    Integer reduce = numbers.stream()
        .reduce(1, Integer::sum);
    
    System.out.println("Sum:" + reduce);
    

    在上述示例中,我们首先创建了一个整数流 numbers。然后,我们调用 reduce() 方法来对流中的元素进行求和操作。初始值设置为 1,元素进行相加操作。

    最后,我们将求和结果输出到控制台:

    Sum:26
    
  • 三个参数:

    <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
    

    每个参数含义如下:

    • identity 是初始值,用于处理空流的情况。
    • accumulator 是一个函数接口 BiFunction<U, ? super T, U> 的实例,定义了一个二元操作符,用于将流中的元素逐个与累加器进行操作。
    • combiner 是一个函数接口 BinaryOperator<U> 的实例,用于在并行流的情况下,将多个部分结果进行合并

    在处理并行流时,reduce() 方法会将流分成多个部分,并发地执行累加操作。然后,使用 combiner 函数将各个部分的结果进行合并,最终生成一个最终的结果。

    下面是一个示例代码,演示了如何使用带有三个参数的 reduce() 方法对整数流进行求和操作:

    List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9);
    
    Integer reduce = numbers.parallelStream()
        					.reduce(0, (a, b) -> a + b, Integer::sum);
    
    System.out.println("Sum:" + reduce);
    

    在上述示例中,我们首先创建了一个整数流 numbers。然后,我们调用 reduce() 方法来对流中的元素进行求和操作。初始值设置为 0,二元操作符使用 lambda 表达式 (a, b) -> a + b 进行相加操作。combiner 函数使用了方法引用 Integer::sum,用于在并行流的情况下合并部分结果。

    最后,我们将求和结果输出到控制台:

    Sum:25
    

    reduce() 方法后流中的元素变化如下:

    image-20231104145241256

collect 收集

collect() 方法是用于将流中的元素收集到集合或者其他数据结构中的操作。它可以将流中的元素进行转换、分组、过滤等操作,并将结果存储到指定的集合中。

collect() 方法使用 Collector 对象来定义收集操作的行为。Collector 接口提供了一系列静态方法,可以创建常见的收集器实例,如 toList()toSet()toMap() 等。

以下是 collect() 方法的语法:

<R, A> R collect(Collector<? super T, A, R> collector)

这里的参数含义如下:

  • collector 是一个 Collector 对象,用于定义收集操作的行为。

返回值类型是根据收集器的定义而确定的。

下面是一些示例代码,展示了如何使用 collect() 方法进行常见的收集操作:

  1. 将流中的元素收集到一个列表中:

    Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
    
    List<Integer> numberList = numbers.collect(Collectors.toList());
    
    System.out.println("Number List: " + numberList);
    
  2. 将流中的元素收集到一个集合中:

    Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
    
    Set<Integer> numberSet = numbers.collect(Collectors.toSet());
    
    System.out.println("Number Set: " + numberSet);
    
  3. 将流中的元素收集到一个映射表中:

    Stream<String> names = Stream.of("Alice", "Bob", "Charlie");
    
    Map<String, Integer> nameLengthMap = names.collect(Collectors.toMap(
        name -> name,
        name -> name.length()
    ));
    
    System.out.println("Name Length Map: " + nameLengthMap);
    

anyMatch 任意匹配

anyMatch() 方法是用于检查流中是否存在满足指定条件的元素。它返回一个boolean值,表示流中是否存在匹配的元素。

以下是anyMatch()方法的语法:

boolean anyMatch(Predicate<? super T> predicate)

参数含义如下:

  • predicate 是一个Predicate函数接口的实例,用于定义匹配条件。

返回值是一个boolean值,如果流中至少有一个元素满足predicate定义的条件,则返回true,否则返回false

下面是一个示例代码,演示了如何使用anyMatch()方法来检查整数流中是否存在大于10的元素:

Stream<Integer> numbers = Stream.of(5, 8, 12, 3, 9);

boolean hasNumberGreaterThanTen = numbers.anyMatch(number -> number > 10);

System.out.println("Has Number Greater Than Ten: " + hasNumberGreaterThanTen);

在上述示例中,我们创建了一个整数流numbers。然后调用anyMatch()方法检查是否存在大于10的元素。

allMatch 全匹配

allMatch() 方法用于检查流中的所有元素是否都满足指定的条件。它返回一个布尔值,表示流中的所有元素是否都满足条件。

以下是 allMatch() 方法的语法:

boolean allMatch(Predicate<? super T> predicate)

参数含义如下:

  • predicate 是一个 Predicate 函数接口的实例,用于定义匹配条件。

返回值是一个布尔值,如果流中的所有元素都满足 predicate 定义的条件,则返回 true,否则返回 false

下面是一个示例代码,演示了如何使用 allMatch() 方法来检查整数流中的所有元素是否都为偶数:

Stream<Integer> numbers = Stream.of(2, 4, 6, 8, 10);

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

System.out.println("All Even: " + allEven);

在上述示例中,创建了一个整数流 numbers,然后调用 allMatch() 方法检查流中的所有元素是否都为偶数。

noneMatch 全不匹配

noneMatch()方法用于检查流中是否没有任何元素满足指定的条件。它返回一个boolean值,表示流中是否不存在满足条件的元素。

以下是noneMatch()方法的语法:

boolean noneMatch(Predicate<? super T> predicate)

这里的参数含义如下:

  • predicate 是一个Predicate函数接口的实例,用于定义匹配条件。

返回值是一个boolean值,如果流中没有任何元素满足predicate定义的条件,则返回true,否则返回false。

下面是一个示例代码,演示了如何使用noneMatch()方法来检查整数流中是否没有负数元素:

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);

boolean noNegativeNumbers = numbers.noneMatch(number -> number < 0);

System.out.println("No Negative Numbers: " + noNegativeNumbers);

在上述示例中,创建了一个整数流numbers,调用noneMatch()方法检查流中是否没有负数元素。

findFirst 查找第一个

findFirst() 方法用于返回流中的第一个元素(按照流的遍历顺序)。它返回一个 Optional 对象,可以用于处理可能不存在的情况。

以下是 findFirst() 方法的语法:

Optional<T> findFirst()

返回值类型是 Optional<T>,其中 T 是流中元素的类型。如果流为空,则返回一个空的 Optional 对象;否则,返回一个包含第一个元素的 Optional 对象。

下面是一个示例代码,演示了如何使用 findFirst() 方法来获取整数流中的第一个元素:

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);

Optional<Integer> firstNumber = numbers.findFirst();

if (firstNumber.isPresent()) {
    System.out.println("First Number: " + firstNumber.get());
} else {
    System.out.println("Stream is empty");
}

在上述示例中,创建了一个整数流 numbers,调用 findFirst() 方法,返回一个 Optional 对象,表示流中的第一个元素。

findAny 查找任意一个

findAny() 方法用于返回流中的任意一个元素。它返回一个 Optional 对象,可以用于处理可能不存在的情况。

以下是 findAny() 方法的语法:

Optional<T> findAny()

返回值类型是 Optional<T>,其中 T 是流中元素的类型。如果流为空,则返回一个空的 Optional 对象;否则,返回一个包含任意一个元素的 Optional 对象。

findAny() 方法与 findFirst() 方法类似,但不保证返回的是流中的第一个元素,而是返回任意一个元素。这在并行流中尤为有用,因为它可以并行处理流的不同部分,然后返回其中的任意一个元素。

下面是一个示例代码,演示了如何使用 findAny() 方法来获取整数流中的任意一个元素:

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);

Optional<Integer> anyNumber = numbers.findAny();

if (anyNumber.isPresent()) {
    System.out.println("Any Number: " + anyNumber.get());
} else {
    System.out.println("Stream is empty");
}

上述示例中,创建了一个整数流 numbers,调用 findAny() 方法,返回一个 Optional 对象,表示流中的任意一个元素。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/510734.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git源码泄露

Git 源码泄露 开发人员会使用 git 进行版本控制&#xff0c;对站点自动部署。但如果配置不当&#xff0c;可能会将 .git 文件夹直接部署到线上环境&#xff0c;这就引起了 git 泄露漏洞&#xff0c;我们可以利用这个漏洞直接获得网页源码。 确定是否存在泄漏 &#xff08;1&…

java项目基于Springboot和Vue的高校心理教育辅导系统的设计与实现

今天要和大家聊的是基于Springboot和Vue的高校心理教育辅导系统的设计与实现 &#xff01;&#xff01;&#xff01; 有需要的小伙伴可以通过文章末尾名片咨询我哦&#xff01;&#xff01;&#xff01; &#x1f495;&#x1f495;作者&#xff1a;李同学 &#x1f495;&…

大模型之路3:趟到了Llama-Factory,大神们请指点

各种AI工具和框架层出不穷&#xff0c;为开发者和研究者提供了前所未有的便利。当然了&#xff0c;也有困扰。尤其是对于动手能力越来越弱的中年油腻老程序员来说&#xff0c;更是难上加难。据说&#xff0c;嗯&#xff0c;据师弟说&#xff0c;说LlamaFactory凭借其独特的功能…

实验:基于Red Hat Enterprise Linux系统的创建磁盘和磁盘分区(一)

目录 一. 实验目的 二. 实验内容 三. 实验设计描述及实验结果 fdisk [参数] [设备] 1. 为虚拟机添加1块大小为3-5G的硬盘nvme&#xff0c;将该硬盘划分1个主分区和两个逻辑分区分别为600MB。 partprobe [选项] [设备] 2. 将主分区格式化为ext4文件系统并挂载到/自己名字命名…

Screeps Arena 游戏基础教程

一. 游戏内教程汉化1. 循环和导入&#xff08;Loop and Import&#xff09;2. 简单移动&#xff08;Simple move&#xff09;3. 首次攻击&#xff08;First Attack&#xff09;4. 爬虫的身体部分&#xff08;Creeps Bodies&#xff09;5. 存储和转移 &#xff08;Store and Tra…

合并两个单链表

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 但行前路&#xff0c;不负韶华&#…

dataloader numworkers

numworkers是加载数据的额外cpu数量&#xff08;也可以看成额外的进程&#xff09;。可以理解是&#xff1a; dataset中的getitem只能得到单个数据&#xff0c; 而numworker设置后是同时加载numwork个数据到RAM中&#xff0c;当需要数据时&#xff0c;不会重新执行getiem的方法…

代码随想录算法训练营第四十二天 | 卡码网46. 携带研究材料、416. 分割等和子集

代码随想录算法训练营第四十二天 | 卡码网46. 携带研究材料、416. 分割等和子集 卡码网46. 携带研究材料题目解法 416. 分割等和子集题目解法 感悟 卡码网46. 携带研究材料 题目 解法 题解链接 二维数组 # include <bits/stdc.h> using namespace std;int n, bagweig…

读取信息boot.bin和xclbin命令

bootgen读Boot.bin命令 johnjohn-virtual-machine:~/project_zynq/kv260_image_ubuntu22.04$ bootgen -read BOOT-k26-starter-kit-202305_2022.2.bin xclbinutil读xclbin命令 johnjohn-virtual-machine:~/project_zynq/kv260_image_ubuntu22.04$ xclbinutil -i kv260-smartca…

【Vue】vue3简介与环境配置

文章目录 项目编码规范什么是 Vue&#xff1f;安装node环境nvm针对node版本惊醒管理的工具 项目编码规范 组合式API Typescript setup(语法糖) 什么是 Vue&#xff1f; Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;…

Linux系统下安装jdk与tomcat【linux】

一、yum介绍 linux下的jdk安装以及环境配置&#xff0c;有两种常用方法&#xff1a; 1.使用yum一键安装。 2.手动安装&#xff0c;在Oracle官网下载好需要的jdk版本&#xff0c;上传解压并配置环境。 这里介绍第一种方法&#xff0c;在此之前简单了解下yum。 yum 介绍 yum&…

联系媒体要有方法莫让投稿发文章只剩一声长叹相见恨晚

曾有一位饱经世事的前辈以一句至理名言警醒世人:“人之所以领悟道理,往往不是源于抽象的道理本身,而是生活给予的实实在在的挫折教训,如同撞南墙一般的痛彻觉醒;同样,让人豁然开朗的,也不是空洞的说教,而是实实在在的人生磨砺。”这一哲理,放在我们日常工作中亦有深刻的启示作用…

困难样本挖掘:Hard Sample Mining(原理及实现)

Hard Sample Mining Hard Sample Mining&#xff0c;即困难样本挖掘&#xff0c;是目标检测中的一种常用方法。其主要思想是针对训练过程中损失较高的样本&#xff08;即那些难以被正确分类的样本&#xff09;进行挖掘&#xff0c;并将其补充到数据集中重新训练&#xff0c;以…

【Qt 学习笔记】Qt 背景介绍

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt 背景介绍 文章编号&#xff1a;Qt 学习笔记 / 01 文章目录 Qt 背景…

配置plsql链接Oracle数据库(新手)

配置plsql链接Oracle数据库 安装Oracle客户端 、安装plsql客户端并激活 配置tnsnames.ora文件&#xff08;路径D:\app\peter\Oracle\InstantClient\network\admin根据你的实际路径设置&#xff09; 配置文件如下 # tnsnames.ora Network Configuration File: D:\app\peter\O…

【CKA模拟题】一文教你用StorageClass轻松创建PV

题干 For this question, please set this context (In exam, diff cluster name) kubectl config use-context kubernetes-adminkubernetesYour task involves setting up storage components in a Kubernetes cluster. Follow these steps: Step 1: Create a Storage Class…

卡尔曼滤波笔记

资料&#xff1a;https://www.zhihu.com/question/47559783/answer/2988744371 https://www.zhihu.com/question/47559783 https://blog.csdn.net/seek97/article/details/120012667 一、基本思想 在对一个状态值进行估计的时候&#xff0c;如果想测量值更准&#xff0c;很自然…

“探秘数据结构:栈的奇妙魔力“

每日一言 兰有秀兮菊有芳&#xff0c;怀佳人兮不能忘。 —刘彻- 栈 栈的概念及结构 栈(Stack) &#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守…

vue3+vite 模板vue3-element-admin框架如何关闭当前页面跳转 tabs

使用模版: 有来开源组织 / vue3-element-admin 需要关闭的.vue 页面增加以下方法 //setup 里import {LocationQuery, useRoute, useRouter} from "vue-router"; const router useRouter(); function close() {console.log(|--router.currentRoute.value, router.cur…

【MySQL系列】使用 ALTER TABLE 语句修改表结构的方法

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…