Stream操作api

Stream流常用的操作api如下:

方法 返回值 说明
filter Stream 过滤数据
map Stream 返回新的数据类型
flatMap Stream 返回新的数据类型,并作扁平化处理
distinct Stream 去重
sorted Stream 排序
limit Stream 获取前几条数据
skip Stream 跳过前几条数据
forEach void 遍历元素
reduce Optional 数据汇总
collect R 收集数据
count long 数据个数统计
anyMatch boolean 数据任意一个匹配
allMatch boolean 数据全匹配
noneMatch boolean 数据全不匹配
findFirst Optional 获取第一个数据
findAny Optional 获取任意一个数据

1、forEach

forEach方法接受一个Consumer接口函数,会将每一个流元素交给函数进行处理。

1
2
3
4
5
6
7
8
9
10
11
// 例子:forEach输出元素
Stream.of(1, 2, 3, 4, 5)
.forEach(i -> {
System.out.println("forEach:" + i);
});
// 输出结果
// forEach:1
// forEach:2
// forEach:3
// forEach:4
// forEach:5

2、filter

filter方法用来筛选出我们想要的数据,方法参数是一个Predicate接口,因为Predicate是一个函数式接口,我们可以使用Lambda表达式来写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 例子1:过滤出大于3的数
Stream.of(1, 2, 3, 4, 5)
.filter(i -> {
return i > 3;
})
.forEach(i -> {
System.out.println("forEach:" + i);
});
// 输出结果:
// forEach:4
// forEach:5

// 例子2:过滤出不为空的字符串
Stream.of("a", "b", null, "", "null")
.filter(StrUtil::isNotEmpty)
.forEach(i -> {
System.out.println("forEach:" + i);
});
// 输出结果:
// forEach:a
// forEach:b
// forEach:null

3、map

map方法用于将一种数据类型转换另一种数据类型,方法参数是Function函数接口参数,该函数接口可以把数据类型T(函数接口输入)换成数据类型R(函数接口输出)

image-20240210174717656

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 例子:map将整数转换成集合
Stream.of(1, 2, 3, 4, 5)
.map(i -> {
ArrayList<Integer> list = new ArrayList<>();
list.add(i);
return list;
})
.forEach(i -> {
System.out.println("forEach:" + i);
});
// 输出结果
//forEach:[1]
//forEach:[2]
//forEach:[3]
//forEach:[4]
//forEach:[5]

4、flatMap

flatMap方法也可以将一种数据类型转换另一种数据类型,方法参数也是Function函数接口参数,该函数接口传参和map方法一样是数据类型T,但是输出是数据类型R对应的Stream流对象,因此上述的map的例子改写成flatMap的方式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 例子:flatMap将整数转化成集合
Stream.of(1, 2, 3, 4, 5)
.flatMap(i -> {
ArrayList<Integer> list = new ArrayList<>();
list.add(i);
return Stream.of(list);
})
.forEach(i -> {
System.out.println("forEach:" + i);
});

// 输出结果
//forEach:[1]
//forEach:[2]
//forEach:[3]
//forEach:[4]
//forEach:[5]

flatMapmap转换的区别点在于:

map 必须是一对一的,即每个元素都只能转换为 1 个新的元素

flatMap 可以是一对多的,即每个元素都可以转换为1个或者多个新的元素

image-20240210204737644

实际上flatMap 操作将每个元素处理返回一个新的 Stream,然后将多个 Stream 展开合并为了一个完整的新的 Stream

image-20240210205855620

正如上述所说,map是将元素一对一的转换,flatMap可以将元素一对多的转换,因此当遇到需要一转多的情况时,flatMap更适用

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
@Data
@AllArgsConstructor
class Role{
private Integer id;
private String roleName;
private List<Integer> userIdList;
}

public class Test {
public static void main(String[] args) {

Role role1 = new Role(1, "班长", Arrays.asList(1, 2));
Role role2 = new Role(2, "副班长", Arrays.asList(3, 4, 5));
Role role3 = new Role(3, "学习委员", Arrays.asList(6, 7, 8));

// 例子:flatMap获取多个角色的所有用户id
Stream.of(role1, role2, role3)
.flatMap(role -> {
return role.getUserIdList().stream();
})
.forEach(i -> {
System.out.println("forEach:" + i);
});
}
}
// 输出结果
// forEach:1
// forEach:2
// forEach:3
// forEach:4
// forEach:5
// forEach:6
// forEach:7
// forEach:8

上述例子中,role.getUserIdList().stream();不要写成Stream.of(role.getUserIdList());,因为如果是后者的话,flatMap操作元素生成的Stream流的元素是个集合,最后合并的Stream的元素也是集合

image-20240210214948361

如果是前者的话,其Stream流元素就是Integer

image-20240210215022177

5、distinct

distinct方法用于对流中的元素进行去重,主要是根据元素对应类的equals方法来判断是不是同一元素

1
2
3
4
5
6
7
8
9
10
11
Stream.of(1, 1, 2, 3, 4, 3, 5)
.distinct()
.forEach(i -> {
System.out.println("forEach:" + i);
});
// 输出结果
//forEach:1
//forEach:2
//forEach:3
//forEach:4
//forEach:5

6、sorted

sorted用于给元素进行排序,传入参数是一个比较器

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
@Data
@AllArgsConstructor
class Role{
private Integer id;
private String roleName;
private List<Integer> userIdList;
}

public class Test {
public static void main(String[] args) {

Role role1 = new Role(1, "班长", Arrays.asList(1, 2));
Role role2 = new Role(2, "副班长", Arrays.asList(3, 4, 5));
Role role3 = new Role(3, "学习委员", Arrays.asList(6, 7, 8));

Stream.of(role1, role2, role3)
// Comparator是函数式接口,使用Lambda表达式进行实现
.sorted((r1, r2)->{
return r2.getUserIdList().size() - r1.getUserIdList().size();
})
.forEach(i -> {
System.out.println("forEach:" + i);
});
}
}

// 输出结果
//forEach:Role(id=2, roleName=副班长, userIdList=[3, 4, 5])
//forEach:Role(id=3, roleName=学习委员, userIdList=[6, 7, 8])
//forEach:Role(id=1, roleName=班长, userIdList=[1, 2])

sorted也可以不传参数,此时会有个默认的比较器Comparators.NaturalOrderComparator.INSTANCE,但这时候需要排序的类实现Comparable类并实现compareTo方法,不然会报错。

我们常用的String类和Integer实际上都实现了Comparable

7、reduce

reduce方法用于数据的聚合、汇总

reduce方法的参数可以归纳为如下:

  • identity:数据聚合的初始值,会一起参与数据聚合
  • accumulator:累加器(数据的聚合方法),定义一个带两个参数的函数,第一个参数是上个归并函数的返回值,第二个是Strem 中下一个元素。
  • combiner:调用一个函数来组合聚合结果,当并行流或者累加器的函数和累加器的实现类型不匹配时才会调用此函数。
7.1 单个参数

传入单个参数时,参数是数据的聚合方法(accumulator)

1
2
3
4
5
// 获取数组的和
Optional<Integer> reduce = Stream.of(1, 2, 3, 4, 4, 5, 6, 2, 3, 5)
.reduce(Integer::sum);
System.out.println(reduce);
// 结果 Optional[35]

我们可以看到,reduce单个参数时,需要的BinaryOperator<T>对象是个函数接口,其继承了BiFunction<T,T,T>接口,因此,BinaryOperator<T>对象的实现仅需要实现BiFunction<T,T,T>接口的R apply(T t, U u)的方法即可。该方法需要传两个参数,返回一个值,因此上述例子可以写成

1
2
3
Optional<Integer> reduce = Stream.of(1, 2, 3, 4, 4, 5, 6, 2, 3, 5)
.reduce((a, b) -> a + b);
System.out.println(reduce);

当计算数组内所有数字的乘积是就可以写成

1
2
3
4
Optional<Integer> reduce = Stream.of(1, 2, 3, 4, 4, 5, 6, 2, 3, 5)
.reduce((a, b) -> a * b);
System.out.println(reduce);
// 结果 Optional[86400]
7.2 两个参数

传入两个参数时,第一个参数是数据聚合的初始值(identity),第二个参数是数据的聚合方法(accumulator)

1
2
3
4
5
// 获取数组的和
Integer reduce = Stream.of(1, 2, 3, 4, 4, 5, 6, 2, 3, 5)
.reduce(100, Integer::sum);
System.out.println(reduce);
// 结果 135
7.3 三个参数

传入三个参数时,第三个参数的组合器只有在并行流或者累加器的函数和累加器的实现类型不匹配时才会调用。因此这个方法不常用。

1
2
3
4
5
6
// 获取数组的和
Integer reduce = Stream.of(1, 2, 3, 4, 4, 5, 6, 2, 3, 5)
.parallel()
.reduce(100, Integer::sum, Integer::sum);
System.out.println(reduce);
// 结果 1035

当对一个流进行并行操作时,在运行时会把流分割多个子流来并行操作。因此需要一个函数来组合各个子流返回的结果。

上述例子中,因为是并行流,10个元素分成了10个流,而每个子流的初始值都是100,因此实际上是10个100与数组的和

8、collect

collect方法用于收集流元素,并将其转换成需要的格式,其传参有两种,不过主要是使用传Collector的方式

1
2
3
4
5
6
7
8
9
10
11
// 收集成List
List<Integer> collect = Stream.of(1, 2, 3, 3, 3)
.collect(Collectors.toList());
System.out.println(collect);
// 结果 [1, 2, 3, 3, 3]

// 收集成Set
Set<Integer> collect = Stream.of(1, 2, 3, 3, 3)
.collect(Collectors.toSet());
System.out.println(collect);
// 结果 [1, 2, 3]

collect的传参:

1
2
3
4
5
6
// 传参方式一
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
// 传参方式二
<R, A> R collect(Collector<? super T, A, R> collector);

因为Collector中同样提供了Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner这三个参数,所以基本都使用的方式二

更多方法的使用可以看看对应的文档或源码