使用 Stream
处理集合数据【Java 1.8 新特性】
Stream
是Java 8中引入的一个重要概念,它提供了对集合对象进行一系列操作的新方式,包括筛选、转换、聚合等。Stream API以声明式方式提供了对数据集合的高效操作,并且可以并行处理数据。
首先构建一个类,下面举例用得着
java
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
private String name; // 姓名
private Integer age; // 年龄
private Double score;// 积分
}
然后构建一个混乱的集合
java
ArrayList<Customer> customers = new ArrayList(){{
add(new Customer("user01",10,7000.0));
add(new Customer("user02",10,2000.0));
add(new Customer("user03",20,3000.0));
add(new Customer("user04",20,4000.0));
add(new Customer("user05",30,5000.0));
add(new Customer("user06",30,6000.0));
add(new Customer("user07",40,1000.0));
}};
中间操作
[!NOTE]
带有⭐️ ⭐️⭐️ 的方法在平常开发中使用频率高
带有⭐️ ⭐️的方法在平常开发中使用频率中等
带有⭐️ 的方法在平常开发中使用频率低
不带⭐️ 的方法在平常开发中几乎不会用到
filter 过滤 ⭐️⭐️⭐️
java
// 筛选出年龄大于20,积分大于1000 的用户并返回
List<Customer> collect = customers.stream()
.filter(c -> c.getAge() > 20 && c.getScore() > 1000 )
.collect(Collectors.toList());
对于 filter
,需掌握3个知识点
-
filter
方法可以多次调用,比如先按年龄过滤,再按积分过滤javaList<Customer> collect = customers.stream() .filter(c -> c.getAge() > 20) .filter(c -> c.getScore() > 1000 ) .collect(Collectors.toList());
[!CAUTION]
filter
的多次调用中,当前是在上一次过滤的基础上再过滤,举个例子:如果一条数据满足条件b但不满足条件a,而过滤顺序是条件a在前条件b在后,那么这条数据在条件a处就被过滤掉了,不会轮到条件b处理。
map 映射 ⭐️ ⭐️⭐️
将流中的每个元素映射到另一个元素,这么说可能有点抽象,通俗来说就是操作集合中的对象。
java
// 将每个用户的积分+1
customers.stream().map(customer -> {
customer.setScore(customer.getScore()+1);
return customer;
}).collect(Collectors.toList());
结果如下
0 = {Customer@706} "Customer(name=user01, age=10, score=7001.0)"
1 = {Customer@707} "Customer(name=user02, age=10, score=2001.0)"
2 = {Customer@708} "Customer(name=user03, age=20, score=3001.0)"
3 = {Customer@709} "Customer(name=user06, age=20, score=4001.0)"
4 = {Customer@710} "Customer(name=user06, age=30, score=5001.0)"
5 = {Customer@711} "Customer(name=user06, age=30, score=6001.0)"
6 = {Customer@712} "Customer(name=user06, age=40, score=1001.0)"
对于 map
,需掌握3个知识点
map
方法可以多次调用,比如先处理年龄+1,再处理积分+1map
的lambda表达式有返回值,需要return,至于是否需要使用一个新集合作为变量接收结果,一般我不建议用,因为"新集合"是浅拷贝,修改了原数据,"新集合"中的数据也会跟着变- 结尾不用
.collect(Collectors.toList())
,操作是无效的~
flatMap 将流中的每个元素替换为目标元素的流
flatMap
用于将流中的每个元素(这些元素通常是集合或数组)转换成流,然后将这些流连接起来形成一个单一的流。如下代码
java
// 如果customer的年龄大于20,就将当前的customer对象复制2份,否则复制3份
List<Customer> collect = customers.stream().flatMap(customer -> {
return customer.getAge() > 20?
IntStream.range(0, 2).mapToObj(
i -> new Customer(customer.getName(), customer.getAge(), customer.getScore()))
: IntStream.range(0, 3).mapToObj(
i -> new Customer(customer.getName(), customer.getAge(), customer.getScore()));
}).collect(Collectors.toList());
对于 flatMap
,需掌握3个知识点
flatMap
方法可以多次调用- lambda表达式返回的对象必须是一个
Stream<U>
类型 - 结尾不用
.collect(Collectors.toList())
,操作是无效的~
[!TIP]
平时开发几乎用不到
flatMap
方法,处理List< List<Obj> >
型的数据用到过。
limit 截取 ⭐️ ⭐️
它用于截取流中的前 n
个元素,返回一个由原始流的前 n
个元素组成的新流。如果流中的元素少于 n
个,则返回包含所有元素的流
java
// 截取customers前两个元素
List<Customer> collect = customers.stream().limit(2).collect(Collectors.toList());
结果如下
0 = {Customer@707} "Customer(name=user01, age=10, score=7000.0)"
1 = {Customer@708} "Customer(name=user02, age=10, score=2000.0)"
对于 limit
,需掌握3个知识点
limit
方法可以多次调用(一般不会连着调多次)limit
是浅拷贝,修改了原数据,"新集合"中的数据也会跟着变- 结尾不用
.collect(Collectors.toList())
,操作是无效的~
sorted 排序 ⭐️ ⭐️⭐️
java
/**
* 顺序排序(两种方案均可)
* */
List<Customer> collect = customers.stream()
.sorted(Comparator.comparingInt(Customer::getAge))
.collect(Collectors.toList());
/**
* 倒序排序,加 reversed 即可
* */
List<Customer> collect = customers.stream()
.sorted(Comparator.comparingInt(Customer::getAge).reversed())
.collect(Collectors.toList());
对于 sorted
,需掌握3个知识点
-
sorted
是浅拷贝,修改了原数据,"新集合"中的数据也会跟着变 -
结尾不用
.collect(Collectors.toList())
,操作是无效的~ -
sorted
方法可以多次调用,比如先按年龄排,再按积分排javaList<Customer> collect = customers.stream() .sorted(Comparator.comparingInt(Customer::getAge)) .sorted(Comparator.comparingDouble(Customer::getScore).reversed()) .collect(Collectors.toList());
[!CAUTION]
但是哈,这种排序会扰乱上一步排序的结果,比如上述代码,其实我希望是在年龄排序的基础上,再排序积分,结果如下,可以看出年龄排序已经被打乱了~,所以一般不会用到多次调用
sorted
0 = {Customer@734} "Customer(name=, age=10, score=7000.0)"
1 = {Customer@739} "Customer( age=30, score=6000.0)"
2 = {Customer@738} "Customer( age=30, score=5000.0)"
3 = {Customer@737} "Customer( age=20, score=4000.0)"
4 = {Customer@736} "Customer( age=20, score=3000.0)"
5 = {Customer@735} "Customer( age=10, score=2000.0)"
6 = {Customer@740} "Customer( age=40, score=1000.0)"
既然如此,那么如何达到要求呢?当然是从
Comparator
上下手了,在第一个排序条件后跟.thenComparing...
,如下javaList<Customer> collect = customers.stream() .sorted( Comparator.comparingInt(Customer::getAge) .thenComparingDouble(Customer::getScore) ).collect(Collectors.toList());
排序结果如下,达到要求了~
0 = {Customer@737} "Customer( age=10, score=2000.0)"
1 = {Customer@738} "Customer( age=10, score=7000.0)"
2 = {Customer@739} "Customer( age=20, score=3000.0)"
3 = {Customer@740} "Customer( age=20, score=4000.0)"
4 = {Customer@741} "Customer( age=30, score=5000.0)"
5 = {Customer@742} "Customer( age=30, score=6000.0)"
6 = {Customer@743} "Customer( age=40, score=1000.0)"
[!NOTE]
- thenComparing支持多种数据类型排序:int,long,double
- Comparator 也支持链式编程
mapToDouble / mapToInt / mapToLong 提取数字 ⭐️⭐️
用于将流中的每个元素映射到一个 double
值。这个操作通常用于将流中的元素转换成数值型数据,以便进行数值计算。这些 方法接受一个函数作为参数,这个函数会被应用到流中的每个元素上,并将每个元素转换成一个 数字 值。这个方法返回一个 DoubleStream
/ LongStream
/ IntStream
,可以被进一步用于数值操作,如求和、平均值计算等。
java
// 把customers中的各元素的积分值提取出来装入一个list
List<Double> collect = customers.stream().mapToDouble(Customer::getScore).boxed().collect(Collectors.toList());
// 计算积分总和
double totalScore = customers.stream().mapToDouble(Customer::getScore).sum();
// 获取最大积分
double maxScore = customers.stream().mapToDouble(Customer::getScore).max();
// 获取最小积分
double minScore = customers.stream().mapToDouble(Customer::getScore).min();
// 计算平均积分
double minScore = customers.stream().mapToDouble(Customer::getScore).average();
方法还有很多,感兴趣可以去探索,这里就不一一例举了~
对于 mapToDouble
/ mapToInt
/ mapToLong
,需掌握1个知识点
mapToDouble
/mapToInt
/mapToLong
可以链式编程,但是一般开发不这么做
终止操作
forEach:遍历流中元素⭐️⭐️⭐️
java
customers.stream().forEach(
customer -> {
Double score = customer.getScore();
customer.setScore(score + 1);
}
);
对于 forEach
,需掌握2个知识点
forEach
中如果修改数据,影响原集合forEach
方法仅能一次调用
collect:将流转换成其他形式(如集合)⭐️⭐️⭐️
需掌握 Collectors
工具类,它提供了多种收集方式
- Collectors.toList():将流收集到一个新的
List
中 ⭐️⭐️⭐️ - Collectors.toSet():将流收集到一个新的
Set
中,这会去除重复元素 ⭐️⭐️⭐️ - Collectors.toCollection():将流收集到给定的
Collection
中 - Collectors.joining():将流中的元素连接成一个字符串 ⭐️
- Collectors.toMap():将流元素收集到一个
Map
中 ⭐️⭐️⭐️ - Collectors.groupingBy():根据某个属性对流元素进行分组,结果收集到一个
Map
中⭐️ - Collectors.reducing():通过某个连接动作(如加法)将所有元素汇总成一个汇总结果⭐️
- Collectors.collectingAndThen():在另一个收集器的结果上执行映射操作
- 除了使用
Collectors
提供的静态方法,开发者还可以自定义收集器(我没这么玩过,感兴趣的可以研究一下)
对于 collect
,需掌握1个知识点
collect
方法仅能一次调用
reduce:通过某个连接动作将所有元素汇总成一个汇总结果 ⭐️⭐️
它用于通过某个连接动作将所有元素汇总成一个汇总结果。reduce
方法可以用于多种类型的归约操作,比如求和、求最大值、求最小值等。【做大数据项目时用的最多】
java
// 计算customers中各元素积分总和
double reduce = customers.stream().mapToDouble(i -> i.getScore()).reduce(0, (a, b) -> a + b);
// 计算customers中积分最大值
double reduce = customers.stream().mapToDouble(i -> i.getScore()).reduce(0, (a, b) -> a > b ? a:b);
// 计算customers中积分最小值
double reduce = customers.stream().mapToDouble(i -> i.getScore()).reduce(0, (a, b) -> a < b ? a:b);
对于 reduce
,需掌握1个知识点
reduce
方法仅能一次调用
allMatch 、anyMatch 、noneMatch:检查流中的元素是否与给定的条件匹配
java
// 是否所有customers中元素的积分数值都大于10
boolean b = customers.stream().allMatch(i -> i.getScore() > 10);
// customers中是否存在有的元素的积分数值大于10
boolean b = customers.stream().anyMatch(i -> i.getScore() > 10);
// customers中所有的元素的积分数值都不大于10
boolean b = customers.stream().noneMatch(i -> i.getScore() > 10);
对于 allMatch
、anyMatch
、noneMatch
,需掌握1个知识点
allMatch
、anyMatch
、noneMatch
方法仅能一次调用
count:返回流中元素的数量 ⭐️
java
// 统计customers中年龄大于20的数据量
long count = customers.stream().filter( i -> i.getAge() > 20 ).count();
对于 count
,需掌握1个知识点
1. `count` 方法仅能一次调用
findFirst、findAny:返回流中的第一个或任意一个元素 ⭐️
java
Customer any = customers.stream().findAny().get();
Customer first = customers.stream().findFirst().get();
对于 findFirst
、findAny
,需掌握3个知识点
findFirst
、findAny
方法仅能一次调用Optional
类的orElse
方法允许你提供一个默认值,如果Optional
为空,则返回这个默认值- orElseGet :类似于
orElse
,但接受一个Supplier
函数,当Optional
为空时,会调用这个函数来生成默认值
先记录到这里,后续有总结会持续补上