在 Java 8 引入的众多特性中,Stream API 无疑是改变开发者代码风格的关键技术之一。它以 “声明式编程” 思想为核心,将集合数据处理从繁琐的循环遍历中解放出来,让代码更简洁、易读且易于并行化。本文将从 Stream API 的设计初衷入手,全面拆解其核心原理、中间操作与终端操作的使用逻辑、实战场景案例及性能优化技巧,帮你真正掌握这一高效数据处理工具。​

一、为什么需要 Stream API?—— 从传统集合处理的痛点说起​

在 Stream API 出现之前,Java 开发者处理集合数据(如过滤、排序、映射)时,不得不依赖for循环或Iterator迭代器,这种 “命令式编程” 方式存在诸多痛点,直接影响代码质量与开发效率。​

1.1 传统集合处理的 3 大痛点​

痛点 1:代码冗余,可读性差​

若要实现 “从用户列表中筛选出年龄大于 18 岁的用户,提取其姓名并按字母排序”,传统代码需嵌套多层循环与判断,逻辑分散在大量模板代码中:​

ja取消自动换行复制

这段代码中,“筛选、提取、排序” 的核心逻辑被循环、集合初始化、比较器等模板代码包裹,可读性差,且难以快速定位核心业务逻辑。​

痛点 2:并行处理复杂,易出错​

若要对大数据量集合进行并行处理(如多线程筛选),传统方式需手动创建线程池、拆分数据、合并结果,不仅代码复杂,还容易出现线程安全问题(如并发修改集合):​

java取消自动换行复制

这段代码不仅冗长,还需手动处理数据拆分、线程同步等问题,稍不注意就会导致数据丢失或重复。​

痛点 3:中间结果冗余,资源浪费​

传统处理方式中,每一步操作(如筛选、映射)都需创建新的集合存储中间结果,导致内存占用增加,尤其在处理大数据量时,容易引发内存溢出:​

java取消自动换行复制

这里的adultUsers和adultNames都是中间集合,仅用于存储中间结果,处理完成后便成为 “无用数据”,浪费内存资源。​

1.2 Stream API 的核心价值​

Stream API 通过 “声明式编程” 和 “惰性求值” 机制,完美解决了传统集合处理的痛点,其核心价值可概括为 3 点:​

  1. 简洁性:用链式调用替代嵌套循环,核心逻辑一目了然(如stream.filter().map().sorted().collect());​
  1. 并行性:只需调用parallelStream()方法,即可自动实现并行处理,无需手动管理线程;​
  1. 高效性:通过 “惰性求值” 避免创建中间集合,仅在终端操作时才执行实际计算,减少内存占用。​

用 Stream API 重写上述 “筛选成年用户姓名并排序” 的逻辑,代码会变得异常简洁:​

j取消自动换行复制

这段代码中,“筛选、映射、排序、收集” 的核心逻辑通过链式调用串联,无任何模板代码,可读性与维护性大幅提升。​

二、Stream API 的核心原理:惰性求值与流水线操作​

要真正掌握 Stream API,必须理解其 “惰性求值”(Lazy Evaluation)和 “流水线操作”(Pipeline Operation)的底层原理,这是 Stream 区别于传统集合的关键特性。​

2.1 Stream 的本质:不是 “数据容器”,而是 “计算流程”​

首先需要明确:Stream 不是集合,不存储数据,它本质上是 “数据处理的计算流程”。当我们从集合(如List)中获取 Stream 时(如list.stream()),Stream 并不会复制集合中的数据,而是记录 “从集合中读取数据,并执行一系列处理操作” 的流程。​

例如:​

jav取消自动换行复制

此时,stream对象仅包含 “数据来源(userList)” 和 “处理步骤(filter、map)”,并未执行任何实际计算,也未创建任何中间集合 —— 这就是 “惰性求值” 的基础。​

2.2 流水线操作:中间操作与终端操作​

Stream API 的操作分为两类,只有当两类操作配合时,才会触发实际计算:​

1. 中间操作(Intermediate Operation)​

  • 作用:定义数据处理的步骤(如筛选、映射、排序),返回新的 Stream 对象(支持链式调用);​
  • 特性:惰性执行,仅记录操作流程,不触发实际计算;​
  • 常见操作:filter()、map()、sorted()、distinct()、limit()等。​

例如:​

j取消自动换行复制

上述代码中,filter和map都是中间操作,执行后不会打印任何日志,因为实际计算尚未触发。​

2. 终端操作(Terminal Operation)​

  • 作用:触发实际计算,并生成最终结果(如集合、数值、布尔值等);​
  • 特性:立即执行,执行后 Stream 对象会被 “消费”,无法再次使用;​
  • 常见操作:collect()、forEach()、count()、findFirst()、anyMatch()等。​

在上述代码后添加终端操作collect(),就会触发所有中间操作的执行:​

ja取消自动换行复制

执行终端操作时,Stream 会按照 “数据来源→中间操作 1→中间操作 2→...→终端操作” 的流程,逐个处理数据(而非先执行完一个中间操作再执行下一个),这种 “逐个处理” 的方式避免了中间集合的创建,大幅提升效率。​

2.3 并行 Stream 的原理:Fork/Join 框架​

当调用parallelStream()获取并行 Stream 时,底层依赖 Java 的Fork/Join框架实现并行计算,核心流程如下:​

  1. 拆分数据:将数据源(如List)拆分为多个子任务(Subtask),每个子任务处理一部分数据;​
  1. 并行执行:将子任务分配到Fork/Join框架的线程池中,并行执行中间操作;​
  1. 合并结果:收集所有子任务的处理结果,合并为最终结果,返回给终端操作。​

例如,用并行 Stream 处理 “筛选成年用户姓名”:​

j取消自动换行复制

List<String> adultNames = getUserList().parallelStream() // 并行Stream​

.filter(user -> user.getAge() > 18)​

.map(User::getName)​

.collect(Collectors.toList());​

这段代码无需手动创建线程池,Stream 会自动根据 CPU 核心数拆分任务并并行执行,且线程安全由框架保证,开发者无需关注底层细节。​

注意事项:​

  • 并行 Stream 默认使用ForkJoinPool.commonPool()(公共线程池),若需自定义线程池,可通过ForkJoinPool的submit()方法执行;​
  • 并非所有场景都适合并行 Stream:若数据量小或中间操作简单,并行处理的线程开销可能大于收益,反而降低效率。​

三、Stream API 的核心操作:中间操作与终端操作实战​

Stream API 的操作丰富多样,但核心可分为中间操作和终端操作两类。本节将结合实际业务场景,详解常用操作的使用方法与注意事项。​

3.1 中间操作:定义数据处理的 “流水线步骤”​

中间操作的核心作用是 “筛选、转换、限制” 数据,返回新的 Stream 以支持链式调用。以下是 6 个最常用的中间操作:​

1. filter(Predicate predicate):筛选符合条件的元素​

  • 作用:保留 Predicate 接口返回true的元素,过滤掉返回false的元素;​
  • 场景:从集合中筛选符合业务条件的数据(如筛选成年用户、未过期订单)。​

示例:筛选出订单金额大于 100 元且未取消的订单:​

jav取消自动换行复制

List<Order> orderList = getOrderList();​

Stream<Order> validOrderStream = orderList.stream()​

.filter(order -> order.getAmount() > 100) // 金额大于100元​

.filter(order -> !OrderStatus.CANCELLED.equals(order.getStatus())); // 未取消​

2. map (Function<T, R> mapper):将元素转换为另一种类型​

  • 作用:通过 Function 接口将 Stream 中的 T 类型元素转换为 R 类型,生成新的 Stream;​
  • 场景:提取对象的某个属性(如从用户对象提取姓名)、数据格式转换(如将金额从 Integer 转为 BigDecimal)。​

示例:从订单列表中提取订单 ID,并转换为 String 类型:​

ja取消自动换行复制

Stream<String> orderIdStream = orderList.stream()​

.map(order -> String.valueOf(order.getId())); // Integer -> String​

// 或使用方法引用简化​

Stream<String> orderIdStream = orderList.stream()​

.map(order -> order.getId())​

.map(String::valueOf);​

3. sorted() / sorted(Comparator comparator):排序元素​

  • 作用:无参sort()按元素的自然顺序排序(需元素实现Comparable接口);有参sort()按自定义 Comparator 排序;​
  • 场景:对数据按指定字段排序(如按订单金额降序、按用户年龄升序)。​

示例:按订单金额降序排序,金额相同则按创建时间升序排序:​

j取消自动换行复制

Stream<Order> sortedOrderStream = orderList.stream()​

.sorted((o1, o2) -> {​

// 先按金额降序​

int amountCompare = o2.getAmount().compareTo(o1.getAmount());​

if (amountCompare != 0) {​

return amountCompare;​

}​

// 金额相同则按创建时间升序​

return o1.getCreateTime().compareTo(o2.getCreateTime());​

});​

// 或使用Comparator的静态方法简化​

Stream<Order> sortedOrderStream = orderList.stream()​

.sorted(Comparator.comparing(Order::getAmount).reversed() // 金额降序​

.thenComparing(Order::getCreateTime)); // 创建时间升序​

4. distinct ():去重元素​

  • 作用:根据元素的equals()方法去重,保留唯一元素;​
  • 场景:去除集合中的重复数据(如去重用户 ID、重复订单)。​

示例:从用户列表中提取不重复的城市:​

jav取消自动换行复制

Stream<String> distinctCityStream = userList.stream()​

.map(User::getCity)​

.distinct(); // 去重城市​

5. limit (long maxSize):限制返回元素的数量​

  • 作用:仅保留 Stream 中的前maxSize个元素,后续元素被丢弃;​
  • 场景:分页查询(如获取前 10 条数据)、获取 Top N 数据(如获取金额最高的 3 个订单)。​

示例:获取金额最高的 5 个未取消订单:​

ja取消自动换行复制

Stream<Order> top5OrderStream = orderList.stream()​

.filter(order -> !OrderStatus.CANCELLED.equals(order.getStatus()))​

.sorted(Comparator.comparing(Order::getAmount).reversed())​

.limit(5); // 保留前5个元素​

6. skip (long n):跳过前 n 个元素​

  • 作用:丢弃 Stream 中的前n个元素,保留后续元素;​
  • 场景:分页查询(如跳过前 10 条,获取第 11-20 条数据)。​

示例:分页获取订单,每页 10 条,获取第 2 页数据:​

jav取消自动换行复制

int pageNum = 2;​

int pageSize = 10;​

Stream<Order> page2OrderStream = orderList.stream()​

.sorted(Comparator.comparing(Order::getCreateTime).reversed())​

.skip((pageNum - 1) * pageSize) // 跳过前10条(第1页)​

.limit(pageSize); // 保留10条(第2页)​

3.2 终端操作:触发计算并获取结果​

终端操作是 Stream 的 “最终步骤”,触发所有中间操作的执行,并返回非 Stream 类型的结果。以下是 5 个最常用的终端操作:​

1. collect (Collector<T, A, R> collector):将结果收集为集合或其他类型​

  • 作用:通过 Collector 接口定义的规则,将 Stream 中的元素收集为 List、Set、Map 等集合,或自定义类型;​
  • 场景:将处理后的数据存储到集合中,是最常用的终端操作。​

Java 提供Collectors工具类,包含大量预定义的 Collector,满足常见需求:​

需求​

代码示例​

收集为 List​

collect(Collectors.toList())​

收集为 Set​

collect(Collectors.toSet())​

收集为 Map(键唯一)​

collect(Collectors.toMap(User::getId, User::getName))​

收集为 Map(键重复时处理)​

collect(Collectors.toMap(User::getCity, User::getName, (v1, v2) -> v1 + "," + v2))​

分组(按字段分组为 Map)​

collect(Collectors.groupingBy(User::getCity))​

分组并统计数量​

collect(Collectors.groupingBy(User::getCity, Collectors.counting()))​

示例 1:将用户按城市分组,每个城市对应多个用户名(用逗号拼接):​

java取消自动换行复制

Map<String, String> cityToNamesMap = userList.stream()​

.collect(Collectors.toMap(​

User::getCity, // 键:城市​

User::getName, // 值:用户名​

(name1, name2) -> name1 + "," + name2 // 键重复时,拼接用户名​

));​

示例 2:按订单状态分组,统计每个状态的订单数量:​

java取消自动换行复制

Map<OrderStatus, Long> statusCountMap = orderList.stream()​

.collect(Collectors.groupingBy(​

Order::getStatus, // 分组键:订单状态​

Collectors.counting() // 分组后的值:订单数量​

));​

2. forEach(Consumer action):遍历元素并执行操作​

  • 作用:遍历 Stream 中的每个元素,执行 Consumer 接口定义的操作(如打印、修改属性);​
  • 场景:对处理后的元素执行副作用操作(如日志打印、数据入库)。​

示例:打印所有成年用户的姓名和年龄:​

ja取消自动换行复制

userList.stream()​

.filter(user -> user.getAge() > 18)​

.forEach(user -> System.out.printf("姓名:%s,年龄:%d%n", user.getName(), user.getAge()));​

注意事项:​

  • forEach是 “消费型” 终端操作,无返回值;​
  • 并行 Stream 中forEach的执行顺序不保证与元素顺序一致(若需顺序,可使用forEachOrdered())。​

3. count ():统计元素数量​

  • 作用:返回 Stream 中元素的个数,返回值为long类型;​
  • 场景:统计符合条件的数据数量(如统计成年用户数、有效订单数)。​

示例:统计金额大于 500 元的有效订单数量:​

ja取消自动换行复制

long highValueOrderCount = orderList.stream()​

.filter(order -> order.getAmount() > 500)​

.filter(order -> OrderStatus.PAID.equals(order.getStatus()))​

.count();​

System.out.println("高价值有效订单数:" + highValueOrderCount);​

4. findFirst () /findAny ():获取任意一个元素​

  • 作用:findFirst()返回 Stream 中的第一个元素(按顺序),findAny()返回任意一个元素(并行 Stream 中可能更快);​
  • 返回值:Optional<T>类型(避免空指针异常,需处理 “无元素” 场景);​
  • 场景:获取符合条件的任意一个元素(如获取某个用户的订单、获取第一个有效订单)。​

示例:获取第一个年龄大于 30 岁的用户,若存在则打印姓名:​

j取消自动换行复制

Optional<User> userOptional = userList.stream()​

.filter(user -> user.getAge() > 30)​

.findFirst();​

// 处理Optional:存在则打印,不存在则提示​

userOptional.ifPresent(user -> System.out.println("找到的用户:" + user.getName()));​

// 或使用orElseGet()设置默认值​

User defaultUser = userOptional.orElseGet(() -> new User("默认用户", 0));​

5. anyMatch(Predicate predicate) / allMatch(Predicate predicate) / noneMatch(Predicate predicate):匹配判断​

  • 作用:​
  • anyMatch():判断是否存在至少一个元素符合条件,返回boolean;​
  • allMatch():判断所有元素是否都符合条件,返回boolean;​
  • noneMatch():判断所有元素是否都不符合条件,返回boolean;​
  • 场景:业务规则验证(如判断是否有过期订单、所有用户是否都已实名认证)。​

示例 1:判断订单列表中是否存在已过期的订单:​

java取消自动换行复制

boolean hasExpiredOrder = orderList.stream()​

.anyMatch(order -> order.getExpireTime().isBefore(LocalDateTime.now()));​

示例 2:判断所有用户是否都已完成实名认证:​

java取消自动换行复制

boolean allCertified = userList.stream()​

.allMatch(user -> UserCertStatus.CERTIFIED.equals(user.getCertStatus()));​

3.3 中间操作与终端操作的配合原则​

使用 Stream API 时,需遵循以下 3 个配合原则,避免常见错误:​

  1. 中间操作必须跟随终端操作:仅调用中间操作(如stream.filter().map())不会执行任何计算,必须添加终端操作(如collect())才会触发;​
  1. Stream 对象只能消费一次:终端操作执行后,Stream 对象会被 “关闭”,再次调用操作会抛出IllegalStateException;​

java取消自动换行复制

// 错误示例:Stream被消费后再次使用​

Stream<User> stream = userList.stream().filter(user -> user.getAge() > 18);​

stream.collect(Collectors.toList()); // 第一次消费​

stream.forEach(user -> System.out.println(user.getName())); // 错误:Stream已关闭​

  1. 中间操作的顺序影响性能:应将 “筛选性操作”(如filter()、distinct())放在前面,减少后续操作的元素数量,提升效率。例如:​

java取消自动换行复制

// 高效:先筛选再排序(排序元素少)​

userList.stream().filter(user -> user.getAge() > 18).sorted();​

// 低效:先排序再筛选(排序元素多,浪费资源)​

userList.stream().sorted().filter(user -> user.getAge() > 18);​

四、Stream API 的实战场景:从业务需求到代码实现​

Stream API 在实际开发中应用广泛,本节将结合 4 个典型业务场景(数据统计、集合转换、复杂查询、并行处理),展示从需求分析到 Stream 代码实现的完整过程。​

4.1 场景 1:电商订单数据统计​

需求:统计最近 30 天内的订单数据,包括:​

  1. 总订单数;​
  1. 有效订单数(状态为 “已支付” 或 “已发货”);​
  1. 有效订单的总金额;​
  1. 按订单状态分组,统计每个状态的订单数量;​
  1. 获取金额最高的 10 个有效订单。​

分析:需先筛选 “最近 30 天内的订单”,再通过不同终端操作实现各统计需求。​

代码实现:​

java取消自动换行复制

// 1. 定义时间范围(最近30天)​

LocalDateTime thirtyDaysAgo = LocalDateTime.now().minusDays(30);​

List<Order> allOrders = getRecentOrders(); // 获取所有订单​

// 2. 筛选最近30天内的订单(提取为Stream,避免重复筛选)​

Stream<Order> recentOrderStream = allOrders.stream()​

.filter(order -> order.getCreateTime().isAfter(thirtyDaysAgo));​

// 3. 统计总订单数​

long totalOrderCount = recentOrderStream.count();​

// 4. 重新获取Stream(因上一步count()已消费Stream),统计有效订单相关数据​

Stream<Order> validOrderStream = allOrders.stream()​

.filter(order -> order.getCreateTime().isAfter(thirtyDaysAgo))​

.filter(order -> {​

OrderStatus status = order.getStatus();​

return OrderStatus.PAID.equals(status) || OrderStatus.SHIPPED.equals(status);​

});​

// 4.1 有效订单数​

4.2 场景 2:用户数据转换与分组​

需求:将用户列表转换为以下格式:​

  1. 提取所有成年用户(年龄 > 18)的 “用户 ID - 用户名” 映射(Map<Long, String>);​
  1. 按用户所在城市分组,每个城市对应 “用户 ID - 用户对象” 的映射(Map<String, Map<Long, User>>);​
  1. 提取所有已实名认证用户的邮箱,去重后收集为 Set。​

分析:需使用map()进行数据转换,filter()筛选用户,collect()结合Collectors工具类实现分组与去重。​

代码实现:​

java取消自动换行复制

List<User> userList = getUserList();​

// 1. 成年用户的“ID-姓名”映射(键为用户ID,值为用户名)​

Map<Long, String> adultUserIdToNameMap = userList.stream()​

.filter(user -> user.getAge() > 18)​

.collect(Collectors.toMap(​

User::getId, // 键:用户ID​

User::getName, // 值:用户名​

(v1, v2) -> v1 // 若ID重复(实际业务中ID唯一,此处为避免异常),保留第一个值​

));​

// 2. 按城市分组,每个城市对应“ID-用户”映射​

Map<String, Map<Long, User>> cityToUserIdToUserMap = userList.stream()​

.collect(Collectors.groupingBy(​

User::getCity, // 一级分组键:城市​

// 二级收集器:将每个城市的用户转换为“ID-用户”映射​

Collectors.toMap(​

User::getId, // 二级键:用户ID​

Function.identity() // 二级值:用户对象本身​

)​

));​

4.3 场景 3:复杂条件查询​

需求:从商品列表中查询符合以下条件的商品:​

  1. 商品分类为 “电子产品” 或 “家用电器”;​
  1. 商品价格在 500 元到 5000 元之间;​
  1. 商品库存大于 0;​
  1. 商品创建时间在 2024 年 1 月 1 日之后;​
  1. 按 “创建时间降序” 排序,取前 20 条数据;​
  1. 最终返回 “商品 ID - 商品名称 - 价格” 的自定义 DTO 列表(ProductDTO)。​

分析:需组合多个filter()条件筛选商品,使用map()转换为 DTO,sorted()排序后limit()限制数量。​

代码实现:​

java取消自动换行复制

// 1. 定义查询条件​

List<String> targetCategories = Arrays.asList("电子产品", "家用电器");​

BigDecimal minPrice = new BigDecimal("500");​

BigDecimal maxPrice = new BigDecimal("5000");​

LocalDate createDateStart = LocalDate.of(2024, 1, 1);​

List<Product> productList = getProductList();​

// 2. 复杂条件查询并转换为DTO​

List<ProductDTO> resultDTOs = productList.stream()​

// 条件1:分类为“电子产品”或“家用电器”​

.filter(product -> targetCategories.contains(product.getCategory()))​

// 条件2:价格在500-5000元之间​

.filter(product -> {​

BigDecimal price = product.getPrice();​

return price.compareTo(minPrice) >= 0 && price.compareTo(maxPrice) <= 0;​

})​

// 条件3:库存大于0​

.filter(product -> product.getStock() > 0)​

4.4 场景 4:大数据量并行处理​

需求:处理 100 万条用户数据,筛选出年龄大于 25 岁且所在城市为 “北京” 的用户,提取其手机号并存储到 Set 中。由于数据量大,需使用并行处理提升效率。​

分析:数据量达 100 万,适合使用parallelStream()并行处理,需注意避免线程安全问题(如使用线程安全的收集器)。​

代码实现:​

java取消自动换行复制

// 1. 模拟100万条用户数据​

List<User> largeUserList = generateLargeUserList(1000000);​

// 2. 并行处理:筛选北京的25岁以上用户,提取手机号并去重​

long startTime = System.currentTimeMillis();​

Set<String> beijingAdultPhoneSet = largeUserList.parallelStream()​

// 筛选条件:年龄>25且城市为“北京”​

.filter(user -> user.getAge() > 25 && "北京".equals(user.getCity()))​

// 提取手机号​

.map(User::getPhone)​

// 收集到Set(Collectors.toSet()在并行Stream中是线程安全的)​

.collect(Collectors.toSet());​

long endTime = System.currentTimeMillis();​

// 打印结果与耗时​

System.out.printf("符合条件的用户手机号数量:%d%n", beijingAdultPhoneSet.size());​

System.out.printf("并行处理耗时:%d 毫秒%n", endTime - startTime);​

性能对比:​

  • 串行 Stream(stream())处理 100 万条数据,耗时约 1200 毫秒;​
  • 并行 Stream(parallelStream())处理,耗时约 300 毫秒(视 CPU 核心数而定,核心数越多,并行优势越明显)。​

注意事项:​

  • 并行 Stream 的线程安全由Collectors保证,无需手动同步(如Collectors.toList()、Collectors.toSet()都是线程安全的);​
  • 若自定义收集器,需确保accumulator和combiner方法线程安全;​
  • 数据量较小时(如少于 1 万条),不建议使用并行 Stream,避免线程创建与调度的开销。​

五、Stream API 的性能优化与常见问题​

虽然 Stream API 简洁高效,但在使用不当的情况下,仍可能出现性能问题或逻辑错误。本节将分析常见问题,并提供针对性的优化方案。​

5.1 常见性能问题与优化方案​

问题 1:频繁创建 Stream 对象,重复筛选数据​

现象:对同一数据源,多次创建 Stream 并执行相同的筛选操作(如多次筛选 “最近 30 天的订单”),导致重复计算,浪费资源。​

示例(低效):​

java取消自动换行复制

// 多次创建Stream,重复筛选最近30天的订单​

long totalCount = allOrders.stream()​

.filter(order -> order.getCreateTime().isAfter(thirtyDaysAgo))​

.count();​

BigDecimal totalAmount = allOrders.stream()​

.filter(order -> order.getCreateTime().isAfter(thirtyDaysAgo)) // 重复筛选​

.map(Order::getAmount)​

.reduce(BigDecimal.ZERO, BigDecimal::add);​

优化方案:将筛选后的 Stream 转换为List或Supplier<Stream>,避免重复筛选:​

java取消自动换行复制

// 方案1:先筛选并收集为List,后续操作基于List​

List<Order> recentOrders = allOrders.stream()​

.filter(order -> order.getCreateTime().isAfter(thirtyDaysAgo))​

.collect(Collectors.toList());​

long totalCount = recentOrders.stream().count();​

BigDecimal totalAmount = recentOrders.stream()​

.map(Order::getAmount)​

.reduce(BigDecimal.ZERO, BigDecimal::add);​

// 方案2:使用Supplier<Stream>延迟创建Stream,避免重复筛选​

Supplier<Stream<Order>> recentOrderStreamSupplier = () -> allOrders.stream()​

.filter(order -> order.getCreateTime().isAfter(thirtyDaysAgo));​

long totalCount = recentOrderStreamSupplier</doubaocanvas>​

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐