目录
- 基础数据
- 元素转Stream
- Terminal opt-Collectors.mapping
- Terminal opt-Collectors.toCollection&collectingAndThen
- Terminal opt-Collectors.toMap
近日频繁应用 Stream 的 Api,记录一下应用实例。
基础数据
实体类:
@Data
@Accessors(chain = true)
public static class Person {
private String name;
private Integer age;
private String hobby;
private LocalDateTime birthday;
}
数据:
public List<Person> data() {
List<Person> data = new ArrayList<>();
data.add(new Person().setName("张三").setAge(25).setHobby("LOL,Food").setBirthday(LocalDateTime.of(1997, 1, 1, 0, 0)));
data.add(new Person().setName("李四").setAge(25).setHobby("CS:GO,Swimming").setBirthday(LocalDateTime.of(1997, 2, 1, 0, 0)));
data.add(new Person().setName("王五").setAge(30).setHobby("RedAlert2,Beer").setBirthday(LocalDateTime.of(1992, 3, 1, 0, 0)));
data.add(new Person().setName("赵六").setAge(40).setHobby("War3,Journey").setBirthday(LocalDateTime.of(1982, 4, 1, 0, 0)));
data.add(new Person().setName("孙七").setAge(40).setHobby("DOTA,Jogging").setBirthday(LocalDateTime.of(1982, 5, 1, 0, 0)));
return data;
}
元素转Stream
当我们需要将一个单值元素转转为一个多值元素,并进行统一收集,flatMap在适合不过。
flatMap 函数:将单个元素映射为Stream。
参数:
Function mapper:元素映射为 Stream 的过程。
栗子:
/**
* 获取所有成员的爱好集合
*/
@Test
public void test1() {
// 方式1:
// 先进行 map 映射单个元素为多值元素
// 在将多值元素映射为 Stream
Set<String> hobbySet = data()
.stream()
.map(p -> p.getHobby().split(","))
.flatMap(Stream::of)
.collect(Collectors.toSet());
System.out.println(hobbySet);
// 方式2:直接将单个元素映射为 Stream
hobbySet = data()
.stream()
.flatMap(p -> Stream.of(p.getHobby().split(",")))
.collect(Collectors.toSet());
System.out.println(hobbySet);
}
输出:
[War3, CS:GO, LOL, DOTA, Swimming, RedAlert2, Journey, Food, Beer, Jogging]
[War3, CS:GO, LOL, DOTA, Swimming, RedAlert2, Journey, Food, Beer, Jogging]
Terminal opt-Collectors.mapping
mapping 函数:在聚合元素时,对元素进行映射转换。若处理元素的中间操作阶段进行了map,那么此时属于二次map。
参数:
-
Function mapper:元素的映射过程。
-
Collector downstream:对于映射后的元素的采集过程。
栗子:
Stream.of("1", "2", "3").map(Integer::parseInt).collect(Collectors.toList());
Stream.of("1", "2", "3").collect(Collectors.mapping(Integer::parseInt, Collectors.toList()));
这两行代码效果是一样的,不过更推荐前者。
那么既然可以通过map实现同样效果,为什么不直接使用map的方式呢?因为在实际应用中,编写一些复杂处理:处理分组后的下游数据,Collectors.mapping更适用。
栗子:
/**
* 根据年龄分组,并统计每组下的成员姓名
*/
@Test
public void test2() {
Map<Integer, Set<String>> ageNameMapping = data()
.stream()
.collect(
Collectors.groupingBy(
Person::getAge,
Collectors.mapping(Person::getName, Collectors.toSet())
)
);
System.out.println(ageNameMapping);
}
输出:
{40=[孙七, 赵六], 25=[李四, 张三], 30=[王五]}
Terminal opt-Collectors.toCollection&collectingAndThen
开发时,经常会遇到根据指定字段进行分组的情况。有时需要对分组时产生的下游数据进行其他操作,个人最经常操作的就是排序。
toCollection函数: 对聚合元素使用的容器进行定制化。
参数:
Supplier collectionFactory:定义装载元素的容器。
collectingAndThen函数: 聚合元素完毕后,对返回容器进行二次处理。
参数:
-
Collector downstream:聚合元素的过程。
-
Function finisher:对downstream参数返回的容器的二次处理过程。处理后,需要将容器返回。
栗子:
/**
* 根据年龄分组,每组根据生日进行排序
*/
@Test
public void test3() {
// 通过定制特定数据结构的容器实现排序:正序
Map<Integer, Collection<Person>> ageMapping = data()
.stream()
.collect(
Collectors.groupingBy(
Person::getAge,
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getBirthday)))
)
);
printMap(ageMapping);
// 通过定制特定数据结构的容器实现排序:逆序
ageMapping = data()
.stream()
.collect(
Collectors.groupingBy(
Person::getAge,
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getBirthday).reversed()))
)
);
printMap(ageMapping);
// 通过对聚合元素后返回的容器二次处理,实现排序
ageMapping = data()
.stream()
.collect(
Collectors.groupingBy(
Person::getAge,
Collectors.collectingAndThen(
Collectors.toList(),
l -> {
l.sort(Comparator.comparing(Person::getBirthday).reversed());
return l;
}
)
)
);
printMap(ageMapping);
}
public void printMap(Map<Integer, Collection<Person>> mapping) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
mapping.forEach((key, val) -> System.out.println(key + " : " + val.stream().map(p -> p.getName() + " -> " + dateTimeFormatter.format(p.getBirthday())).collect(Collectors.joining(" / "))));
System.out.println();
}
输出:
40 : 赵六 -> 1982-04-01 00:00:00 / 孙七 -> 1982-05-01 00:00:00
25 : 张三 -> 1997-01-01 00:00:00 / 李四 -> 1997-02-01 00:00:00
30 : 王五 -> 1992-03-01 00:00:00
40 : 孙七 -> 1982-05-01 00:00:00 / 赵六 -> 1982-04-01 00:00:00
25 : 李四 -> 1997-02-01 00:00:00 / 张三 -> 1997-01-01 00:00:00
30 : 王五 -> 1992-03-01 00:00:00
40 : 孙七 -> 1982-05-01 00:00:00 / 赵六 -> 1982-04-01 00:00:00
25 : 李四 -> 1997-02-01 00:00:00 / 张三 -> 1997-01-01 00:00:00
30 : 王五 -> 1992-03-01 00:00:00
Terminal opt-Collectors.toMap
有时我们分组后,可以确定每组的值是一个单值,而不是多值。这种情况下就可以使用toMap,避免取值时的不便。
toMap函数:将聚合的元素组装为一个Map返回。
参数:
-
Function keyMapper:分组时使用的 Key
-
Function valueMapper:将 Key 匹配元素的什么值作为 Key 的 Value
-
BinaryOperator mergeFunction:若发生单 Key 匹配到多个元素时的合并过程(只能返回一个元素作为 Key 的 Value)
-
Supplier mapSupplier:指定装载元素的 Map 类型容器
栗子:
/**
* 根据年龄分组,只保留生日最大的成员
*/
@Test
public void test4() {
// 下面注释代码会抛出异常,因为没有指定当单Key匹配到多值时的 merge 行为
// 源码中的默认指定的 meger 行为则是抛出异常:throwingMerger()
// Map<Integer, Person> toMap = data().stream().collect(Collectors.toMap(Person::getAge, Function.identity()));
Map<Integer, Person> toMap = data()
.stream()
.collect(
Collectors.toMap(
Person::getAge,
Function.identity(),
(v1, v2) -> Comparator.comparing(Person::getBirthday).compare(v1, v2) > 0 ? v1 : v2
)
);
toMap.forEach((key, val) -> System.out.println(key + " : " + val.getName() + " -> " + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(val.getBirthday())));
}
输出:
40 : 孙七 -> 1982-05-01 00:00:00
25 : 李四 -> 1997-02-01 00:00:00
30 : 王五 -> 1992-03-01 00:00:00