欢迎光临
我们一直在努力

JDK 8 Stream API全解析:核心用法与优化方向

一、前言

在上一篇文章中,我们通过对比 JDK 8 前后的代码,直观感受到了 Stream API 带来的便捷性。但要真正用好 Stream,尤其是理解后续版本的优化点,你必须先吃透 JDK 8 作为 “奠基版本” 的核心用法 —— 这是所有 Stream 进阶的基础。

本文会从 Stream 创建、中间操作、终止操作、并行流 四个维度,全面解析 JDK 8 Stream 的核心用法,同时会明确指出每个环节中 JDK 8 存在的局限性,让你清楚后续版本的优化方向。

二、如何创建 Stream?

Stream 作为 “数据处理流水线”,第一步是从数据源创建 Stream。JDK 8 提供了 5 种核心创建方式,覆盖绝大多数场景:

1、从集合创建(最常用)

所有实现 Collection 接口的集合(List、Set、Queue 等)都内置了 stream() (串行流)和 parallelStream() (并行流)方法:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class StreamCreateDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Stream");
        list.add("JDK8");
        // 串行流
        Stream<String> serialStream = list.stream();
        // 并行流
        Stream<String> parallelStream = list.parallelStream();
    }
}

2、从数组创建

通过 Arrays.stream() 方法,支持任意类型数组创建 Stream:

import java.util.Arrays;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class ArrayStreamDemo {
    public static void main(String[] args) {
        // 对象数组
        String[] strArray = {"a", "b", "c"};
        Stream<String> strStream = Arrays.stream(strArray);
        // 基本类型数组(有专用的 IntStream/LongStream/DoubleStream)
        int[] intArray = {1, 2, 3};
        IntStream intStream = Arrays.stream(intArray);
    }
}

注意:JDK 8 中基本类型数组不会自动装箱为包装类型 Stream,而是提供了专用的 IntStream / LongStream 等,避免拆装箱性能损耗。

3、通过 Stream 静态方法创建

(1) Stream.of() :创建包含指定元素的 Streamjava

import java.util.stream.Stream;
public class StreamOfDemo {
    public static void main(String[] args) {
        // 单个元素
        Stream<String> singleStream = Stream.of("Hello");
        // 多个元素
        Stream<Integer> multiStream = Stream.of(1, 2, 3, 4);
    }
}

(2) Stream.empty() :创建空 Stream

用于处理 “无数据” 的场景,避免返回 null:

Stream<String> emptyStream = Stream.empty();
System.out.println(emptyStream.count()); // 输出:0

(3) Stream.iterate() :创建无限流(仅支持初始值 + 一元操作)

这是 JDK 8 的 核心局限性 之一: iterate 只能创建无限流,必须配合 limit() 才能变成有限流:

import java.util.stream.Stream;
public class StreamIterateDemo {
    public static void main(String[] args) {
        // JDK 8 写法:初始值 1,每次 +1(无限流)
        Stream<Integer> infiniteStream = Stream.iterate(1, n -> n + 1);
        // 必须用 limit 限制长度,否则会无限循环
        infiniteStream.limit(5).forEach(System.out::println); // 输出 1,2,3,4,5
    }
}

痛点:JDK 8 无法直接给 iterate 设置终止条件(比如 “生成小于 10 的数”),只能靠 limit 硬编码长度,灵活性差。

(4) Stream.generate() :创建无限流(通过 Supplier 生成)

适合生成随机数、常量等无规律的无限流,同样需要 limit 限制:

import java.util.Random;
import java.util.stream.Stream;
public class StreamGenerateDemo {
    public static void main(String[] args) {
        // 生成 3 个随机数
        Stream<Double> randomStream = Stream.generate(() -> new Random().nextDouble());
        randomStream.limit(3).forEach(System.out::println);
    }
}

4、从文件创建(IO 流)

JDK 8 新增了 Files.lines() 方法,可读取文件的每一行作为 Stream 元素:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
public class FileStreamDemo {
    public static void main(String[] args) {
        try (Stream<String> lines = Files.lines(Paths.get("test.txt"))) {
            // 读取文件并输出非空行
            lines.filter(line -> !line.isEmpty()).forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

5、从函数式接口创建(小众)

通过 StreamSupport 配合 Spliterator 创建,适合自定义数据源(如自定义集合),日常开发极少用到,此处不展开。

三、核心中间操作

中间操作是 Stream 的 “加工环节”,特点是惰性求值 (不触发实际计算)、 返回新 Stream 、 可链式调用。JDK 8 提供的核心中间操作可分为 4 类:

1、 筛选与切片

方法

作用

示例

filter(Predicate)

distinct()

limit(long)

skip(long)

按条件筛选元素

stream.filter(s -> s.length() > 3)

去重(基于 equals 方法)

stream.distinct()

截取前 N 个元素

stream.limit(5)

跳过前 N 个元素

stream.skip(2)

JDK 8。

JDK 8 痛点:缺少 takeWhile() / dropWhile() 方法 —— 对于有序集合,无法 “按条件截取前缀” 或 “按条件丢弃前缀”,只能用 filter 遍历全部元素,效率低。

示例:筛选列表中长度大于 2 的字符串,去重后取前 2 个:

import java.util.Arrays;
import java.util.stream.Stream;
public class FilterDemo {
    public static void main(String[] args) {
        Stream<String> stream = Arrays.stream(new String[]{"a", "ab", "abc", "abc", "abcd"});
        stream.filter(s -> s.length() > 2)
              .distinct()
              .limit(2)
              .forEach(System.out::println); // 输出 abc, abcd
    }
}

2、映射(转换元素)

方法

作用

示例

map(Function)

flatMap(Function)

mapToInt(ToIntFunction)

将元素转换为另一种类型

stream.map(String::length)

将每个元素转换为 Stream,再合并为一个 Stream

stream.flatMap(s -> Arrays.stream(s.split(",")))

转换为 IntStream(避免装箱)

stream.mapToInt(String::length)

示例:将字符串列

示例:将字符串列表转换为长度列表,再求和:

import java.util.Arrays;
import java.util.List;
public class MapDemo {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Java", "Stream", "JDK8");
        int totalLength = list.stream()
.mapToInt(String::length) // 转换为 IntStream
.sum(); // 终止操作:求和
        System.out.println(totalLength); // 输出:4+6+4=14
    }
}

3、排序

方法

作用

示例

sorted()

sorted(Comparator)

自然排序(实现 Comparable 接口)

stream.sorted()

自定义排序

stream.sorted((a,b) -> b.compareTo(a))

示例:自定义排序(倒序):

import java.util.Arrays;
import java.util.List;
public class SortedDemo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(3, 1, 2);
        list.stream()
            .sorted((a, b) -> b – a)
            .forEach(System.out::println); // 输出 3,2,1
    }
}

4、消费

peek(Consumer) :遍历元素但不修改,主要用于调试(查看中间结果):

import java.util.Arrays;
import java.util.List;
public class PeekDemo {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c");
        list.stream()
            .peek(s -> System.out.println("原始元素:" + s))
            .map(s -> s.toUpperCase())
            .peek(s -> System.out.println("转换后:" + s))
            .forEach(System.out::println);
    }
}

输出:

原始元素:a
转换后:A
A
原始元素:b
转换后:B
B
原始元素:c
转换后:C
C

四、核心终止操作

终止操作是 Stream 的 “成品输出环节”,特点是触发实际计算 、 消耗 Stream (执行后 Stream 不可复用)、 返回非 Stream 结果 (如集合、数值、void)。

1、遍历与匹配

方法

作用

返回类型

示例

forEach(Consumer)

forEachOrdered

(Consumer)

anyMatch(Predicate)

allMatch(Predicate)

noneMatch(Predicate)

findFirst()

findAny()

遍历元素

void

stream.forEach(System.out::println)

按数据源顺序遍历(并行流中生效)

void

parallelStream.forEachOrdered(…)

是否存在满足条件的元素

boolean

stream.anyMatch(s -> s.contains("Java"))

是否所有元素都满足条件

boolean

stream.allMatch(s -> s.length() > 0)

是否没有元素满足条件

boolean

stream.noneMatch(s -> s.isEmpty())

获取第一个元素

Optional

stream.findFirst().orElse("默认值")

获取任意一个元素(并行流中效率高)

Optional

stream.findAny().orElse("默认值")

示例:判断列表中是否有包含 “JD

示例:判断列表中是否有包含 “JDK” 的字符串:

import java.util.Arrays;import java.util.List;
public class MatchDemo {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("Java", "Stream", "JDK8");
        boolean hasJdk = list.stream().anyMatch(s -> s.contains("JDK"));
        System.out.println(hasJdk); // 输出 true
    }
}

2、归约(聚合)

reduce() :将 Stream 元素聚合为一个值,是 sum() / max() / min() 的底层实现:

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class ReduceDemo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4);
        // 方式1:无初始值,返回 Optional
        Optional<Integer> sum1 = list.stream().reduce((a, b) -> a + b);
        System.out.println(sum1.get()); // 输出 10
        // 方式2:有初始值,返回具体类型
        int sum2 = list.stream().reduce(0, (a, b) -> a + b);
        System.out.println(sum2); // 输出 10
        // 求最大值
        Optional<Integer> max = list.stream().reduce(Integer::max);
        System.out.println(max.get()); // 输出 4
    }
}

3、收集(转换为集合 / 其他类型)

collect(Collector) :最常用的终止操作,JDK 8 提供了 Collectors 工具类封装常用收集器:

收集器

作用

示例

Collectors.toList()

Collectors.toSet()

Collectors.toMap()

Collectors.joining()

Collectors.groupingBy()

Collectors.summarizingInt()

转换为 List(默认 ArrayList)

stream.collect(Collectors.toList())

转换为 Set(默认 HashSet)

stream.collect(Collectors.toSet())

转换为 Map(需处理重复键)

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

拼接字符串

stream.collect(Collectors.joining(","))

分组

stream.collect(Collectors.groupingBy(User::getAge))

统计(计数、求和、最大 / 最小 / 平均值)

stream.collect(Collectors.summarizingInt(User::getAge))

JDK 8 痛点:

Col数;

JDK 8 痛点:

Collectors.toList()返回的是可变 ArrayList ,创建不可变列表需要手动封装(如 Collections.unmodifiableList() );

无直接的 toMap() 重复键处理简化方案,需手动指定合并函数;

无法同时执行多个收集器(如一次遍历同时求最大值和最小值)。

示例:将用户列表按年龄分组,拼接每组的用户名:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CollectDemo {
    static class User {
        private String name;
        private int age;
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public String getName() { return name; }
        public int getAge() { return age; }
    }
    public static void main(String[] args) {
        List<User> userList = Arrays.asList(
            new User("Alice", 20),
            new User("Bob", 20),
            new User("Charlie", 25)
        );
        // 按年龄分组,拼接每组用户名
        Map<Integer, String> ageGroup = userList.stream()
                .collect(Collectors.groupingBy(
                    User::getAge,
                    Collectors.mapping(User::getName, Collectors.joining(","))
                ));
        System.out.println(ageGroup); // 输出 {20=Alice,Bob, 25=Charlie}
    }
}

4、数值计算(专用流)

IntStream / LongStream / DoubleStream 提供了便捷的数值计算方法:

import java.util.Arrays;
import java.util.List;
public class NumericDemo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4);
        int sum = list.stream().mapToInt(Integer::intValue).sum(); // 求和
        long count = list.stream().count(); // 计数
        double avg = list.stream().mapToInt(Integer::intValue).average().getAsDouble(); // 平均值
        int max = list.stream().mapToInt(Integer::intValue).max().getAsInt(); // 最大值
        System.out.println("和:" + sum + ",计数:" + count + ",平均值:" + avg + ",最大值:" + max);
        // 输出:和:10,计数:4,平均值:2.5,最大值:4
    }
}

五、并行流:便捷但有坑

JDK 8 最亮眼的特性之一是并行流( parallelStream() ),无需手动管理线程池,即可实现数据并行处理:

import java.util.Arrays;
import java.util.List;
public class ParallelStreamDemo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        // 并行流求和
        int sum = list.parallelStream().mapToInt(Integer::intValue).sum();
        System.out.println(sum); // 输出 15
    }
}

JDK 8 并行流的核心坑点:

1.线程安全问题:如果在并行流中修改共享变量(如 ArrayList ),会导致数据错乱:

// 错误示例:并行流操作非线程安全集合
List<Integer> result = new ArrayList<>();
List<Integer> list = Arrays.asList(1, 2, 3, 4);
list.parallelStream().forEach(result::add);
System.out.println(result.size()); // 可能不是 4,数据错乱

2.性能未必最优:并行流基于 Fork/Join 框架,小数据量场景下,线程切换开销大于并行收益;

3.顺序不可控:默认 forEach 不保证顺序,需用 forEachOrdered (但会损失并行性能);

4.JDK 8 优化不足:后续版本(如 JDK 11+)对并行流的性能和稳定性做了大量优化。

六、核心局限性

学完 JDK 8 Stream 的核心用法,我们再梳理一遍它的核心痛点(也是后续版本的优化方向):

  • 创建环节: Stream.iterate() 仅支持无限流,无终止条件;无 ofNullable() 方法,处理 null 易抛 NPE;

  • 中间操作:缺少 takeWhile() / dropWhile() ,有序集合条件截取效率低;

  • 终止操作: Collectors.toList() 返回可变列表;无简洁的不可变集合收集器;无法同时执行多个收集器;

  • 并行流:性能和稳定性不足,线程安全问题易踩坑;

  • 其他:Stream 不可复用,关闭逻辑不完善。

  • 这些痛点,正是 JDK 9、11、17 版本对 Stream API 进行增强的核心动机 —— 下一篇文章,我们会重点讲解 JDK 9 对 Stream 的首次增强,看看这些新特性如何解决上述痛点。

    七、总结

  • JDK 8 Stream 的核心流程是 “创建 → 中间操作 → 终止操作”,中间操作惰性求值,终止操作触发计算;

  • JDK 8 提供了丰富的创建方式和操作方法,覆盖绝大多数集合处理场景,但存在诸多局限性;

  • 并行流虽便捷,但 JDK 8 版本存在线程安全、性能优化不足等问题,使用时需谨慎。

  • 掌握这些基础用法,是理解后续版本差异的关键 —— 下一篇,我们将聚焦 JDK 9,看看它如何填补 JDK 8 Stream 的这些 “漏洞”。

    赞(0)
    未经允许不得转载:171主机测评 » JDK 8 Stream API全解析:核心用法与优化方向
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址