Stream 流是 Java8 新增,用于集合 / 数组的函数式编程,简化遍历、筛选、转换、聚合代码,告别繁琐 for 循环,链式调用。
包:
java.util.stream.*
一、Stream 流定义与核心概念
1. 什么是 Stream 流
Stream(流)是数据处理流水线 ,不存储数据,数据源来自集合、数组等。它可以对数据源做一系列中间操作,最后通过终端操作得到结果。
类比工厂流水线:原材料(集合)→过滤、加工(中间操作)→产出成品(终端操作)
2. Stream 五大核心特性
- 无存储:Stream 本身不保存元素,元素保存在原始数据源。流只是操作规则。
- 惰性求值(延迟执行) :中间操作不会立刻执行,只有调用终端操作时,所有中间操作才一次性执行。
- 不修改源数据:所有操作不会改动原始集合 / 数组,返回新流或新结果。
- 一次性消费 :流一旦执行终端操作,流就关闭销毁,不能重复使用,重复使用抛出
IllegalStateException。 - 支持串行 / 并行 :
stream()串行单线程;parallelStream()自动多线程并行处理大数据。
3. Stream 使用三步固定流程
① 获取流 :拿到数据源对应的 Stream 对象 ② 中间操作 :链式调用,对数据做多次处理(返回新 Stream) ③ 终端操作:触发流水线执行,生成结果,流关闭
必须:中间操作可以多个,终端操作只能一个,且写在最后
二、Stream 流的获取方式(4 大类)
java
import java.util.*;
import java.util.stream.Stream;
public class GetStreamDemo {
public static void main(String[] args) {
//1. Collection单列集合获取流:集合.stream()
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
Stream<String> parallelStream = list.parallelStream(); //并行流
//2. Map双列集合获取流(Map没有直接stream(),要转单列)
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. 数组获取流:Arrays.stream(数组)
String[] arr = {"A","B","C"};
Stream<String> stream3 = Arrays.stream(arr);
//4. 多个零散数据直接创建流:Stream.of(T... values)
Stream<String> stream4 = Stream.of("张三","李四","王五");
}
}
⚠️注意:
Stream.of()底层是数组,不能传 null 数组,会空指针。
三、Stream 操作分类(重点)
分为中间操作(Intermediate) 和终端操作(Terminal)
表格
| 分类 | 特点 | 常用 API | 返回值 |
|---|---|---|---|
| 中间操作 | 链式调用、惰性执行,返回新 Stream | filter、map、flatMap、sorted、distinct、limit、skip、peek | Stream<T> |
| 终端操作 | 触发流水线,执行完毕流关闭,不再返回 Stream | forEach、collect、count、max、min、reduce、anyMatch、allMatch、noneMatch | void / 普通值(Long、Optional、集合等) |
一句话区分:返回 Stream 就是中间操作;返回其他 /void 就是终端操作
四、常用中间操作详解 + 代码示例
1. filter 过滤(Predicate 接口)
作用:根据条件筛选元素,保留返回 true 的元素
java
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
//保留大于3的数字
List<Integer> res = list.stream()
.filter(num -> num >3)
.collect(Collectors.toList());
System.out.println(res); // [4,5,6]
2. map 映射转换(Function 接口)
作用:对流中每一个元素做类型 / 内容转换,一个元素映射成一个新元素
java
List<String> nameList = Arrays.asList("张三","李四","王五");
//把每个名字转为名字长度
List<Integer> lenList = nameList.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lenList); // [2,2,2]
对象场景:
map(Movie::getActor)提取电影对象的演员属性,就是你前面代码用到的!
3. flatMap 扁平化映射
作用:把每个元素转成流,然后把所有流合并成一个大流。用于拆分集合里面嵌套集合。
java
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("a","b"),
Arrays.asList("c","d")
);
//扁平化,把两层集合拆成单层流
List<String> flat = nestedList.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(flat); // [a,b,c,d]
4. sorted 排序
sorted():自然排序,元素类需要实现 Comparable 接口sorted(Comparator):自定义比较器排序(推荐)
java
List<Integer> nums = Arrays.asList(5,1,3,9,2);
//升序
List<Integer> sortAsc = nums.stream().sorted().collect(Collectors.toList());
//降序
List<Integer> sortDesc = nums.stream().sorted((o1,o2)->o2-o1).collect(Collectors.toList());
System.out.println(sortAsc); // [1,2,3,5,9]
5. distinct 去重
底层依靠equals()和hashCode()方法
java
List<Integer> numList = Arrays.asList(1,2,2,3,3,3);
List<Integer> distinctList = numList.stream()
.distinct()
.collect(Collectors.toList());
System.out.println(distinctList); // [1,2,3]
(tips)自定义对象去重
必须重写 equals 和 hashCode
对象去重要在实体类内部重写equals和hashcode方法(Stream底层去重会调用linkHashSet,会先比较哈希值,如果相同则会调用object原生equals默认比较存储地址)
- hashcode:由地址获取哈希值转变为属性获取
- equals:直接比较存储地址变为先比较地址,然后保证参数对象不为空且属于实体类,最后返回属性比较的布尔值
java
public class Student {
private Integer id;
private String name;
// 构造、get、set、toString
// ✅重写equals:按业务属性判断对象内容是否相等
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
// 业务:id和name相同,就认为是同一个学生
return Objects.equals(id, student.id) && Objects.equals(name, student.name);
}
// ✅重写hashCode:和equals使用完全相同的属性生成哈希码
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
6. limit (long maxSize):截取前 N 个元素
java
List<Integer> list = Arrays.asList(10,20,30,40,50);
List<Integer> limitList = list.stream()
.limit(3)
.collect(Collectors.toList());
System.out.println(limitList); // [10,20,30]
7. skip (long n):跳过前 N 个元素
java
List<Integer> list = Arrays.asList(10,20,30,40,50);
List<Integer> skipList = list.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println(skipList); // [30,40,50]
limit+skip 组合可以实现分页:skip (页码 - 1 * 每页条数).limit (每页条数)
8. peek 调试用,遍历元素,不修改流
多用于打印看中间数据,开发调试。
java
List<Integer> res = Stream.of(1,2,3)
.peek(num-> System.out.println("原始:"+num))
.map(n->n*2)
.collect(Collectors.toList());
五、常用终端操作详解 + 代码示例
1. forEach (Consumer):遍历流中元素
java
Stream.of("A","B","C").forEach(System.out::println);
❗注意:forEach 是终端操作,执行完流就关闭,不能继续链式调用。
2. collect (Collectors):收集,最常用!
把流的数据收集回集合(List/Set/Map),需要导入java.util.stream.Collectors
java
List<String> list = Stream.of("a","b","c").collect(Collectors.toList());
Set<String> set = Stream.of("a","b","c").collect(Collectors.toSet());
// 收集到Map:参数1-key,参数2-value,参数3解决key重复冲突
Map<String,Integer> map = Stream.of("张三","李四")
.collect(Collectors.toMap(s->s, s->s.length(),(oldVal,newVal)->oldVal));
Collectors.joining():字符串拼接
java
String str = Stream.of("张三","李四","王五").collect(Collectors.joining(","));
System.out.println(str); //张三,李四,王五
3. count ():统计元素个数,返回 long
java
long count = Stream.of(1,2,3,4).filter(n->n>2).count();
System.out.println(count); //2
4. max () /min ():获取最大 / 最小值,返回 Optional
java
Optional<Integer> max = Stream.of(5,2,9,1).max(Integer::compareTo);
Optional<Integer> min = Stream.of(5,2,9,1).min(Integer::compareTo);
System.out.println(max.get()); //9
5. reduce 归约、聚合,把流元素反复合并成一个结果
求和、求乘积
java
// 参数1:初始值;参数2:累加器
int sum = Stream.of(1,2,3,4).reduce(0,Integer::sum);
System.out.println(sum); //10
6. 匹配操作(返回 boolean)
allMatch():全部元素满足条件返回 trueanyMatch():任意一个满足返回 truenoneMatch():全部都不满足返回 true
java
List<Integer> num = Arrays.asList(2,4,6);
boolean allEven = num.stream().allMatch(n->n%2==0); //true
boolean has5 = num.stream().anyMatch(n->n==5); //false
六、原始类型流(IntStream、LongStream、DoubleStream)
普通Stream<Integer>会有自动装箱拆箱损耗,大数据推荐使用原始流。 mapToInt / mapToLong / mapToDouble 转为原始流
java
List<Integer> list = Arrays.asList(1,2,3,4);
int sum = list.stream().mapToInt(Integer::intValue).sum();
System.out.println(sum);
七、综合实战案例(电影对象)
场景:Movie 类,name 电影名,actor 演员名字;筛选出演员包含 star 的电影,收集为 List
java
class Movie{
private String name;
private String actor;
//构造器 get set toString
public String getActor() {return actor;}
}
public class MovieStreamDemo {
public static void main(String[] args) {
List<Movie> movies = new ArrayList<>();
movies.add(new Movie("电影1","张三"));
movies.add(new Movie("电影2","李四"));
movies.add(new Movie("电影3","张三"));
String star = "张三";
//Stream写法:筛选演员包含star的电影,收集
List<Movie> result = movies.stream()
.filter(movie -> movie.getActor().contains(star))
.collect(Collectors.toList());
// 删除符合条件的电影(就是你之前removeIf底层就是Predicate流思想)
movies.removeIf(movie -> movie.getActor().contains(star));
}
}
八、Stream 常见坑点
- 流只能使用一次! 终端调用之后流失效,再次使用抛异常
java
Stream<String> stream = Stream.of("a","b");
stream.forEach(System.out::println);
stream.count(); //报错 IllegalStateException
- 惰性求值陷阱:如果只有中间操作,没有终端操作,代码完全不执行!
java
Stream.of(1,2,3).filter(n->{
System.out.println(n);
return n>1;
});
// 控制台无任何输出!没有终端操作,中间代码不运行
- distinct 自定义对象必须重写 equals 和 hashCode
- 并行流
parallelStream()不要随便用,有线程安全问题,适合纯无状态数据。 - map 和 flatMap 容易混淆:map 一对一转换;flatMap 一对多,扁平化拆分流。
九、Stream 对比传统 for 循环
传统 for:怎么做(How),手动控制循环、索引、判断。
Stream:做什么(What),声明式编程,只写业务条件,不用关心循环细节,代码简洁。
Stream流
├─定义:Java8,流水线,不存数据,惰性求值
├─获取流:Collection.stream()|Map转单列|Arrays.stream|Stream.of
├─中间操作(返回Stream)
│ ├─filter过滤、map转换、flatMap扁平化
│ ├─sorted排序、distinct去重、limit截取、skip跳过
├─终端操作(关闭流)
│ ├─collect收集、forEach遍历、count计数
│ ├─max/min、reduce归约、allMatch/anyMatch匹配
└─特性:一次性消费、不修改源数据、串行并行