在工作中时常会使用 Java Stream 对集合进行特殊操作,Stream 虽然能简化代码,但是书写以及阅读性不高。故在此记录常用的 Stream 案例以便在未来工作中查阅和使用(复制粘贴😅)

一、Stream 介绍

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。

使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

1.1 Stream 操作流程

  1. 创建 Stream:一个数据源(如:集合、数组),获取一个流。

  2. 中间操作:一个中间操作链,对数据源的数据进行处理。

  3. 终止操作:一个终止操作,执行中间操作链,并产生结果。

1.2 Stream 的创建

通过 Collection 集合创建

1
2
3
4
// 获取顺序流
Stream<String> stream = list.stream();
// 获取并行流
Stream<String> parallelStream = list.parallelStream();

通过 Arrays 创建

1
2
String[] arr = {"hello","world","abc"};
Stream<String> stream = Arrays.stream(arr);

通过 Stream 的静态方法创建

1
2
3
4
5
Stream<String> stream1 = Stream.of("hello","world","abc");
// 获取无限流,迭代
Stream<Integer> stream2 = Stream.iterate(0,(x) -> x + 2);
// 获取无限流,生成
Stream<Integer> stream3 = Stream.generate(() -> (int)(Math.random()));

1.3 Stream 的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,称为 “惰性求值”。

筛选与切片

方法说明
filter(Predicate p)接收 Lambda , 从流中过滤出元素
distinct()筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize)截断流,使其元素不超过给定数量。
skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test() {
List<Person> list = Arrays.asList(
new Person(1,"aaa",21),
new Person(2,"bbb",22),
new Person(3,"ccc",23),
new Person(4,"ddd",24),
new Person(5,"eee",25)
);
// 中间操作
Stream<Person> stream = list.stream()
.filter((p) -> p.getAge() > 22) // 过滤得到 id 为 3、4、5 的元素
.skip(1) // 跳过 1 个元素,得到 id 为 4、5 元素
.limit(1); // 指获取 1 个元素,得到 id 为 4 的元素

// 终止操作
stream.forEach(System.out::println);
}

映射

方法说明
map(Function f)接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f)接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f)接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
1
2
3
4
5
6
7
8
9
10
11
@Test
public void test() {
List<String> list = Arrays.asList("aaa","bbb","ccc");

// 中间操作
Stream<String> stream = list.stream()
.map((str) -> str.toUpperCase()); // 将流中所有元素进行转大写操作

// 终止操作
stream.forEach(System.out::println);
}

排序

方法说明
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator comp)产生一个新流,其中按比较器顺序排序
1
2
3
4
5
6
7
8
9
10
@Test
public void test() {
List<String> list = Arrays.asList("ccc","aaa","bbb");

// 中间操作
Stream<String> stream = list.stream().sorted(); // 自然排序

// 终止操作
stream.forEach(System.out::println);
}

1.4 Stream 的终止操作

终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

查找与匹配

方法说明
allMatch(Predicate p)检查是否匹配所有元素。
anyMatch(Predicate p)检查是否至少匹配一个元素。
noneMatch(Predicate p)检查是否没有匹配所有元素。
findFirst()返回第一个元素。
findAny()返回当前流中的任意元素。
count()返回流中元素总数。
max(Comparator c)返回流中最大值。
min(Comparator c)返回流中最小值。
forEach(Consumer c)内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void test() {
List<Person> list = Arrays.asList(
new Person(1,"aaa",21),
new Person(2,"bbb",22),
new Person(3,"ccc",23),
new Person(4,"ddd",24),
new Person(5,"eee",25)
);

boolean result = list.stream()
.allMatch((p) -> p.getAge() > 22); // 是否所有元素中年龄大于 22

System.out.println(result);

Optional<Integer> op = list.stream()
.map(Person::getAge) // 获取所有元素的年龄
.max(Integer::compare); // 获取最大年龄

System.out.println(op.get());
}

归约

方法说明
reduce(T iden, BinaryOperator b)可以将流中元素反复结合起来,得到一个值,返回 T。
reduce(BinaryOperator b)可以将流中元素反复结合起来,得到一个值,返回 Optional
1
2
3
4
5
6
7
8
@Test
public void test() {
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

Integer result = list.stream().reduce(0, (x,y) -> x + y); // 所有元素进行累加

System.out.println(result);
}

收集

方法说明
collect(Collector c)将流转换为其他形式。接收一个 Collector接口的实现,用于给 Stream 中元素做汇总的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test() {
List<Person> list = Arrays.asList(
new Person(1,"aaa",21),
new Person(2,"bbb",22),
new Person(3,"ccc",23),
new Person(4,"ddd",24),
new Person(5,"eee",25)
);

List<String> result = list.stream()
.map(Person::getName) // 获取所有元素的名字
.collect(Collectors.toList()); // 将名字从流中放到新的集合中

System.out.println(result);
}

二、案例汇总

准备测试数据

1
2
3
4
5
6
7
8
9
10
11
12
List<User> userList = new ArrayList<>();
// id: id,name: 姓名,age: 年龄
User user1 = new User(1, "张三", 26);
User user2 = new User(2, "张三", 28);
User user3 = new User(3, "李四", 24);
User user4 = new User(4, "王五", 30);
User user5 = new User(4, "王五2", 31);
userList.add(user1);
userList.add(user2);
userList.add(user3);
userList.add(user4);
userList.add(user5);

2.1 获取 id 集合

1
2
List<Integer> idList = userList.stream().map(User::getId).collect(Collectors.toList());
System.out.println("idList:" + idList);

结果:

1
idList:[1, 2, 3, 4, 4]

2.2 拼接所有用户姓名

1
2
String names = userList.stream().map(User::getName).collect(Collectors.joining(","));
System.out.println("names:" + names);

结果:

1
names:张三,张三,李四,王五,王五2

2.3 年龄递增排序

1
2
List<User> sortList = userList.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toList());
System.out.println("sortList:" + sortList);

结果:

1
sortList:[User [id=3, name=李四, age=24], User [id=1, name=张三, age=26], User [id=2, name=张三, age=28], User [id=4, name=王五, age=30], User [id=4, name=王五2, age=31]]

如要递减,编写 Comparator.comparing(User::getAge).reversed()

2.4 过滤出年龄大于 30 的用户

1
2
List<User> filterList = userList.stream().filter(i -> i.getAge() > 30).collect(Collectors.toList());
System.out.println("filterList:" + filterList);

结果:

1
filterList:[User [id=4, name=王五2, age=31]]

2.5 统计年龄小于 30 的用户数量

1
2
long count = userList.stream().filter(i -> i.getAge() < 30).count();
System.out.println("count:" + count);

结果:

1
count:3

2.6 获取年龄最大用户

1
2
User maxUser = userList.stream().max(Comparator.comparing(User::getAge)).get();
System.out.println("maxUser:" + maxUser);

结果:

1
maxUser:User [id=4, name=王五2, age=31]

2.7 获取所有用户年龄总和

1
2
Integer totalAge = userList.stream().collect(Collectors.summingInt(User::getAge));
System.out.println("totalAge:" + totalAge);

结果:

1
totalAge:139

2.8 获取所有用户年龄平均值

1
2
Double averageAge = userList.stream().collect(Collectors.averagingDouble(User::getAge));
System.out.println("averageAge:" + averageAge);

结果:

1
averageAge:27.8

2.9 包含集合总数,年龄最大值, 年龄总和, 年龄平均值

1
2
DoubleSummaryStatistics statistics = userList.stream().collect(Collectors.summarizingDouble(User::getAge));
System.out.println("count:" + statistics.getCount() + ", max:" + statistics.getMax() + ", sum:" + statistics.getSum() + ", average:" + statistics.getAverage());

结果:

1
count:5, max:31.0, sum:139.0, average:27.8

2.10 List 转成 Map

1
2
3
Map<Integer, User> userMap = userList.stream()
.collect(Collectors.toMap(User::getId, Function.identity(), (v1, v2) -> v2));
System.out.println("userMap:" + userMap);

结果:

1
userMap:{1=User [id=1, name=张三, age=26], 2=User [id=2, name=张三, age=28], 3=User [id=3, name=李四, age=24], 4=User [id=4, name=王五2, age=31]}

其中 (v1, v2) -> v2) 表示 v1,v2 相同时 取 v2

2.11 获取名字对应的的 id 集合

同样是 List 转成 Map 。

1
2
3
4
5
6
Map<String, List<Integer>> userMap = userList.stream()
.collect(Collectors.groupingBy(
User::getName,
Collectors.mapping(User::getId, Collectors.toList()))
);
System.out.println("userMap:" + userMap);

结果:

1
userMap:{李四=[3], 王五2=[4], 张三=[1, 2], 王五=[4]}