本文纲要
Stream流初体验:传统方式 vsStream方式Stream流的思想与特点- 如何获取
Stream流
单列集合
双列集合
数组
同种数据类型的多个数据 - 中间方法:
filter过滤 - 其他常用中间方法
limit截取
skip 跳过concat合并distinct` 去重 - 终结方法:
forEach与count Stream流的重要特性:不修改数据源- 收集方法:
toList和toSet - 收集方法:
toMap - 综合练习
Stream流初体验:传统方式 vs Stream方式
项目代码结构如下:
mystream/
└── src/
└── com/
└── wb/
└── streamdemo/
├── Actor.java
├── MyStream1.java
├── MyStream2.java
├── MyStream3.java
├── MyStream4.java
├── MyStream5.java
├── MyStream6.java
├── MyStream7.java
├── MyStream8.java
└── MyStream9.java
先看一个需求:创建一个集合存储多个字符串,把所有以“张”开头的元素筛选出来,再从中筛选出长度为3的元素,最后遍历。
传统方式 需要手动创建多个集合,循环判断添加:
// MyStream1.java 传统方式
ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));
// 遍历list1,把以“张”开头的元素添加到list2中
ArrayList<String> list2 = new ArrayList<>();
for (String s : list1) {
if (s.startsWith("张")) {
list2.add(s);
}
}
// 遍历list2,把长度为3的元素添加到list3中
ArrayList<String> list3 = new ArrayList<>();
for (String s : list2) {
if (s.length() == 3) {
list3.add(s);
}
}
// 遍历list3输出
for (String s : list3) {
System.out.println(s);
}
Stream流方式 可以一行链式调用完成:
java
// MyStream1.java Stream方式
list1.stream()
.filter(s -> s.startsWith("张"))
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
两种方式运行结果相同,但Stream流代码量大幅减少、逻辑更清晰。
Stream流的思想与特点
Stream流可以形象地看作一条“流水线”:
- 获取
Stream流:创建流水线并把数据放上去。 - 中间方法:流水线上的各种加工操作(过滤、映射、去重等),执行完可以继续调用其它方法,返回的还是流。
- 终结方法:流水线的最后一步,如遍历、计数、收集,执行后流就关闭了,不能再使用。
下面这张流程图也对应了饮料工厂的流水线比喻:
检查瓶子 → 消毒 → 灌装 → 密封 → 包装。每一步都可以保留或剔除瓶子,与Stream的中间操作逻辑一致。
如何获取Stream流
Stream 流只能在以下几种数据源上获得:
| 数据源 | 获取方式 |
|---|---|
单列集合(Collection实现类) | 集合对象.stream() |
双列集合(Map) | 不能直接获取,需通过 keySet().stream() 或 entrySet().stream() 间接获取 |
| 数组 | Arrays.stream(数组) |
| 同种数据类型的多个数据 | Stream.of(数据1, 数据2, ...) |
代码演示:
// MyStream2.java
// ========== 单列集合 ==========
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.stream().forEach(s -> System.out.println(s));
// ========== 双列集合 ==========
HashMap<String, Integer> hm = new HashMap<>();
hm.put("zhangsan", 23);
hm.put("lisi", 24);
hm.put("wangwu", 25);
hm.put("zhaoliu", 26);
hm.put("qianqi", 27);
// 方式1:通过keySet获取键的流
hm.keySet().stream().forEach(s -> System.out.println(s));
// 方式2:通过entrySet获取键值对对象的流
hm.entrySet().stream().forEach(s -> System.out.println(s));
// ========== 数组 ==========
int[] arr = {1, 2, 3, 4, 5};
Arrays.stream(arr).forEach(s -> System.out.println(s));
// ========== 同种数据类型的多个数据 ==========
Stream.of(1, 2, 3, 4, 5, 6, 7, 8).forEach(s -> System.out.println(s));
注意:双列集合需要间接获取,keySet() 得到的是所有键的 Set,entrySet() 得到的是包含键值对的Set,之后再用 .stream() 转为流
中间方法:filter过滤
filter 方法接收一个 Predicate 接口,只有一个抽象方法 boolean test(T t),用来判断数据是否留下。
- 返回
true:当前数据保留 - 返回
false:当前数据剔除
代码推导过程:
- 匿名内部类方式(最繁琐)
- 完整Lambda表达式
- 简化Lambda表达式(方法体只有一行时可省略大括号、return和分号)
// MyStream3.java
ArrayList<String> list = new ArrayList<>();
list.add("张三丰");
list.add("张无忌");
list.add("张翠山");
list.add("王二麻子");
list.add("张良");
list.add("谢广坤");
// 方式一:匿名内部类
list.stream().filter(
new Predicate<String>() {
@Override
public boolean test(String s) {
boolean result = s.startsWith("张");
return result;
}
}
).forEach(s -> System.out.println(s));
// 方式二:完整Lambda(Predicate接口只有一个抽象方法,可以用Lambda简化)
list.stream().filter(
(String s) -> {
boolean result = s.startsWith("张");
return result;
}
).forEach(s -> System.out.println(s));
// 方式三:简化Lambda(参数类型可省略,单行方法体可省略大括号和return)
list.stream().filter(s -> s.startsWith("张")).forEach(s -> System.out.println(s));
关键点:filter 会遍历流中的每个数据,s 依次代表每个元素,我们的判断逻辑返回布尔值即可。
其他常用中间方法
1 ) limit 截取
保留前n个元素,后面的丢弃。
// MyStream4.method1()
list.stream().limit(2).forEach(s -> System.out.println(s));
// 输出:张三丰、张无忌(只保留前两个)
2 ) skip 跳过
跳过前n个元素,保留后面的。
// MyStream4.method2()
list.stream().skip(2).forEach(s -> System.out.println(s));
// 输出:张翠山、王二麻子、张良、谢广坤...(跳过了前两个)
3 ) concat 合并
静态方法,将两个流合并为一个。
// MyStream4.method3()
ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山"));
ArrayList<String> list2 = new ArrayList<>(List.of("王二麻子","张良","谢广坤"));
// 写法一:分步
Stream<String> stream1 = list1.stream();
Stream<String> stream2 = list2.stream();
Stream<String> stream3 = Stream.concat(stream1, stream2);
stream3.forEach(s -> System.out.println(s));
// 写法二:链式
Stream.concat(list1.stream(), list2.stream()).forEach(s -> System.out.println(s));
4 ) distinct 去重
去除流中重复的元素,底层依赖 hashCode() 和 equals() 方法。
// MyStream4.method4()
list.add("谢广坤");
list.add("谢广坤");
list.add("谢广坤");
list.stream().distinct().forEach(s -> System.out.println(s));
// 多个谢广坤只会保留一个
终结方法:forEach与count
终结方法是流的最后一步,执行后流关闭。
1 ) forEach
遍历流中的每个元素,参数是 Consumer 接口,抽象方法 void accept(T t)。
同样支持从匿名内部类到Lambda的简化:
// MyStream5.method1()
// 方式一:匿名内部类
list.stream().forEach(
new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
}
);
// 方式二:完整Lambda
list.stream().forEach(
(String s) -> {
System.out.println(s);
}
);
// 方式三:简化Lambda
list.stream().forEach(s -> System.out.println(s));
2 ) count
返回流中元素个数,返回类型为 long。
// MyStream5.main()
long count = list.stream().count();
System.out.println(count); // 6
Stream流的重要特性:不修改数据源
Stream流 只能操作流上的数据,不会修改原始集合或数组。
// MyStream6.java
ArrayList<Integer> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
list.add(i);
}
// 流中过滤出偶数,并输出
list.stream()
.filter(number -> number % 2 == 0)
.forEach(number -> System.out.println(number)); // 输出2,4,6,8,10
// 再次遍历原集合,仍然包含1~10所有数字
for (Integer integer : list) {
System.out.println(integer); // 1,2,3,...,10
}
即使对流做了过滤、去重等操作,原集合内容不变。如果想把结果保留下来,需要使用收集方法。
收集方法:toList 和 toSet
collect 方法负责将流中的数据收集到一个新的集合中,它需要借助 Collectors 工具类来创建容器并添加数据。
Collectors.toList():收集到List集合Collectors.toSet():收集到Set集合(自动去重)
// MyStream7.java
ArrayList<Integer> list1 = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
list1.add(i);
}
list1.add(10);
list1.add(10);
list1.add(10); // 故意添加重复元素
// 收集到List(保留重复)
List<Integer> list = list1.stream()
.filter(number -> number % 2 == 0)
.collect(Collectors.toList());
System.out.println(list); // [2, 4, 6, 8, 10, 10, 10, 10, 10]
// 收集到Set(自动去重)
Set<Integer> set = list1.stream()
.filter(number -> number % 2 == 0)
.collect(Collectors.toSet());
System.out.println(set); // [2, 4, 6, 8, 10]
分工说明:
filter负责过滤数据;collect负责把流中最终的数据收集起来;Collectors.toList()/Collectors.toSet()在底层创建对应集合并添加数据。
收集方法:toMap
当需要把流中的数据收集到双列集合(Map)时,需要使用 Collectors.toMap(),并指定 键 和 值 的获取方式
// MyStream8.java
ArrayList<String> list = new ArrayList<>();
list.add("zhangsan,23");
list.add("lisi,24");
list.add("wangwu,25");
Map<String, Integer> map = list.stream().filter(
s -> {
String[] split = s.split(",");
int age = Integer.parseInt(split[1]);
return age >= 24; // 只保留年龄>=24的人
}
).collect(Collectors.toMap(
s -> s.split(",")[0], // 如何获取键(姓名)
s -> Integer.parseInt(s.split(",")[1]) // 如何获取值(年龄)
));
System.out.println(map); // {lisi=24, wangwu=25}
toMap 方法需要两个Lambda参数:
- 第一个Lambda:如何从流中的一个元素得到Map的键
- 第二个Lambda:如何从流中的一个元素得到Map的值
同样可以进行简化(单参数可省略括号,单行可省略return和大括号)。
综合练习
下面用一个综合案例巩固学过的Stream操作。
需求:分别存储6位男演员和6位女演员,按要求处理:
- 男演员只要名字为3个字的前两人;
- 女演员只要姓杨的,且排除第一个;
- 将过滤后的男女演员姓名合并;
- 把合并后的每个姓名包装成演员对象(
Actor),然后遍历输出。
Actor 类:
// Actor.java
package com.wb.streamdemo;
public class Actor {
private String name;
public Actor() {
}
public Actor(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Actor{" +
"name='" + name + '\'' +
'}';
}
}
主测试类:
// MyStream9.java
package com.wb.streamdemo;
import java.util.ArrayList;
import java.util.stream.Stream;
public class MyStream9 {
public static void main(String[] args) {
ArrayList<String> manList = new ArrayList<>();
manList.add("张国立");
manList.add("张晋");
manList.add("刘烨");
manList.add("郑伊健");
manList.add("徐峥");
manList.add("王宝强");
ArrayList<String> womanList = new ArrayList<>();
womanList.add("郑爽");
womanList.add("杨紫");
womanList.add("关晓彤");
womanList.add("张天爱");
womanList.add("杨幂");
womanList.add("赵丽颖");
// 1. 男演员名字为3个字的前两人
Stream<String> stream1 = manList.stream()
.filter(name -> name.length() == 3)
.limit(2);
// 结果:张国立、郑伊健
// 2. 女演员姓杨的,跳过第一个
Stream<String> stream2 = womanList.stream()
.filter(name -> name.startsWith("杨"))
.skip(1);
// 结果:杨幂(跳过了杨紫)
// 3. 合并流
// 4. 将每个姓名封装为Actor对象并遍历
Stream.concat(stream1, stream2)
.forEach(name -> {
Actor actor = new Actor(name);
System.out.println(actor);
});
}
}
输出结果:
Actor{name='张国立'}
Actor{name='郑伊健'}
Actor{name='杨幂'}
过程图解:
该案例综合运用了 filter、limit、skip、concat 以及 forEach 方法,充分展示了Stream流对集合数据处理的链式与声明式风格
总结
Stream 流是Java 8引入的强大数据处理工具,通过获取流 → 中间操作 → 终结操作的流水线模型,可以极大简化集合/数组的复杂处理逻辑。
| 分类 | 方法 | 作用 |
|---|---|---|
| 获取流 | stream() / Arrays.stream() / Stream.of() | 将数据源转成流 |
| 中间操作 | filter | 过滤数据 |
| 中间操作 | limit / skip | 截取前n个 / 跳过前n个 |
| 中间操作 | distinct | 去重 |
| 中间操作 | concat (静态) | 合并两个流 |
| 终结操作 | forEach | 遍历每个元素 |
| 终结操作 | count | 统计元素个数 |
| 终结操作 | collect + Collectors.toList/toSet/toMap | 将流收集为集合 |
掌握这些核心方法后,你就可以在实际项目中写出更简洁、更易读的Java代码了。
1263

被折叠的 条评论
为什么被折叠?



