Java8(三)--Stream

一、核心概念:

1、什么是 Stream?

非存储结构:不直接修改底层数据源,仅描述计算过程。
流水线操作:由零或多个中间操作(Intermediate)和一个终止操作(Terminal)组成。
惰性求值:中间操作延迟执行,直到触发终止操作。

2、与集合的区别:

特性 集合(Collection) Stream
数据存储 存储实际数据 不存储数据
遍历方式 外部迭代(显式循环) 内部迭代(自动处理)
可复用性 可多次遍历 一次性消费
数据处理 命令式(How) 声明式(What)

二、创建 Stream:

1、基础创建方式:

// 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();

// 从数组创建
String[] array = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);

// 直接创建元素流
Stream<String> stream3 = Stream.of("a", "b", "c");

// 生成无限流
Stream<Double> randomStream = Stream.generate(Math::random).limit(5);

2、特殊类型流:

// 基本类型流(避免装箱开销)
IntStream intStream = IntStream.range(1, 5);      // 1,2,3,4
LongStream longStream = LongStream.rangeClosed(1,5); // 1,2,3,4,5

// 文件流
try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
    lines.forEach(System.out::println);
}

三、核心操作:

1、中间操作(Intermediate Operations):

操作 描述 示例
filter 条件过滤 .filter(s -> s.length() > 3)
map 元素转换 .map(String::toUpperCase)
flatMap 扁平化嵌套结构 .flatMap(list -> list.stream())
distinct 去重 .distinct()
sorted 排序 .sorted(Comparator.reverseOrder())
peek 调试观察元素 .peek(System.out::println)
limit/skip 限制元素数量 .limit(5).skip(2)

2、终止操作(Terminal Operations):

操作 描述 示例
forEach 遍历元素 .forEach(System.out::println)
collect 转换为集合 .collect(Collectors.toList())
reduce 聚合计算 .reduce(0, Integer::sum)
count 统计元素数量 .count()
anyMatch 任意匹配 .anyMatch(s -> s.contains("a"))
findFirst 获取第一个元素 .findFirst()

四、高阶应用:

1、自定义收集器:

实现 Collector 接口处理复杂聚合:

// 统计字符串长度总和
Collector<String, ?, Integer> lengthSumCollector = 
    Collector.of(
        () -> new int[1],                // Supplier 初始化容器
        (container, str) -> container[0] += str.length(), // Accumulator
        (c1, c2) -> { c1[0] += c2[0]; return c1; },       // Combiner(并行用)
        container -> container[0]        // Finisher
    );

int totalLength = Stream.of("Java", "Stream")
                       .collect(lengthSumCollector); // 4 + 6 = 10

2、并行流优化:

通过 parallel() 启用并行处理:

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

// 并行计算平方和
int sum = numbers.parallelStream()
                .mapToInt(n -> n * n)
                .sum(); // 1+4+9+16+25+36=91

并行流注意事项:

数据规模需足够大(推荐 N > 10,000)
避免共享可变状态
底层使用 ForkJoinPool(默认线程数=CPU核心数)

五、性能与最佳实践:

1、操作链优化:

合并过滤条件:减少中间操作次数

// 劣化写法
.filter(s -> s.length() > 3)
.filter(s -> s.startsWith("A"))

// 优化写法
.filter(s -> s.length() > 3 && s.startsWith("A"))
优先使用基本类型流(IntStream/LongStream)避免装箱开销

2、短路操作利用:

利用 anyMatch/findFirst 提前终止计算:

boolean hasAdmin = users.stream()
                       .anyMatch(u -> u.isAdmin()); // 找到第一个即停止

3、避免副作用:

保持函数纯度(无状态、不修改外部变量):

// 错误示例(修改外部变量)
List<String> result = new ArrayList<>();
stream.filter(s -> s.startsWith("A"))
      .forEach(s -> result.add(s)); // 可能引发并发问题

// 正确写法
List<String> result = stream.filter(s -> s.startsWith("A"))
                           .collect(Collectors.toList());

六、典型应用场景:

1、数据转换:

// 对象属性提取
List<String> names = users.stream()
                         .map(User::getName)
                         .collect(Collectors.toList());

2、分组统计:

// 按部门分组统计薪资
Map<String, Double> deptSalaries = employees.stream()
    .collect(Collectors.groupingBy(
        Employee::getDepartment,
        Collectors.averagingDouble(Employee::getSalary)
    ));

将list转化为Map,这里需要注意key重复的场景(如果不处理可能会导致异常),是新值覆盖旧值,还是一致保留旧值,又或者对重复的场景做额外的操作都是支持的。

Map<Integer, String> userMap = users.stream()
    .collect(Collectors.toMap(
        User::getAge,
        User::getName,
        (first, second) -> first  // 始终保留第一个值
    ));
// 结果: {25=Alice, 30=Charlie}
Map<Integer, String> mergedNamesMap = users.stream()
    .collect(Collectors.toMap(
        User::getAge,
        User::getName,
        (existing, newName) -> existing + ", " + newName  // 拼接重复值
    ));
// 结果: {25=Alice, Bob, 30=Charlie}

3、复杂流水线:

// 找出价格>100的前3个商品名称(按价格降序)
List<String> topItems = products.stream()
    .filter(p -> p.getPrice() > 100)
    .sorted(Comparator.comparing(Product::getPrice).reversed())
    .limit(3)
    .map(Product::getName)
    .collect(Collectors.toList());

七、常见误区:

1、重复使用流:

Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println); 
stream.count(); // 抛出 IllegalStateException

2、混淆中间/终止操作:

Stream.of("a", "b", "c").sorted(); // 错误!缺少终止操作

3、过度使用并行流:
可能引发线程安全问题或性能下降

八、与传统循环对比:

案例:统计长单词数量

// 传统方式
int count = 0;
for (String word : words) {
    if (word.length() > 5) {
        count++;
    }
}

// Stream 方式
long count = words.stream()
                .filter(word -> word.length() > 5)
                .count();

优势分析:

代码更简洁(减少模板代码)
易于实现并行处理(.parallelStream())
支持更复杂的链式操作

九、总结:

Java Stream 通过声明式编程范式,显著提升了集合数据处理的表达力和可维护性。掌握以下要点可最大化其价值:

理解惰性求值机制,合理设计操作链
优先选择无状态函数,避免副作用
根据场景选择并行流,注意线程安全
善用 Collectors 工具类处理复杂聚合

随着函数式编程的普及,Stream 已成为现代 Java 开发的必备技能。合理运用可使代码既简洁高效,又具备良好的可扩展性。

posted @ 2025-04-11 20:40  Diamond-Shine  阅读(61)  评论(0)    收藏  举报