30_Java Stream流操作全解

Java Stream流操作全解

文章目录

前言

Stream API是Java 8引入的一套用于处理集合数据的声明式编程API,它配合Lambda表达式,使得对集合的过滤、映射、排序、聚合等操作变得异常简洁高效。Stream不是数据结构,不存储数据,它更像是一个高级迭代器,提供了一套链式操作流水线。本文将从Stream的创建开始,深入讲解中间操作、终端操作以及并行流的使用,帮助你全面掌握Stream API。

为什么要学Stream? 在实际项目中,处理集合数据是家常便饭:从数据库查出1000条订单,需要过滤出有效订单、按用户分组、统计金额、排序后输出。如果用传统的for循环+if判断,代码冗长且容易出错;而Stream只需几行链式调用就能完成。更关键的是,Stream的惰性求值和并行流特性,能在数据量大时显著提升性能。很多面试官也会围绕Stream考核候选人对Java 8新特性的掌握程度,尤其是mapflatMap的区别、reduce归约操作、Collectors.groupingBy分组等高频考点。

一、Stream的核心概念

1.1 什么是Stream

Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

java 复制代码
// 对比:传统集合操作 vs Stream操作
List<String> names = Arrays.asList("张三", "李四", "王五", "赵六");

// 传统方式:for循环 + if判断
List<String> result1 = new ArrayList<>();
for (String name : names) {
    if (name.startsWith("张")) {
        result1.add(name);
    }
}

// Stream方式:声明式、链式调用
List<String> result2 = names.stream()
        .filter(name -> name.startsWith("张"))
        .collect(Collectors.toList());

1.2 Stream操作的三个步骤

复制代码
[数据源] → [中间操作链] → [终端操作] → [结果]
  1. 创建Stream:从数据源(集合、数组、I/O等)获取流
  2. 中间操作:对数据进行过滤、映射、排序等(返回新Stream,可链式调用)
  3. 终端操作:执行计算并产生结果(触发实际计算,关闭流)
java 复制代码
// 示例:三步曲
List<Integer> result = list.stream()             // 1. 创建Stream
        .filter(x -> x > 10)                     // 2. 中间操作
        .sorted()                                // 2. 中间操作
        .collect(Collectors.toList());           // 3. 终端操作

重要特性

  • 惰性求值 :终端操作未调用前,中间操作不会真正执行。打个比方,filtermap这些中间操作只是在"构建执行计划",真正遍历数据的工作要等到collectforEach等终端操作调用时才开始。这意味着你可以构建非常复杂的操作链,而不用担心中间产生大量临时对象。但这也带来了调试上的挑战------如果你在中间操作里打了断点,必须调用终端操作才会触发。
  • 不可重用 :Stream一旦被终端操作消费后就不能再使用。如果尝试对同一个Stream实例调用两次终端操作,JVM会抛出IllegalStateException: stream has already been operated upon or closed。这是一个常见的初学者错误:先调了count()想看数量,再用同一个Stream做collect,结果直接报错。正确的做法是重新从数据源创建新的Stream,或者先用collect收集到List再操作。
  • 不修改数据源:Stream操作不会改变原始数据源,无论怎么过滤、映射,原始集合的内容保持不变。这种不可变性使得代码更安全,也方便在多线程环境下共享数据源。

二、创建Stream

2.1 从集合创建

java 复制代码
import java.util.*;
import java.util.stream.*;

public class CreateStreamDemo {
    public static void main(String[] args) {
        // 从Collection创建(最常用)
        List<String> list = Arrays.asList("a", "b", "c");
        Stream<String> stream1 = list.stream();           // 串行流
        Stream<String> stream2 = list.parallelStream();   // 并行流

        // 从数组创建
        String[] arr = {"x", "y", "z"};
        Stream<String> stream3 = Arrays.stream(arr);
        Stream<String> stream4 = Stream.of(arr);
        Stream<String> stream5 = Stream.of("x", "y", "z");

        // 从Map创建(需要先获取entrySet/keySet/values)
        Map<String, Integer> map = new HashMap<>();
        map.put("张三", 85);
        map.put("李四", 92);
        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();
        Stream<String> keyStream = map.keySet().stream();
        Stream<Integer> valueStream = map.values().stream();
    }
}

2.2 生成Stream

java 复制代码
public class GenerateStreamDemo {
    public static void main(String[] args) {
        // Stream.iterate() --- 无限迭代流(有初始值)
        Stream<Integer> iterateStream = Stream.iterate(1, n -> n + 2);
        // 取前10个奇数
        iterateStream.limit(10).forEach(n -> System.out.print(n + " "));
        // 输出:1 3 5 7 9 11 13 15 17 19
        System.out.println();

        // Stream.generate() --- 无限生成流(无初始值,靠Supplier)
        Stream<Double> randomStream = Stream.generate(Math::random);
        randomStream.limit(5).forEach(n ->
                System.out.printf("%.4f ", n));
        System.out.println();

        // 使用IntStream等数值流
        IntStream intStream = IntStream.range(1, 11);       // 1~10(不包含11)
        System.out.println("1~10的和:" + intStream.sum());

        IntStream closedRange = IntStream.rangeClosed(1, 10); // 1~10(包含10)
        System.out.println("1~10的平均值:" + closedRange.average().getAsDouble());

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

        // 连接两个流
        Stream<String> combined = Stream.concat(
                Stream.of("A", "B"),
                Stream.of("C", "D")
        );
        combined.forEach(System.out::print);  // 输出:ABCD
    }
}

三、中间操作

中间操作分为无状态操作 (不需要知道全部元素)和有状态操作(需要处理所有元素)。

3.1 筛选与切片

java 复制代码
import java.util.*;
import java.util.stream.*;

public class FilterSliceDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // filter(Predicate) --- 过滤
        List<Integer> evens = numbers.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());
        System.out.println("偶数:" + evens);  // [2, 4, 6, 8, 10]

        // distinct() --- 去重(基于equals)
        List<String> words = Arrays.asList("apple", "banana", "apple", "cherry", "banana");
        List<String> unique = words.stream()
                .distinct()
                .collect(Collectors.toList());
        System.out.println("去重后:" + unique);  // [apple, banana, cherry]

        // limit(n) --- 截取前n个
        List<Integer> first3 = numbers.stream()
                .limit(3)
                .collect(Collectors.toList());
        System.out.println("前3个:" + first3);  // [1, 2, 3]

        // skip(n) --- 跳过前n个
        List<Integer> after5 = numbers.stream()
                .skip(5)
                .collect(Collectors.toList());
        System.out.println("跳过5个后:" + after5);  // [6, 7, 8, 9, 10]
    }
}

3.2 映射

java 复制代码
public class MapDemo {
    public static void main(String[] args) {
        List<String> words = Arrays.asList("Hello", "World", "Java", "Stream");

        // map(Function) --- 一对一映射
        List<Integer> lengths = words.stream()
                .map(String::length)
                .collect(Collectors.toList());
        System.out.println("每个单词长度:" + lengths);  // [5, 5, 4, 6]

        // mapToInt / mapToDouble --- 返回数值流(有sum、average等方法)
        int totalLength = words.stream()
                .mapToInt(String::length)
                .sum();
        System.out.println("总长度:" + totalLength);  // 20

        // flatMap --- 一对多映射(将多个流合并为一个流)
        List<String> allChars = words.stream()
                .map(word -> word.split(""))      // Stream<String[]>
                .flatMap(Arrays::stream)           // Stream<String>
                .distinct()
                .collect(Collectors.toList());
        System.out.println("所有不重复字符:" + allChars);

        // flatMap 更直观的示例 --- 合并嵌套集合
        List<List<Integer>> nested = Arrays.asList(
                Arrays.asList(1, 2, 3),
                Arrays.asList(4, 5),
                Arrays.asList(6, 7, 8)
        );
        List<Integer> flattened = nested.stream()
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
        System.out.println("扁平化后:" + flattened);  // [1,2,3,4,5,6,7,8]
    }
}

map vs flatMap

  • map:一个输入元素映射为一个输出元素(1:1)
  • flatMap:一个输入元素映射为一个流,然后将多个流合并为一个流(1:N)

这是面试中的高频考点。你可以这样理解:map像是"一对一翻译"------把英文单词翻译成中文,每个输入对应一个输出;flatMap则是"一对多展开"------把一句话拆成单个字,再把所有字合并成一个字列表。实际开发中,flatMap最常见的用途是扁平化嵌套集合(如List<List<String>>)、拆分字符串后合并、以及处理Optional对象。

常见错误 :很多开发者会在map里返回一个Stream,结果得到的不是预期的Stream<String>而是Stream<Stream<String>>,这就是忘记用flatMap的典型症状。如果你用map后发现返回类型嵌套了一层stream,就应该换成flatMap

3.3 排序

java 复制代码
public class SortDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Charlie", "Alice", "Bob", "David");

        // sorted() --- 自然排序
        List<String> natural = names.stream()
                .sorted()
                .collect(Collectors.toList());
        System.out.println("自然排序:" + natural);  // [Alice, Bob, Charlie, David]

        // sorted(Comparator) --- 自定义排序
        List<String> byLength = names.stream()
                .sorted(Comparator.comparingInt(String::length))
                .collect(Collectors.toList());
        System.out.println("按长度排序:" + byLength);

        // 降序排序
        List<String> reversed = names.stream()
                .sorted(Comparator.reverseOrder())
                .collect(Collectors.toList());
        System.out.println("降序排序:" + reversed);
    }
}

3.4 peek --- 调试利器

peek是一个非常实用的调试方法,它允许你在流处理的每个步骤中"偷看"元素当前的状态。与forEach不同的是,peek是中间操作(惰性求值),不会终止流,而forEach是终端操作。在复杂的数据处理管道中,peek能帮你快速定位是哪个环节出了问题------比如数据在filter之后数量不符预期,在map之后值不对等等。

java 复制代码
public class PeekDemo {
    public static void main(String[] args) {
        List<Integer> result = Stream.of(1, 2, 3, 4, 5)
                .peek(x -> System.out.println("原始:" + x))
                .filter(x -> x % 2 == 0)
                .peek(x -> System.out.println("过滤后:" + x))
                .map(x -> x * x)
                .peek(x -> System.out.println("平方后:" + x))
                .collect(Collectors.toList());

        System.out.println("最终结果:" + result);  // [4, 16]
    }
}

四、终端操作

4.1 匹配与查找

匹配操作是Stream中最常用的短路操作之一------也就是说,一旦找到满足条件的结果,就不会继续遍历后续元素。这个特性在大数据集上能显著提升性能。三个匹配方法各有分工:

java 复制代码
public class MatchFindDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(3, 7, 1, 9, 5, 8, 2);

        // allMatch --- 所有元素都满足条件
        boolean allPositive = numbers.stream().allMatch(n -> n > 0);
        System.out.println("全部为正?" + allPositive);  // true

        // anyMatch --- 任意元素满足条件
        boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
        System.out.println("存在偶数?" + hasEven);  // true

        // noneMatch --- 没有元素满足条件
        boolean noneNegative = numbers.stream().noneMatch(n -> n < 0);
        System.out.println("没有负数?" + noneNegative);  // true

        // findFirst --- 返回第一个元素
        Optional<Integer> first = numbers.stream()
                .filter(n -> n > 5)
                .findFirst();
        first.ifPresent(n -> System.out.println("第一个大于5的数:" + n));

        // findAny --- 返回任意元素(并行流中更高效)
        Optional<Integer> any = numbers.parallelStream()
                .filter(n -> n > 5)
                .findAny();
        any.ifPresent(n -> System.out.println("任意大于5的数:" + n));
    }
}

4.2 聚合与归约

聚合操作让原本需要写循环和临时变量才能完成的统计计算变成一行代码。其中reduce是最灵活也最难掌握的操作------它本质上是一个"折叠"过程:用一个初始值和一个二元操作,将流中所有元素逐个合并,最终得到一个结果。面试中常见的"用Stream实现阶乘""用Stream找最大值"都可以用reduce解决。

常见错误 :使用reduce时一定要注意结合性。比如用reduce(0, (a,b) -> a - b)求所有元素的差,但减法不满足结合律,并行流下结果可能不同。正是为了避免这类问题,IntStream等数值流提供了sum()average()等专用方法,应优先使用。

java 复制代码
public class ReduceDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // count --- 计数
        long count = numbers.stream().count();
        System.out.println("元素个数:" + count);

        // max / min
        Optional<Integer> max = numbers.stream().max(Integer::compareTo);
        Optional<Integer> min = numbers.stream().min(Integer::compareTo);
        System.out.println("最大值:" + max.get());
        System.out.println("最小值:" + min.get());

        // sum / average(仅数值流)
        double avg = numbers.stream().mapToInt(Integer::intValue).average().getAsDouble();
        System.out.println("平均值:" + avg);

        // reduce --- 归约(最强大的聚合操作)
        // 求和:有初始值
        Integer sum = numbers.stream().reduce(0, (a, b) -> a + b);
        System.out.println("求和(有初始值):" + sum);

        // 求和:无初始值(返回Optional)
        Optional<Integer> sumOpt = numbers.stream().reduce((a, b) -> a + b);
        System.out.println("求和(无初始值):" + sumOpt.get());

        // 求乘积
        Integer product = numbers.stream().reduce(1, (a, b) -> a * b);
        System.out.println("1*2*3*4*5 = " + product);  // 120

        // 求最大值用reduce
        Optional<Integer> maxByReduce = numbers.stream()
                .reduce((a, b) -> a > b ? a : b);
        System.out.println("最大值(reduce):" + maxByReduce.get());

        // 字符串拼接
        List<String> words = Arrays.asList("Java", "Stream", "API");
        String joined = words.stream()
                .reduce("", (a, b) -> a + (a.isEmpty() ? "" : "-") + b);
        System.out.println("拼接结果:" + joined);  // Java-Stream-API
    }
}

4.3 收集操作

收集操作是Stream中最强大的终端操作之一。Collectors工具类提供了几十种现成的收集器,能满足绝大多数业务需求。下面演示最常用的几种,但在实际项目中需要特别注意:

toMap的key冲突问题 :当多个元素映射到同一个key时,toMap会抛出IllegalStateException: Duplicate key。解决方案是提供第三个参数------合并函数,例如(oldVal, newVal) -> newVal保留新值,或Integer::sum累加。很多线上bug就是因为忽略了key冲突引起的。

groupingBy的性能考量 :分组后的结果默认是HashMap,无序。如果需要保持元素顺序,可以用groupingBy(classifier, LinkedHashMap::new, downstream)指定LinkedHashMap作为容器。

java 复制代码
import java.util.*;
import java.util.stream.*;

public class CollectDemo {
    public static void main(String[] args) {
        List<String> names = Arrays.asList(
                "张三", "李四", "王五", "赵六", "孙七", "周八"
        );

        // toList() --- 收集为List
        List<String> list = names.stream()
                .filter(n -> n.length() == 2)
                .collect(Collectors.toList());

        // toSet() --- 收集为Set(去重)
        Set<Integer> lengthSet = names.stream()
                .map(String::length)
                .collect(Collectors.toSet());

        // toMap() --- 收集为Map
        Map<String, Integer> map = names.stream()
                .collect(Collectors.toMap(
                        name -> name,           // keyMapper
                        String::length          // valueMapper
                ));
        System.out.println("Map: " + map);

        // joining() --- 字符串拼接
        String joined = names.stream().collect(Collectors.joining(", ", "[", "]"));
        System.out.println("拼接:" + joined);  // [张三, 李四, 王五, 赵六, 孙七, 周八]

        // groupingBy --- 分组
        Map<Integer, List<String>> byLength = names.stream()
                .collect(Collectors.groupingBy(String::length));
        System.out.println("按长度分组:");
        byLength.forEach((len, group) ->
                System.out.println("  长度" + len + ": " + group));

        // partitioningBy --- 分区(分true/false两组)
        Map<Boolean, List<String>> byStartWithZhang = names.stream()
                .collect(Collectors.partitioningBy(n -> n.startsWith("张")));
        System.out.println("是否姓张的分区:");
        System.out.println("  姓张:" + byStartWithZhang.get(true));
        System.out.println("  不姓张:" + byStartWithZhang.get(false));

        // summarizing --- 统计汇总
        IntSummaryStatistics stats = names.stream()
                .collect(Collectors.summarizingInt(String::length));
        System.out.println("统计信息:count=" + stats.getCount()
                + ", sum=" + stats.getSum()
                + ", min=" + stats.getMin()
                + ", max=" + stats.getMax()
                + ", avg=" + stats.getAverage());
    }
}

五、并行流

**并行流(Parallel Stream)**可以将操作自动分配到多个CPU核心上并行执行:

java 复制代码
import java.util.*;
import java.util.stream.*;

public class ParallelStreamDemo {
    public static void main(String[] args) {
        // 创建并行流的方式
        List<Integer> numbers = IntStream.rangeClosed(1, 100)
                .boxed().collect(Collectors.toList());

        // 方式一:parallelStream()
        Stream<Integer> parallel1 = numbers.parallelStream();

        // 方式二:stream().parallel()
        Stream<Integer> parallel2 = numbers.stream().parallel();

        // 方式三:parallel() 转换为并行流,sequential() 转回串行流
        numbers.stream()
                .parallel()    // 转为并行流
                .filter(n -> n > 50)
                .sequential()  // 转回串行流(以后面的为准)
                .forEach(n -> System.out.print(n + " "));

        // 性能对比
        long start, end;
        final int SIZE = 10_000_000;

        // 串行流
        start = System.currentTimeMillis();
        long count1 = Stream.iterate(0L, n -> n + 1)
                .limit(SIZE)
                .filter(n -> n % 2 == 0)
                .count();
        end = System.currentTimeMillis();
        System.out.println("\n串行流耗时:" + (end - start) + "ms, 偶数个数:" + count1);

        // 并行流
        start = System.currentTimeMillis();
        long count2 = Stream.iterate(0L, n -> n + 1)
                .limit(SIZE)
                .parallel()
                .filter(n -> n % 2 == 0)
                .count();
        end = System.currentTimeMillis();
        System.out.println("并行流耗时:" + (end - start) + "ms, 偶数个数:" + count2);
        // 注意:iterate()产生的流有序,限制了并行效率,此处只是示例
    }
}

并行流使用注意事项

  1. 数据量大时才有优势:小数据集用串行流即可。并行流涉及线程调度和结果合并的开销,数据量小于几千条时,串行可能更快。
  2. 避免有状态操作 :并行流中的操作应是无状态、无副作用的。比如在filter的lambda中修改外部变量,可能导致不可预期的结果。
  3. 线程安全 :如果最终要收集到集合,使用toConcurrentMap()等线程安全的收集器,而非普通的toList()/toMap()。普通的收集器在并行流下虽然由JDK内部做了合并处理,但性能不如专门设计的并发收集器。
  4. 注意装箱开销 :尽量使用IntStreamLongStreamDoubleStream等原始类型流,避免频繁的装箱/拆箱。
  5. 不适合IO密集型任务 :并行流使用的是ForkJoinPool.commonPool(),默认线程数为CPU核心数-1。这意味着它适合CPU密集型计算。如果在流中执行数据库查询、网络请求等IO操作,反而会阻塞公共线程池,影响整个JVM的其他并行任务。
java 复制代码
// 并行流中线程安全的收集
ConcurrentMap<String, List<String>> safeMap = names.parallelStream()
        .collect(Collectors.groupingByConcurrent(
                String::toUpperCase
        ));

// 并行流 + forEachOrdered 保证顺序
names.parallelStream().forEachOrdered(System.out::println);

六、综合实战案例

java 复制代码
import java.util.*;
import java.util.stream.*;

public class ComprehensiveDemo {
    public static void main(String[] args) {
        // 准备测试数据
        List<Transaction> transactions = Arrays.asList(
                new Transaction("张三", "北京", 2024, 1500),
                new Transaction("李四", "上海", 2024, 2500),
                new Transaction("王五", "北京", 2023, 800),
                new Transaction("赵六", "广州", 2024, 3200),
                new Transaction("张三", "深圳", 2024, 1200),
                new Transaction("李四", "上海", 2023, 1800),
                new Transaction("孙七", "北京", 2024, 4500)
        );

        // 需求1:找出2024年的所有交易,按金额降序排列
        System.out.println("=== 2024年交易(金额降序) ===");
        transactions.stream()
                .filter(t -> t.getYear() == 2024)
                .sorted(Comparator.comparing(Transaction::getAmount).reversed())
                .forEach(System.out::println);

        // 需求2:按城市分组,统计每个城市的总交易额
        System.out.println("\n=== 各城市总交易额 ===");
        Map<String, Integer> cityTotal = transactions.stream()
                .collect(Collectors.groupingBy(
                        Transaction::getCity,
                        Collectors.summingInt(Transaction::getAmount)
                ));
        cityTotal.forEach((city, total) ->
                System.out.println(city + ": " + total));

        // 需求3:每个人的平均交易额
        System.out.println("\n=== 每人平均交易额 ===");
        Map<String, Double> avgByPerson = transactions.stream()
                .collect(Collectors.groupingBy(
                        Transaction::getPerson,
                        Collectors.averagingDouble(Transaction::getAmount)
                ));
        avgByPerson.forEach((person, avg) ->
                System.out.printf("%s: %.2f%n", person, avg));

        // 需求4:交易额最高的交易记录
        System.out.println("\n=== 最高交易额记录 ===");
        transactions.stream()
                .max(Comparator.comparingInt(Transaction::getAmount))
                .ifPresent(System.out::println);

        // 需求5:所有交易金额的统计信息
        System.out.println("\n=== 交易金额统计 ===");
        IntSummaryStatistics stats = transactions.stream()
                .collect(Collectors.summarizingInt(Transaction::getAmount));
        System.out.println("总金额:" + stats.getSum());
        System.out.println("平均金额:" + stats.getAverage());
        System.out.println("最高金额:" + stats.getMax());
        System.out.println("最低金额:" + stats.getMin());
        System.out.println("交易数:" + stats.getCount());
    }
}

class Transaction {
    private String person;
    private String city;
    private int year;
    private int amount;

    public Transaction(String person, String city, int year, int amount) {
        this.person = person;
        this.city = city;
        this.year = year;
        this.amount = amount;
    }

    public String getPerson() { return person; }
    public String getCity() { return city; }
    public int getYear() { return year; }
    public int getAmount() { return amount; }

    @Override
    public String toString() {
        return String.format("Transaction{人=%s, 城市=%s, 年=%d, 金额=%d}",
                person, city, year, amount);
    }
}

七、Stream vs 传统循环:选型指南

很多开发者会有疑问:Stream这么好,是不是所有循环都应该改成Stream?答案是视场景而定。以下是选型建议:

  • 优先用Stream的场景:过滤+映射+收集的组合操作、需要对数据进行分组和聚合统计、链式操作超过3步的复杂转换。代码可读性会明显提升。
  • 优先用传统循环的场景 :简单的遍历(3步以内的操作)、需要访问循环索引、需要从循环中提前breakreturn(Stream的findFirst虽然能模拟,但不够直观)、需要在lambda中抛出受检异常(需要额外的try-catch包装)。
  • 性能考量 :小数据量下两者差异微乎其微;大数据量下,Stream的惰性求值加上findFirst等短路操作可能比for循环更快,但需要实测。不要盲目相信"Stream更快"或"Stream更慢"的说法。

八、Stream调试技巧

java 复制代码
import java.util.stream.*;

public class StreamDebug {
    public static void main(String[] args) {
        // 技巧1:使用peek查看中间状态
        IntStream.rangeClosed(1, 10)
                .peek(n -> System.out.println("处理前:" + n))
                .filter(n -> n % 2 == 0)
                .peek(n -> System.out.println("过滤后偶数:" + n))
                .map(n -> n * n)
                .peek(n -> System.out.println("平方后:" + n))
                .sum();

        // 技巧2:IDEA断点调试 --- 在Stream链上打断点
        // 可使用"Trace Current Stream Chain"查看流的状态

        // 技巧3:分步调试
        Stream<Integer> step1 = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> step2 = step1.filter(n -> n > 2);
        Stream<Integer> step3 = step2.map(n -> n * 10);
        step3.forEach(System.out::println);
    }
}

总结

本文全面讲解了Java Stream API的使用方法:

  • 创建Stream :从集合(stream())、数组(Arrays.stream())、值(Stream.of())以及iterate/generate生成
  • 中间操作
    • 筛选切片:filterdistinctlimitskip
    • 映射:map(1:1)、flatMap(1:N)、mapToInt
    • 排序:sorted自然排序、sorted(Comparator)自定义排序
    • 调试:peek查看中间状态
  • 终端操作
    • 匹配查找:allMatchanyMatchnoneMatchfindFirstfindAny
    • 聚合归约:countmaxminsumreduce
    • 收集:toListtoSettoMapjoininggroupingBypartitioningBy
  • 并行流parallelStream()多核并行处理,注意线程安全和数据结构选择
  • 惰性求值:中间操作不会立即执行,在终端操作调用时才触发

Stream API是Java函数式编程的核心组件,掌握后可以写出更简洁、更高效的集合处理代码。

✅ 亮点总结

  • 创建Stream的多种方式(集合、数组、值、iterate/generate),覆盖所有数据源场景
  • 中间操作与终端操作的清晰划分------filtermapflatMapsortedpeek 各司其职
  • groupingBypartitioningByjoining 等高级收集器,一行代码完成复杂分组聚合
  • Collectors.toMap 的key冲突解决方案与 collectingAndThen 的组合用法
  • 惰性求值机制(中间操作不触发,终端操作才执行)的深入理解,优化性能

适用场景

  • 日志分析:对大量日志行做 filtermapgroupingBy,快速统计异常频率和分布
  • 订单处理:筛选有效订单 → 按用户分组 → 计算总金额 → 按金额排序,一套Stream链完成
  • 数据ETL:从数据库查出原始数据 → flatMap 展开关联 → filter 清洗 → collect 输出

扩展方向

  • 并行流深入:掌握ForkJoinPool原理,了解并行流的适用场景与线程安全问题
  • Collectors高级用法teeingmappingfiltering 等Java 9+新增收集器
  • Reactive Streams:从同步Stream过渡到异步响应式流(推荐阅读上一篇:Java Lambda表达式入门)

上一篇:Java Lambda表达式入门(29_Java Lambda表达式入门.md)

相关推荐
qq_2518364572 小时前
基于java Web网络订餐系统设计与实现 源码文档
java·开发语言·前端
秋92 小时前
3年经验Python后端转AI Engineer:3个月实战转型计划(2026版)
开发语言·人工智能·python
凡人叶枫2 小时前
Effective C++ 条款17:以独立语句将 newed 对象置入智能指针
java·linux·开发语言·c++·算法
飞天狗1112 小时前
零基础JavaWeb入门——第2课:让网页“活”起来 —— JSP是什么?
java·开发语言·前端·后端·web
2601_956319882 小时前
期货夜盘无人值守监控什么:断线、无成交与拒单信号
python·区块链
CTA终结者2 小时前
期货量化目标仓和净持仓对不齐:天勤 TargetPosTask 与 pos 偏差排查
python·区块链
梦@_@境3 小时前
面向 Spring Boot 的可观测业务流程编排引擎
java·spring boot·后端
科技林总3 小时前
解决vllm服务漏扫问题
python·安全
云烟成雨TD3 小时前
Spring AI Alibaba 1.x 系列【77】执行取消
java·人工智能·spring