什么是Stream
一句话理解:Stream就是数据的流水线加工 ,把集合里的数据一个个送上传送带,经过过滤、转换、排序等操作,最后收集结果
注意:这个Stream跟IO流(InputStream/OutputStream)完全没关系,别搞混了
Stream三步曲:创建流 → 中间操作(加工) → 终端操作(收结果)
Lambda是Stream的基础,不熟悉Lambda的先看 Lambda表达式
创建Stream
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void testCreateStream () {System.out.println("=== 创建Stream ===" ); List<String> list = Arrays.asList("Java" , "Python" , "Go" ); Stream<String> stream1 = list.stream(); int [] arr = {1 , 2 , 3 , 4 , 5 };IntStream stream2 = Arrays.stream(arr);Stream<String> stream3 = Stream.of("A" , "B" , "C" ); Stream<Integer> stream4 = Stream.iterate(0 , n -> n + 2 ).limit(5 ); stream4.forEach(n -> System.out.print(n + " " )); System.out.println(); }
中间操作(懒执行)
中间操作不会立刻执行,只是在”攒计划”,等终端操作来了才一起干活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Test public void testMiddleOperations () {System.out.println("=== 中间操作 ===" ); List<Integer> numbers = Arrays.asList(3 , 1 , 4 , 1 , 5 , 9 , 2 , 6 , 5 , 3 ); numbers.stream() .filter(n -> n > 3 ) .forEach(n -> System.out.print(n + " " )); System.out.println(); List<String> names = Arrays.asList("alice" , "bob" , "charlie" ); names.stream() .map(String::toUpperCase) .forEach(s -> System.out.print(s + " " )); System.out.println(); numbers.stream() .sorted() .forEach(n -> System.out.print(n + " " )); System.out.println(); numbers.stream() .distinct() .forEach(n -> System.out.print(n + " " )); System.out.println(); numbers.stream() .limit(3 ) .forEach(n -> System.out.print(n + " " )); System.out.println(); numbers.stream() .skip(7 ) .forEach(n -> System.out.print(n + " " )); System.out.println(); }
flatMap:把嵌套结构”拍平”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void testFlatMap () {System.out.println("=== flatMap ===" ); List<List<String>> courses = Arrays.asList( Arrays.asList("数学" , "英语" ), Arrays.asList("物理" , "化学" ), Arrays.asList("历史" , "地理" ) ); courses.stream() .flatMap(Collection::stream) .forEach(c -> System.out.print(c + " " )); }
操作
作用
示例
filter
过滤
.filter(n -> n > 5)
map
转换
.map(String::toUpperCase)
sorted
排序
.sorted() 或 .sorted(Comparator)
distinct
去重
.distinct()
limit
取前N个
.limit(3)
skip
跳过前N个
.skip(2)
flatMap
拍平嵌套结构
.flatMap(Collection::stream)
peek
调试用,不影响流
.peek(System.out::println)
终端操作(触发执行)
终端操作是”收割”步骤,调用之后整个流水线才真正跑起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 @Test public void testTerminalOperations () {System.out.println("=== 终端操作 ===" ); List<Integer> numbers = Arrays.asList(1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ); numbers.stream().forEach(n -> System.out.print(n + " " )); System.out.println(); List<Integer> evenList = numbers.stream() .filter(n -> n % 2 == 0 ) .collect(Collectors.toList()); System.out.println("偶数列表:" + evenList); long count = numbers.stream().filter(n -> n > 5 ).count();System.out.println("大于5的个数:" + count); int sum = numbers.stream().reduce(0 , Integer::sum);System.out.println("求和:" + sum); Optional<Integer> min = numbers.stream().min(Integer::compareTo); Optional<Integer> max = numbers.stream().max(Integer::compareTo); System.out.println("最小值:" + min.get() + ",最大值:" + max.get()); boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0 );boolean allPositive = numbers.stream().allMatch(n -> n > 0 );boolean noNegative = numbers.stream().noneMatch(n -> n < 0 );System.out.println("有偶数?" + hasEven); System.out.println("全是正数?" + allPositive); System.out.println("没有负数?" + noNegative); }
关于 Optional类 的用法,min/max 返回的就是Optional
Collectors工具类
Collectors是收集器的工具箱,配合 collect() 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Test public void testCollectors () {System.out.println("=== Collectors ===" ); List<String> names = Arrays.asList("Alice" , "Bob" , "Charlie" , "Alice" , "David" ); List<String> list = names.stream().distinct().collect(Collectors.toList()); System.out.println("去重列表:" + list); Set<String> set = names.stream().collect(Collectors.toSet()); System.out.println("Set:" + set); String joined = names.stream().collect(Collectors.joining(", " ));System.out.println("拼接:" + joined); Map<String, Integer> nameLength = names.stream() .distinct() .collect(Collectors.toMap( name -> name, name -> name.length() )); System.out.println("名字长度Map:" + nameLength); Map<Integer, List<String>> grouped = names.stream() .collect(Collectors.groupingBy(String::length)); System.out.println("按长度分组:" + grouped); }
Collector
作用
示例
toList()
收集到List
.collect(Collectors.toList())
toSet()
收集到Set
.collect(Collectors.toSet())
toMap()
收集到Map
.collect(Collectors.toMap(k, v))
groupingBy()
分组
.collect(Collectors.groupingBy(分组条件))
joining()
拼接字符串
.collect(Collectors.joining(", "))
counting()
分组计数
groupingBy(xx, counting())
summarizingInt()
统计(sum/avg/min/max)
.collect(Collectors.summarizingInt(xx))
实战案例:学生列表处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @Test public void testStudentStream () {System.out.println("=== 学生列表Stream实战 ===" ); List<Student> students = Arrays.asList( new Student ("张三" , 85 , "计算机" ), new Student ("李四" , 92 , "数学" ), new Student ("王五" , 78 , "计算机" ), new Student ("赵六" , 95 , "数学" ), new Student ("孙七" , 60 , "英语" ), new Student ("周八" , 88 , "计算机" ), new Student ("吴九" , 70 , "英语" ) ); List<String> highScoreNames = students.stream() .filter(s -> s.getScore() > 80 ) .map(Student::getName) .collect(Collectors.toList()); System.out.println("80分以上:" + highScoreNames); List<Student> sorted = students.stream() .sorted((s1, s2) -> s2.getScore() - s1.getScore()) .collect(Collectors.toList()); sorted.forEach(s -> System.out.println(s.getName() + ": " + s.getScore())); Map<String, List<Student>> byDept = students.stream() .collect(Collectors.groupingBy(Student::getDepartment)); byDept.forEach((dept, list) -> { System.out.println(dept + ": " + list.size() + "人" ); }); Map<String, Double> avgByDept = students.stream() .collect(Collectors.groupingBy( Student::getDepartment, Collectors.averagingInt(Student::getScore) )); System.out.println("各专业平均分:" + avgByDept); Optional<Student> topStudent = students.stream() .max(Comparator.comparingInt(Student::getScore)); topStudent.ifPresent(s -> System.out.println("最高分:" + s.getName() + " " + s.getScore())); }
并行流 parallelStream
并行流会把数据拆成多块,用多个线程同时处理,理论上更快
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Test public void testParallelStream () {System.out.println("=== 并行流 ===" ); List<Integer> bigList = new ArrayList <>(); for (int i = 0 ; i < 1000000 ; i++) { bigList.add(i); } long start1 = System.currentTimeMillis();long count1 = bigList.stream() .filter(n -> n % 2 == 0 ) .count(); long time1 = System.currentTimeMillis() - start1;long start2 = System.currentTimeMillis();long count2 = bigList.parallelStream() .filter(n -> n % 2 == 0 ) .count(); long time2 = System.currentTimeMillis() - start2;System.out.println("串行:" + time1 + "ms,结果:" + count1); System.out.println("并行:" + time2 + "ms,结果:" + count2); }
什么时候用并行流
✅ 数据量大(几万条以上)
✅ 每个元素的处理比较耗时(比如网络请求、复杂计算)
✅ 操作无状态、无副作用
什么时候不要用并行流
❌ 数据量小(并行的线程调度开销可能比计算本身还大)
❌ 操作有顺序要求(并行流不保证顺序)
❌ 操作涉及共享可变状态(会出线程安全问题)
❌ 数据源是LinkedList(拆分效率低,ArrayList更适合并行)
常见坑
坑1:Stream只能消费一次!
1 2 3 4 5 6 7 @Test public void testStreamOnce () {Stream<String> stream = Stream.of("A" , "B" , "C" ); stream.forEach(System.out::println); }
坑2:中间操作是懒执行的,没有终端操作就不会执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testLazy () {List<String> names = Arrays.asList("Alice" , "Bob" ); names.stream().filter(n -> { System.out.println("过滤:" + n); return true ; }); names.stream().filter(n -> { System.out.println("过滤:" + n); return true ; }).collect(Collectors.toList()); }
坑3:在forEach里修改外部集合可能出问题
1 2 3 4 5 6 List<String> result = new ArrayList <>(); list.parallelStream().forEach(s -> result.add(s)); List<String> result = list.parallelStream().collect(Collectors.toList());
坑4:toMap的key重复会抛异常
1 2 3 4 5 6 7 8 9 list.stream().collect(Collectors.toMap(Student::getDepartment, Student::getName)); list.stream().collect(Collectors.toMap( Student::getDepartment, Student::getName, (old, newVal) -> old + "," + newVal ));
练习题
题1 :给定一个整数列表,用Stream找出所有偶数,排序后收集到新列表
题2 :给定字符串列表 ["hello", "world", "hello", "java"],用Stream统计每个字符串出现的次数(提示:groupingBy + counting)
题3 :用Stream把一个 List<String> 转成一个逗号分隔的字符串
题4 :用flatMap把 List<String> 中每个字符串拆成字符流,统计不重复字符的个数
提示:str.chars() 或 str.split("")
题5 :用reduce实现一个列表中所有数字的乘积