带你了解 Stream 的使用,提升集合开发效率

2023年 8月 21日 80.8k 0

当涉及 Java 编程时,Java Stream 是一个功能强大且高效的工具,用于处理集合数据。它提供了一种声明式的方式来操作数据,可以显著简化代码并提高可读性。在本文中,我们将深入探讨 Java Stream,介绍其基本概念、常用操作和用例。

什么是 Java Stream

Java Stream 是 Java 8 引入的一种新的抽象层,用于处理集合数据(如列表、数组等)。它允许你以一种更简洁、更声明式的方式对数据进行操作,而无需编写冗长的循环和条件语句。

其核心类库主要改进了对集合类的 API 和新增 Stream 操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中,能让代码更加简洁,极大地简化了集合的处理操作,提高了开发的效率和生产力。

同时 Stream 不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel 等。在 Stream 中的操作每一次都会产生新的流,内部不会像普通集合操作一样立刻获取值,而是惰性取值,只有等到用户真正需要结果的时候才会执行。

通过使用 Stream,你可以对数据执行各种操作,如筛选、映射、排序、聚合等。

集合操作与 Stream 操作对比

集合操作

处理方式

集合操作是通过迭代和循环来处理集合中的元素。你需要编写显式的循环代码,手动访问和操作每个元素。

适用场景

集合操作适用于简单的数据处理需求,或者在需要直接操作集合内部的情况下。它在处理小型数据集时可能较为方便。

优点

  • 直观:集合操作可以更直观地展示数据的处理过程。
  • 熟悉:如果你对循环和迭代操作非常熟悉,可能更容易上手。

缺点

  • 冗余:集合操作通常需要编写较多的循环和条件语句,导致代码冗余。
  • 低效:在大数据集上,集合操作可能会导致性能问题,因为它需要显式迭代每个元素。

Stream 操作

处理方式

Stream 操作使用声明式的方式来操作集合数据。你可以链式调用多个操作,以流水线的方式对数据进行处理。

适用场景

Stream 操作适用于需要对数据进行筛选、转换、排序、聚合等复杂操作的情况。它在处理大型数据集和需要并行处理的情况下更为有效。

优点

  • 简洁:Stream 操作可以以更简洁的方式表达数据处理逻辑,减少了循环和条件的使用。
  • 高效:Stream 操作可以在内部进行优化,如并行处理,从而在大数据集上提供更高的性能。

缺点

  • 学习曲线:如果你对 Stream 操作不熟悉,可能需要一些时间来学习其概念和语法。
  • 不适合直接操作集合:Stream 操作通常不直接修改原始集合,而是生成新的 Stream 或最终结果。

案例

本示例以操作订单金额集合为例,查询金额小于 100 的订单,并且根据创建时间进行排序,得到订单标题,生成新集合

public class List {

  public static void main(String[] args) {

    java.util.List orders = new ArrayList();
    orders.add( new Order(1, "水果", 18, "2023-08-13"));
    orders.add( new Order(2, "衣服", 59, "2023-08-12"));
    orders.add( new Order(3, "日用品", 28, "2023-08-11"));
    orders.add( new Order(4, "电子产品", 8999, "2023-08-10"));
    orders.add( new Order(5, "图书", 128, "2023-08-09"));
    java.util.List result = new ArrayList();

    // 过滤出价格大于100的订单
    for (Order order : orders) {
      if (order.getPrice() > 100){
        result.add(order);
      }
    }

    // 按照日期进行排序
    Collections.sort(result,new Comparator(){
      @Override
      public int compare(Order o1, Order o2) {
        return Integer.compare(o1.getDate(),o2.getDate());
      }
    });

    // 取出订单名称
    ArrayList names = new ArrayList();

    for (Order order : result) {
      names.add(order.getName());
    }
    // 打印
    System.out.println(names.toString());

  }

} 

上述示例代码代码中产生了两个多余的声明,result 和 names,这两个的声明在整个过程中的作用是作为一个中间数据存储容器,而且还需要将整体的操作多次遍历。

那么通过 Stream 流是如何处理上述数据的,请看下面示例

public class Stream {
  public static void main(String[] args) {

    java.util.List orders = new ArrayList();
    orders.add( new Order(1, "水果", 18, "2023-08-13"));
    orders.add( new Order(2, "衣服", 59, "2023-08-12"));
    orders.add( new Order(3, "日用品", 28, "2023-08-11"));
    orders.add( new Order(4, "电子产品", 8999, "2023-08-10"));
    orders.add( new Order(5, "图书", 128, "2023-08-09"));

    // 直接遍历输出
    orders.stream()
        .filter(order -> order.getPrice() > 100) // 过滤出价格大于100的订单
        .sorted(Comparator.comparing(Order::getDate)) // 按照日期进行排序
        .map(Order::getName)// 取出订单名称
        .forEach(System.out::println); // 遍历输出

    // 得到新的集合进行输出
    List result = orders.stream()
        .filter(order -> order.getPrice() > 100).sorted(Comparator.comparing(Order::getDate))
        .map(Order::getName).collect(Collectors.toList());
    System.out.println(result);
  }
}

通过上述代码的执行,可以发现无需再去定义过多的冗余变量。我们可以将多个操作组成一个调用链,形成数据处理流水线。在减少代码量的同时也更加的清晰易懂。

并且对于现在调用的方法,本身都是一种高层次构件,与线程模型无关。因此在并行使用中,开发者们无需再去操心线程和锁了。Stream内部都已经做好了。

更好的理解 Sream 流操作

把 Stream 流操作理解成数据库的查询操作

  • 集合=数据表
  • 元素=表中的每条数据
  • 属性=数据列
  • 流 API = SQL 查询

Stream 流主要思想

Stream 流思想就是将集合的操作由传统的 for 循环式(外部迭代)转变为 Stream 流式操作(内部迭代)

外部迭代

所有的集合迭代所及都是在我们自己编写的代码中,所以这种显式的方式称之为外部迭代。其主要关注于数据本身。并且一般都是串行的。

1)for 循环是串行的,而且必须按照集合中元素的顺序进行依次处理,要想改造成并行的话,需要修改每个for循环
2)使用是及早求值,返回的是另一个值或空。使用性能上存在一点点的瑕疵
3)易读性不好,如果for中嵌套大量循环与功能代码,阅读起来简直是灾难

内部迭代

而内部迭代来说,它所操作的就是不是一个集合了,而是一个流。它会将所有的操作融合在流中,由其在内部进行处
理,这种隐式的方式称之为内部迭代。并且内部迭代支持并行处理,更利于集合操作的性能优化。其关注与对数据的计算。

Stream 操作详解

Stream 流接口中定义了许多对于集合的操作方法,总的来说分为以下两大类

  • 中间操作:会返回一个流,通过这种方式可以将多个中间操作连接起来,形成一个调用链,从而转换为另外一个流。除非调用链最后存在一个终端操作,否则中间操作对流不会进行任何结果处理。
  • 终端操作:会返回一个具体的结果,比如 boolean、list、integer 等类型结果。

image.png

筛选操作

对于集合的操作,经常性的会涉及到对于集中符合条件的数据筛选,Stream 中对于数据筛选两个常见的API:filter(过滤)、distinct(去重)

filter

filterStream 中的一个中间操作,它接受一个 Predicate 参数,用于筛选出符合条件的元素。Predicate 是一个函数式接口,用于表示一个条件判断操作。

使用 filter 操作筛选元素
import java.util.List;
import java.util.stream.Collectors;

public class StreamFilterExample {
    public static void main(String[] args) {
        List numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        List evenNumbers = numbers.stream()
                .filter(num -> num % 2 == 0)
                .collect(Collectors.toList());
        System.out.println("Even numbers: " + evenNumbers);
    }
}

在上面的示例中,我们创建了一个整数列表 numbers,然后使用 stream 方法将其转换为一个流。接着,我们使用 filter 操作筛选出偶数,最后通过 collect 方法将结果收集到一个新的列表中。

自定义筛选条件

你可以根据自己的需求定义不同的筛选条件。例如,筛选出字符串长度大于 5 的元素:

import java.util.List;
import java.util.stream.Collectors;

public class StreamFilterExample {

    public static void main(String[] args) {
        List words = List.of("apple", "banana", "cherry", "date", "elderberry");
        List longWords = words.stream()
                .filter(word -> word.length() > 5)
                .collect(Collectors.toList());
        System.out.println("Long words: " + longWords);
    }
}
源码解析
    @Override
    public final Stream filter(Predicate upstream,
                                        long skip, long limit) {
        if (skip < 0)
            throw new IllegalArgumentException("Skip must be non-negative: " + skip);

        return new ReferencePipeline.StatefulOp(upstream, StreamShape.REFERENCE,
                                                      flags(limit)) {
            Spliterator unorderedSkipLimitSpliterator(Spliterator s,
                                                         long skip, long limit, long sizeIfKnown) {
                if (skip  n * n)
        .collect(Collectors.toList());
    }
}

上述代码中,map() 方法将集合中的每个元素进行平方计算,并将计算结果组成一个新的集合。

源码分析

image.png

    public final  Stream map(Function

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论