JDK8 Stream流保姆级教程:从入门到实战,告别繁琐遍历

JDK8 Stream流保姆级教程:从入门到实战,告别繁琐遍历

在Java开发中,我们每天都要和集合、数组打交道,传统的for循环遍历、过滤、排序代码不仅冗长,还容易出现逻辑漏洞。而JDK8推出的Stream流,凭借Lambda表达式的加持,让数据处理代码变得简洁优雅、可读性拉满。今天就带大家从零吃透Stream流,从基础用法到实战案例一网打尽!

一、初识Stream流:告别传统遍历的臃肿

先来看一个经典场景:从姓名列表中筛选出姓张且名字为3个字的人,存入新集合。我们先对比传统方案和Stream流方案的代码差异。

1. 传统for循环方案

csharp 复制代码
List<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
list.add("张翠山");

// 传统筛选逻辑
List<String> newlist = new ArrayList<>();
for (String name : list) {
    if (name.startsWith("张") && name.length() == 3) {
        newlist.add(name);
    }
}
System.out.println(newlist); // [张无忌, 张三丰, 张翠山]

2. Stream流方案

scss 复制代码
// Stream流链式调用
List<String> newlist2 = list.stream()
    .filter(name -> name.startsWith("张"))
    .filter(name -> name.length() == 3)
    .collect(Collectors.toList());
System.out.println(newlist2); // [张无忌, 张三丰, 张翠山]

关键点 :Stream流是惰性求值 的,只有在终结方法(如forEachcount)被调用时才会执行操作,这大大提高了效率。

Stream流的核心优势

  • 代码简洁:Lambda语法+链式调用,告别嵌套循环
  • 性能高效:底层实现了并行处理机制,适合大数据量操作
  • 可读性强:方法名直观(filter、sorted等),业务逻辑清晰
  • 惰性求值:只在需要结果时才执行操作,避免不必要的计算

二、获取Stream流:3种数据源的获取方式

Stream流的操作第一步是获取流 ,不同数据源(集合、Map、数组)有不同的获取方式,对应代码在StreamDemo2.java中。

1. 集合获取Stream流

所有Collection接口的实现类(ArrayList、HashSet等),都可以直接调用stream()方法:

ini 复制代码
List<String> list = new ArrayList<>();
Stream<String> s1 = list.stream();

2. Map集合获取Stream流

Map不直接实现Collection接口,需要先转成键集合、值集合或键值对集合,再获取流:

arduino 复制代码
Map<String, Integer> map = new HashMap<>();
// 键流
Stream<String> keyStream = map.keySet().stream();
// 值流
Stream<Integer> valueStream = map.values().stream();
// 键值对流
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();

3. 数组获取Stream流

可以通过Arrays.stream()Stream.of()两种方式:

ini 复制代码
String[] names = {"张三丰", "张无忌", "张翠山", "张良", "张学友"};
// 方式1:Arrays工具类
Stream<String> s5 = Arrays.stream(names);
// 方式2:Stream静态方法
Stream<String> s6 = Stream.of(names);
Stream<String> s7 = Stream.of("张三丰", "张无忌", "张翠山"); // 直接传元素

三、Stream流核心操作:中间方法+终结方法

Stream流的操作分为中间方法终结方法,中间方法支持链式调用,终结方法会终止流并返回结果。

1. 中间方法:数据处理的"流水线"

中间方法调用后会返回新的Stream流,可继续链式操作,核心方法如下:

方法 作用 示例 说明
filter() 过滤数据 filter(name -> name.startsWith("张")) 筛选符合条件的数据
sorted() 排序 sorted()/sorted((a,b)->b-a) 默认升序,自定义降序
limit(n) 取前n个元素 sorted().limit(2) 取前2个元素
skip(n) 跳过前n个元素 sorted().skip(2) 跳过前2个元素
distinct() 去重 distinct() 自定义对象需重写hashCode和equals
map() 数据加工 map(score -> score + 10) 将数据转换为新形式
concat() 合并流 Stream.concat(s1, s2) 合并两个流

注意distinct()去重对于自定义对象(如Teacher)需要重写hashCodeequals方法才能生效。

2. 终结方法:数据处理的"终点站"

终结方法调用后流会关闭,无法再操作,核心方法如下:

方法 作用 示例
forEach() 遍历元素 filter(t -> t.getSalary() > 17000).forEach(System.out::println)
count() 统计元素个数 filter(t -> t.getSalary() > 17000).count()
max()/min() 获取最大值/最小值 max((t1,t2)->Double.compare(t1.getSalary(),t2.getSalary()))
collect() 收集结果 collect(Collectors.toList())

3. 收集Stream流:转回集合/数组

Stream流是"临时工具",最终需要将处理结果转回集合或数组,核心用collect()方法:

rust 复制代码
// 转List
List<String> list1 = list.stream()
    .filter(name -> name.startsWith("张"))
    .collect(Collectors.toList());

// 转Set(自动去重)
Set<String> set = list.stream()
    .filter(name -> name.startsWith("张"))
    .collect(Collectors.toSet());

// 转数组
String[] arr = list.stream()
    .filter(name -> name.startsWith("张"))
    .toArray(String[]::new);

// 转Map(key为老师名,value为薪水)
Map<String, Double> teacherMap = teachers.stream()
    .collect(Collectors.toMap(Teacher::getName, Teacher::getSalary));

⚠️ 重要提示 :一个Stream流只能被收集一次,重复收集会报错!StreamDemo4.java中明确展示了这一点。

四、实战案例:用Stream+Collections实现斗地主游戏

理论学完,我们用一个斗地主游戏实战,结合Stream流和Collections工具类,实现洗牌、发牌、排序的完整流程。

1. 核心流程

  1. 准备牌 :定义Card类封装牌的点数、花色、权重,初始化54张牌
  2. 洗牌 :用Collections.shuffle()打乱牌序
  3. 发牌:循环分发51张牌,剩余3张作为底牌
  4. 排序 :用Collections.sort()结合自定义比较器,按牌权重降序排列
  5. 抢地主:将底牌分给地主玩家

2. 关键代码:牌的排序逻辑

scss 复制代码
private void sortCards(List<Card> cards) {
    // 按牌的权重降序排列(权重num越大,牌越大)
    Collections.sort(cards, (o1, o2) -> o2.getNum() - o1.getNum());
}

💡 技巧 :这里用Collections.sort的比较器,和Stream的sorted方法逻辑一致,体现了Java比较器的通用性。

五、进阶技巧:自定义对象排序

Teacher.java中,我们看到如何为自定义对象实现排序:

less 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher implements Comparable<Teacher> {
    private String name;
    private int age;
    private double salary;
    
    @Override
    public int compareTo(Teacher o) {
        // 按年龄升序
        return this.age - o.age;
        // return o.age - this.age; // 降序
    }
}

重要提示 :如果希望使用sorted()方法对自定义对象排序,必须实现Comparable接口,或提供自定义比较器。

六、常见错误与解决方案

1. 自定义对象去重失败

scss 复制代码
// 错误:自定义对象默认无法去重
scores.stream().sorted().distinct().forEach(System.out::println);

解决方案 :重写hashCodeequals方法:

kotlin 复制代码
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Teacher teacher = (Teacher) o;
    return age == teacher.age && Double.compare(teacher.salary, salary) == 0 && Objects.equals(name, teacher.name);
}

@Override
public int hashCode() {
    return Objects.hash(name, age, salary);
}

2. 流重复使用

ini 复制代码
Stream<String> s1 = list.stream().filter(name -> name.startsWith("张"));
List<String> list1 = s1.collect(Collectors.toList());
// 重复使用s1会抛出异常
List<String> list2 = s1.collect(Collectors.toList());

解决方案:每个Stream流只能被收集一次,如需多次使用,应重新获取流。

七、学习总结

Stream流是JDK8的核心特性之一,它不是替代集合/数组,而是操作集合/数组的高效工具

  1. 获取流:从集合、Map、数组中获取Stream流
  2. 中间方法filtersortedmap等,支持链式调用
  3. 终结方法forEachcountcollect等,触发实际计算
  4. 惰性求值:只有在终结方法被调用时才执行操作
  5. 与Lambda结合:让代码更简洁,业务逻辑更清晰

掌握Stream流,能大幅提升集合操作的开发效率,告别"屎山"遍历代码!

写在最后

如果你也被传统集合遍历折磨过,不妨试试Stream流,相信会打开Java数据处理的新世界。后续可以多尝试用Stream重构旧代码,感受它的便捷性!

附:Stream流常用方法速查表

类别 方法 说明
获取流 list.stream() 集合获取流
Arrays.stream(arr) 数组获取流
Stream.of(...) 静态方法获取流
中间方法 filter() 过滤数据
sorted() 排序
map() 转换数据
limit() 取前n个
skip() 跳过前n个
distinct() 去重
终结方法 forEach() 遍历
count() 统计
max()/min() 获取最大/最小值
collect() 收集结果
toArray() 转数组

💡 小贴士 :在IDE中,Stream流的链式调用可以按Ctrl+Enter换行,让代码更易读。


现在,你准备好用Stream流重写你的集合操作代码了吗? 评论区留下你的实践心得,我们一起交流进步!

相关推荐
踏浪无痕14 小时前
缓存一致性的工业级解法:用Java实现Facebook租约机制
后端·面试·架构
remaindertime14 小时前
一文掌握 Spring AI:集成主流大模型的完整方案与思考
后端·spring·ai编程
Lear14 小时前
【JavaSE】多态深度解析:从核心概念到最佳实践
后端
零度@14 小时前
Spring 同方法自调用导致 @Transactional 失效
java·性能优化
血小溅14 小时前
SpringBoot 整合 QLExpress 教程:患者随访画像多条件适配案例
后端
毕设源码-邱学长14 小时前
【开题答辩全过程】以 社团管理系统为例,包含答辩的问题和答案
java·eclipse
cherry有点甜·14 小时前
如何获取命令行的配置
java·开发语言
HelloReader14 小时前
从 Rocket 0.4 升级到 0.5一份实战迁移指南
后端·rust
ChrisitineTX14 小时前
Spring Boot 3 + GraalVM Native Image 原理:从启动 10秒 到 0.05秒,AOT 编译到底干了什么?
java·spring boot·后端