Java Stream 入门级教程:从基础到实战

Java 8 引入的 ​Stream API​ 是处理集合数据的革命性工具,它通过声明式编程​(告诉计算机“做什么”而非“怎么做”)简化了集合操作,支持链式调用、并行处理,并能显著提升代码可读性和开发效率。本教程将从基础概念出发,逐步带你掌握 Stream 的核心用法。

图片[1]_Java Stream 入门级教程:从基础到实战_知途无界

一、Stream 是什么?

1. 核心概念

  • 不是数据结构​:Stream 本身不存储数据,而是对数据源(如集合、数组)的抽象操作流水线
  • 惰性执行​:中间操作(如 filtermap)不会立即执行,只有遇到终止操作(如 collectforEach)时才会触发整个流水线处理。
  • 不可复用​:Stream 一旦被终止操作消费,就不能再次使用。

2. 数据源

Stream 的数据来源通常是集合(如 ListSet)、数组,或通过 Stream.of() 直接创建。


二、Stream 的基本操作流程

Stream 操作分为两大类:

  1. 中间操作​(返回新的 Stream,可链式调用)
    如:filter()map()sorted()distinct()
  2. 终止操作​(触发实际计算,返回结果或副作用)
    如:collect()forEach()count()reduce()

流程示例​:
数据源 → 中间操作1 → 中间操作2 → ... → 终止操作 → 结果


三、Stream 的创建方式

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

List<String> list = Arrays.asList("apple", "banana", "orange");
Stream<String> stream = list.stream(); // 创建普通顺序流
Stream<String> parallelStream = list.parallelStream(); // 创建并行流(多线程处理)

2. 从数组创建

String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);

3. 直接创建(静态方法)

// 创建包含指定元素的流
Stream<String> stream1 = Stream.of("hello", "world"); 

// 创建空流
Stream<Object> emptyStream = Stream.empty(); 

// 通过生成器创建无限流(需结合 limit 限制数量)
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2); // 生成 0, 2, 4, 6...
Stream<Integer> limitedStream = infiniteStream.limit(5); // 限制前5个:0, 2, 4, 6, 8

四、常用中间操作

1. 过滤:filter(Predicate)

筛选满足条件的元素。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
                                  .filter(n -> n % 2 == 0) // 保留偶数
                                  .collect(Collectors.toList()); // 终止操作:转为List
// 结果:[2, 4]

2. 映射:map(Function)

将每个元素转换为另一种形式。

List<String> words = Arrays.asList("java", "stream", "api");
List<Integer> wordLengths = words.stream()
                                 .map(String::length) // 转换为字符串长度
                                 .collect(Collectors.toList());
// 结果:[4, 6, 3]

3. 去重:distinct()

移除重复元素(依赖元素的 equals()hashCode())。

List<Integer> nums = Arrays.asList(1, 2, 2, 3, 3);
List<Integer> uniqueNums = nums.stream()
                               .distinct()
                               .collect(Collectors.toList());
// 结果:[1, 2, 3]

4. 排序:sorted()

自然排序或自定义排序。

List<String> fruits = Arrays.asList("banana", "apple", "orange");
// 自然排序(按字母顺序)
List<String> sortedFruits = fruits.stream()
                                  .sorted()
                                  .collect(Collectors.toList());
// 结果:[apple, banana, orange]

// 自定义排序(按字符串长度降序)
List<String> customSorted = fruits.stream()
                                  .sorted((s1, s2) -> s2.length() - s1.length())
                                  .collect(Collectors.toList());
// 结果:[banana, orange, apple]

5. 截取:limit()skip()

  • limit(n):保留前 n 个元素。
  • skip(n):跳过前 n 个元素。
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> firstThree = nums.stream()
                               .limit(3) // 取前3个
                               .collect(Collectors.toList());
// 结果:[1, 2, 3]

List<Integer> afterSkipTwo = nums.stream()
                                 .skip(2) // 跳过前2个
                                 .collect(Collectors.toList());
// 结果:[3, 4, 5]

五、常用终止操作

1. 收集结果:collect(Collector)

将 Stream 元素转换为集合或其他格式(最常用)。

// 转为 List
List<String> list = stream.collect(Collectors.toList());

// 转为 Set(自动去重)
Set<String> set = stream.collect(Collectors.toSet());

// 转为 Map(需指定键和值的生成规则)
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Map<String, Integer> nameLengths = names.stream()
                                        .collect(Collectors.toMap(
                                            name -> name,       // 键:原字符串
                                            name -> name.length() // 值:字符串长度
                                        ));
// 结果:{Alice=5, Bob=3, Charlie=7}

2. 遍历:forEach(Consumer)

对每个元素执行操作(无返回值)。

List<String> fruits = Arrays.asList("apple", "banana");
fruits.stream().forEach(System.out::println); // 依次打印:apple, banana

3. 统计:count()

返回元素总数。

long count = Arrays.asList(1, 2, 3).stream().count();
// 结果:3

4. 匹配:anyMatch() / allMatch() / noneMatch()

  • anyMatch(predicate):是否有任意元素满足条件。
  • allMatch(predicate):是否所有元素都满足条件。
  • noneMatch(predicate):是否所有元素都不满足条件。
List<Integer> nums = Arrays.asList(2, 4, 6);
boolean hasEven = nums.stream().anyMatch(n -> n % 2 == 0); // 是否有偶数?true
boolean allEven = nums.stream().allMatch(n -> n % 2 == 0); // 是否全是偶数?true

5. 查找:findFirst() / findAny()

  • findFirst():返回第一个元素(顺序流中确定,并行流中可能不确定)。
  • findAny():返回任意一个元素(并行流中更高效)。
Optional<Integer> first = Arrays.asList(1, 2, 3).stream()
                                    .findFirst();
first.ifPresent(System.out::println); // 输出:1

6. 聚合:reduce(BinaryOperator)

将元素合并为一个结果(如求和、拼接字符串)。

// 求和
List<Integer> nums = Arrays.asList(1, 2, 3);
int sum = nums.stream().reduce(0, (a, b) -> a + b); // 0 是初始值
// 结果:6

// 拼接字符串
List<String> words = Arrays.asList("Hello", "World");
String combined = words.stream().reduce("", (a, b) -> a + " " + b).trim();
// 结果:"Hello World"

六、实战示例

示例1:统计单词长度

List<String> sentences = Arrays.asList("Java is fun", "Stream API rocks");
List<Integer> wordLengths = sentences.stream()
                                    .flatMap(sentence -> Arrays.stream(sentence.split(" "))) // 拆分单词并扁平化为一个流
                                    .map(String::length)
                                    .collect(Collectors.toList());
// 结果:[4, 2, 3, 6, 3, 5](Java, is, fun, Stream, API, rocks 的长度)

示例2:分组统计

List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date");
// 按首字母分组
Map<Character, List<String>> grouped = fruits.stream()
                                             .collect(Collectors.groupingBy(s -> s.charAt(0)));
// 结果:{a=[apple], b=[banana], c=[cherry], d=[date]}

七、注意事项

  1. 避免副作用​:中间操作应尽量无副作用(如不修改外部变量),终止操作如 forEach 需谨慎使用(推荐用 collect 替代复杂逻辑)。
  2. 并行流慎用​:parallelStream() 虽能提升性能,但需确保操作是线程安全的(如共享变量加锁)。
  3. 短路操作​:部分终止操作(如 findFirstanyMatch)在满足条件时会提前终止,提升效率。

通过本教程,你已经掌握了 Stream 的基础用法!接下来可以尝试在项目中用 Stream 替换传统的 for 循环,体验更简洁高效的代码风格。 🚀

© 版权声明
THE END
喜欢就点个赞,支持一下吧!
点赞87 分享
评论 抢沙发
头像
欢迎您留下评论!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容