Content Table

Lambda and Stream 笔记

创建 Stream:

1
2
3
4
Stream<String>  stringStream1  = list.stream();
Stream<String> stringStream2 = Stream.of("taobao");
Stream<Integer> integerStream1 = Stream.of(1, 2, 3, 5);
Stream<Integer> integerStream1 = Stream.iterate(1, item -> item + 1).limit(10);

distict

对于 Stream 中包含的元素进行去重操作 (去重逻辑依赖元素的 equals 方法),新生成的 Stream 中没有重复的元素:

filter

对于 Stream 中包含的元素使用给定的过滤函数进行过滤操作,新生成的 Stream 只包含符合条件的元素:

map

对于 Stream 中包含的元素使用给定的转换函数进行转换操作,新生成的 Stream 只包含转换后生成的另一种类型的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong 和 mapToDouble。这三个方法也比较好理解,比如 mapToInt 就是把原始 Stream 转换成一个新的 Stream,这个新生成的 Stream 中的元素都是 int 类型,之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗:

flatMap

把 2 层的集合扁平化为 1 层的集合 (List<List> -> List),与 map 类似,不同的是其每个元素转换得到的是 Stream 对象,会把子 Stream 中的元素压缩到父集合中 (flatMap 的参数是 Stream):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
List<List<String>> list = new LinkedList<>();
list.add(Arrays.asList("One", "Two", "Three"));
list.add(Arrays.asList("Alice", "Bob", "Carry"));

// 二层的集合扁平化为一层的集合
List<String> result = list.stream().flatMap(List::stream).map(String::toUpperCase).collect(Collectors.toList());
System.out.println(result); // [ONE, TWO, THREE, ALICE, BOB, CARRY]

Arrays.asList("Huang Biao", "Hill Man").stream()
.flatMap(e -> Arrays.asList(e.split(" ")).stream())
.collect(Collectors.toList()); // [Huang, Biao, Hill, Man]

Arrays.asList("Huang Biao", "Hill Man")
.stream()
.map(line -> Arrays.asList(line.split(" ")))
.flatMap(List::stream)
.collect(Collectors.toList());
}

limit

对一个 Stream 进行截断操作,获取其前 N 个元素,如果原 Stream 中包含的元素个数小于 N,那就获取其所有的元素:

skip

返回一个丢弃原 Stream 的前 N 个元素后剩下元素组成的新 Stream,如果原 Stream 中包含的元素个数小于 N,那么返回空 Stream:

peek

生成一个包含原 Stream 的所有元素的新 Stream,同时会提供一个消费函数 (Consumer实例),新 Stream 每个元素被消费的时候都会执行给定的消费函数:

reduce

Stream 接口有一些通用的汇聚操作,比如 reduce()collect();也有一些特定用途的汇聚操作,比如 sum(), max()count()。注意:sum() 方法不是所有的 Stream 对象都有的,只有 IntStream、LongStream 和 DoubleStream 是实例才有。

1
2
3
4
5
6
7
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).collect(Collectors.toList());

List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
int sum = ints.stream().reduce((sum, item) -> sum + item).get();

int[] nums = new int[]{1, 2, 3, 4, 5, 6, 7, 8};
int min = IntStream.of(nums).min().getAsInt();

常见示例

使用 Lambda 的函数引用时:

  • 只有一条语句
  • 调用参数自己的无参函数: User::getName, List::stream
  • 参数作为其他类的静态函数的参数: System.out::println

有时使用静态导入可以使代码更清晰。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.util.*;
import java.util.stream.Collectors;
import static java.util.Comparator.comparing;
import static java.util.Collections.reverseOrder;

@Getter
@Setter
@ToString
@Accessors(chain = true)
public class User {
private int teamId;
private String username;

public User(int teamId, String username) {
this.teamId = teamId;
this.username = username;
}

public static List<User> prepareData() {
List<User> users = new LinkedList<>();
users.add(new User(1, "Alice"));
users.add(new User(1, "Bob"));
users.add(new User(1, "John"));
users.add(new User(2, "Bob"));
users.add(new User(2, "Steven"));
users.add(new User(3, "John"));
users.add(new User(3, "Loa"));

return users;
}

public static void main(String[] args) {
List<User> users = prepareData();

// 1. 过滤: 只保留 teamId 大于 2 的元素,过滤掉 teamId 小于 3 的元素
users.stream().filter(u -> u.getTeamId() > 2).forEach(System.out::println);

// 2. 去重: 获取所有不同的名字 (toCollection 的参数为 Supplier)
// 2.1 使用 Set 去重: Collectors.toSet(), Collectors.toCollection(TreeSet::new)
// 2.2 使用 distinct 去重
Set<String> names1 = users.stream().map(User::getUsername).collect(Collectors.toSet());
Set<String> names2 = users.stream().map(User::getUsername).collect(Collectors.toCollection(TreeSet::new));
List<String> names3 = users.stream().map(User::getUsername).distinct().collect(Collectors.toList());
System.out.println(names1);
System.out.println(names2);
System.out.println(names3);

// 3. 合并字符串: joining
System.out.println(users.stream().map(User::getUsername).collect(Collectors.joining(", ")));

// 4. 分组 (Map): teamId 相同的放在一组
Map<Integer, List<User>> teamUsers = users.stream().collect(Collectors.groupingBy(User::getTeamId));
System.out.println(teamUsers);

// 5. List -> Map: key 为 teamId, value 为 User,一般用在快速查找
// 如果 list 中有 2 个元素的 teamId 相同,则会报 duplicate key 的错误,
// 解决这个问题可以给 toMap 第 3 个参数指定重复的时候使用哪一个元素
Map<Integer, User> userMap = users.stream().collect(Collectors.toMap(User::getTeamId, u -> u, (ou, nu) -> nu));
System.out.println(userMap);

// 6. 排序: 先按名字升序排序,名字相同按 teamId 降序排序
// 默认为升序排序,后面一个 reversed 会取反前面的所有 comparator,
// 还可以使用 .thenComparing(Collections.reverseOrder(comparing(User::getTeamId))))
users.sort(Comparator.comparing(User::getUsername).reversed().thenComparing(User::getTeamId).reversed());
System.out.println(users);

prepareData().stream()
.sorted(Comparator.comparing(User::getUsername).reversed().thenComparing(User::getTeamId).reversed())
.forEach(System.out::println);

prepareData().stream()
.sorted(comparing(User::getUsername).thenComparing(reverseOrder(comparing(User::getTeamId))))
.forEach(System.out::println);
}
}
1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
// 1. 去重
// 2. 降序
List<Integer> list = Arrays.asList(5, 2, 2, 3, 4)
.stream().collect(Collectors.toSet())
.stream().sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println(list); // [5, 4, 3, 2]
}
1
2
int[] nums = new int[] {1, 2, 3, 4, 5, 6, 7, 8};
int min = IntStream.of(nums).min().getAsInt();

int 数组 to list:

1
2
3
4
5
6
7
int[] ints = {1,2,3};

List<Integer> list1 = IntStream.of(ints).boxed().collect(Collectors.toList());
List<Integer> list2 = Arrays.stream(ints).boxed().collect(Collectors.toList());

System.out.println(list1);
System.out.println(list2);

anyMatch

1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException {
List<Integer> ns = new LinkedList<>();
ns.add(1);
ns.add(3);
ns.add(4);
ns.add(5);
System.out.println(ns.stream().anyMatch(n -> n > 2));
}

还有 findFirst

频率统计

Word frequency count Java 8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main(String[] args) {
// 统计出现频率
List<Integer> ns = Arrays.asList(1, 2, 3, 2, 4, 3, 2);

// 频率为 Long
Map<Integer, Long> map1 = ns.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(map1);

// 频率为 Int
Map<Integer, Integer> map2 = ns.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.summingInt(e -> 1)));
System.out.println(map2);
}
}

输出:

1
2
{1=1, 2=3, 3=2, 4=1}
{1=1, 2=3, 3=2, 4=1}

统计单词次数

等价于 Scala 里的 mapValues:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
List<String> list = Arrays.asList("Hello", "Hello", "World");
Map<String, Long> map1 = list.stream().collect(Collectors.groupingBy(e->e, Collectors.counting()));
System.out.println(map1); // {Hello=2, World=1}

List<User> users = Arrays.asList(new User(1, "Biao"), new User(2, "Biao"), new User(3, "Alice"));
Map<String, Long> map2 = users.stream().collect(Collectors.groupingBy(User::getUsername, Collectors.counting()));
System.out.println(map2); // {Alice=1, Biao=2}
}

分组 (只取某个属性)

1
2
3
4
5
Map<Long, List<Integer>> optionIdWithStarValuesMap = starOptionAnswers.stream().collect(
Collectors.groupingBy(
QuestionOptionAnswer::getQuestionOptionId,
Collectors.mapping(QuestionOptionAnswer::getValue, Collectors.toList())
));