Java 函数式编程 - Stream API Java 8 API 添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式处理数据。 Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Java 函数式编程 - Stream API
Stream 介绍
Java 8 API 添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。
Stream 的特性
- Stream 不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
- Stream 不会改变数据源,通常情况下会产生一个新的集合或一个值。
- Stream 具有延迟执行特性,只有调用终止操作时,中间操作才会执行。
Stream 操作分类
中间操作:惰性求值,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算
终止操作:执行计算操作,返回计算结果,每个流只能执行一次终止操作
无状态:指元素的处理不受之前元素的影响
有状态:指该操作只有拿到所有元素之后才能继续下去
非短路操作:指必须处理所有元素才能得到最终结果
短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要 A 为 true,则无需判断 B 的结果
中间操作 | 无状态 |
|
有状态 |
| |
终止操作 | 非短路操作 |
|
短路操作 |
|
串行流与并行流
Stream API 可以声明性地通过 parallel()
和 sequential()
在并行流和串行流(顺序流)之间进行切换。
串行流:通过单线程的方式来处理数据的流
并行流:把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流,最后把不同线程的处理结果进行汇总,是基于 Fork/Join 框架实现的
并行流使用 ForkJoinPool.commonPool 线程池,默认的线程数量就是你的处理器核心数。多个并行流之间默认使用的是同一个线程池,所以 IO 操作尽量不要放进并行流中,否则会阻塞其他并行流。
在单次计算量较少的情况下并行操作并没有比串行操作快,因为对数据进行分片和汇总等操作也需要消耗时间。
流的创建方式
- 使用 Collection 下的
stream()
和parallelStream()
方法
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();
- 使用 Arrays 的
stream()
方法,将数组转成流
Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);
- 使用 Stream 的静态方法
of()
、iterate()
、generate()
、concat()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
// 无限长的偶数流
Stream<Integer> stream2 = Stream.iterate(0, x -> x + 2);
stream2.forEach(System.out::println); // 0 2 4 6 8 10 ...
// 无限长的随机数浮点数流
Stream<Double> stream3 = Stream.generate(Math::random);
stream3.forEach(System.out::println);
// 连接两个流
Stream.concat(stream, stream2);
- 使用
BufferedReader.lines()
方法,将每行内容转成流
BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
- 使用 Pattern 的
splitAsStream()
方法,将字符串分割成流
Pattern pattern = Pattern.compile(",");
Stream<String> stream = pattern.splitAsStream("a,b,c");
流的中间操作
筛选与切片
filter
:过滤掉流中的某些元素
// 过滤掉流中的奇数
Stream.of(1, 2, 3, 4)
.filter(i -> i % 2 == 0)
.forEach(System.out::println);
// 输出:2 4
limit(n)
:只获取前 n 个元素
// 只获取流的中前2个元素
Stream.of(1, 2, 3, 4)
.limit(2)
.forEach(System.out::println);
// 输出:1 2
skip(n)
:跳过 n 个元素,配合 limit(n)
可实现分页
// 跳过流中的前2个元素
Stream.of(1, 2, 3, 4)
.skip(2)
.forEach(System.out::println);
// 输出:3 4
distinct
:去除流中的重复元素,通过元素的 equals()
和 hashCode()
方法来去重
Stream.of(1, 2, 3, 4, 3 ,2)
.distinct()
.forEach(System.out::println);
// 输出:1 2 3 4
映射
map
:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素
// 将流中的每个整数乘以2
Stream.of(1, 2, 3, 4)
.map(i -> i * 2)
.forEach(System.out::println);
// 输出:2 4 6 8
// 将流中的每个整数转换成浮点数
Stream.of(1, 2, 3, 4)
.map(i -> (double) i)
.forEach(System.out::println);
// 输出:1.0 2.0 3.0 4.0
flatMap
:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把这些流连接成一个流
// 将两个字符串按照逗号分割,并汇总分割后的元素
Stream.of("1,2,3", "a,b,c")
.flatMap(str -> Arrays.stream(str.split(",")))
.forEach(System.out::println);
// 输出:1 2 3 a b c
排序
sorted()
:自然排序(从小到大),流中元素需实现 Comparable 接口
// 从小到大排序
Stream.of(4, 2, 1, 3)
.sorted()
.forEach(System.out::println);
// 输出:1 2 3 4
sorted(Comparator)
:自定义排序,传入自定义的 Comparator 排序器
// 从大到小排序
Stream.of(4, 2, 1, 3)
.sorted(Comparator.reverseOrder())
.forEach(System.out::println);
// 输出:4 3 2 1
消费
peek
:观察流中的每个元素
Stream.of(1, 2, 3, 4)
.peek(i -> System.out.println(i))
.forEach(System.out::println);
// 输出:1 1 2 2 3 3 4 4
流的终止操作
消费
forEach
:对于串行流是顺序遍历流的元素;对于并发流,则是随机遍历forEachOrdered
:对于串行流,跟 forEach
一样;对于并发流,则是按照顺序遍历流的元素
匹配、聚合操作
allMatch
:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回 true,否则返回 false
// 流中元素是否都是偶数
boolean result = Stream.of(0, 2, 4, 8)
.allMatch(i -> i % 2 == 0);
System.out.println(result);
// 输出:true
noneMatch
:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回 true,否则返回 false
// 流中元素是否都大于0
boolean result = Stream.of(1, 2, 3, 4)
.noneMatch(i -> i <= 0);
System.out.println(result);
// 输出:true
anyMatch
:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回 true,否则返回 false
// 流中是否有任意一个元素小于0
boolean result = Stream.of(1, 2, -1, 3)
.anyMatch(i -> i < 0);
System.out.println(result);
// 输出:true
findFirst
:返回流中第一个元素
Optional<Integer> first = Stream.of(1, 2, 3, 4).findFirst();
System.out.println(first.get());
// 输出:1
findAny
:返回流中的任意元素,对于串行流一般会返回第一个结果,对于并行流,那就不能确保是第一个。找到第一个元素在并行流上限制更多。如果你不关心返回的元素是哪个,请使用 findAny
,因为它在使用并行流时限制较少;
Optional<Integer> first = Stream.of(1, 2, 3, 4).findAny();
System.out.println(first.get());
// 输出:1
count
:返回流中元素的总个数
long count = Stream.of(1, 2, 3, 4).count();
System.out.println(count);
// 输出:4
max
:返回流中元素最大值
Optional<Integer> max = Stream.of(1, 2, 3, 4)
.max(Comparator.naturalOrder());
System.out.println(max.get());
// 输出:4
min
:返回流中元素最小值
Optional<Integer> min = Stream.of(1, 2, 3, 4)
.min(Comparator.naturalOrder());
System.out.println(min.get());
// 输出:1
归约操作
reduce
方法一
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
参数
-
identity
初始值,如果流为空,则为此默认结果 -
accumulator
累加器,具有两个参数的函数,归约 reduce 运算后的部分结果和流的下一个元素 -
combiner
组合器,用于并行流合并 fork join 结果,串行流不起作用
注意:reduce
方法对于串行流和并行流的逻辑是不一样的
示例:
// 串行流:将流中元素相加
Integer result = Stream.of(1, 2, 3, 4)
.reduce(100, (acc, i) -> {
System.out.printf("acc: %d + %d = %d%n", acc, i, acc + i);
return acc + i;
}, (sum, part) -> {
System.out.printf("com: %d + %d = %d%n", sum, part, sum + part);
return sum + part;
});
System.out.println(result);
// 输出:
// acc: 100 + 1 = 101
// acc: 101 + 2 = 103
// acc: 103 + 3 = 106
// acc: 106 + 4 = 110
// 110
// 并行流:将流中的每个元素都和identity相加,然后再把它们的结果加起来
Integer result = Stream.of(1, 2, 3, 4).parallel()
.reduce(100, (acc, i) -> {
System.out.printf("acc: %d + %d = %d%n", acc, i, acc + i);
return acc + i;
}, (sum, part) -> {
System.out.printf("com: %d + %d = %d%n", sum, part, sum + part);
return sum + part;
});
System.out.println(result);
// 输出:
// acc: 100 + 3 = 103
// acc: 100 + 2 = 102
// acc: 100 + 4 = 104
// acc: 100 + 1 = 101
// com: 103 + 104 = 207
// com: 101 + 102 = 203
// com: 203 + 207 = 410
// 410
方法二
T reduce(T identity, BinaryOperator<T> accumulator);
示例:
// 将流中元素相加
Integer result = Stream.of(1, 2, 3, 4)
.reduce(100, (acc, i) -> acc + i);
System.out.println(result);
// 输出:110
方法三
Optional<T> reduce(BinaryOperator<T> accumulator);
示例:
// 将流中元素相加
Optional<Integer> result = Stream.of(1, 2, 3, 4)
.reduce((acc, i) -> acc + i);
System.out.println(result.get());
// 输出:10
收集操作
toArray
将流转换为数组
方法一
<A> A[] toArray(IntFunction<A[]> generator);
参数
-
generator
生成所需类型和长度的新数组的函数
示例:
Integer[] array = Stream.of(1, 2, 3, 4)
.toArray(Integer[]::new);
System.out.println(Arrays.toString(array));
// 输出:[1, 2, 3, 4]
方法二
Object[] toArray();
示例:
Object[] array = Stream.of(1, 2, 3, 4)
.toArray();
System.out.println(Arrays.toString(array));
// 输出:[1, 2, 3, 4]
collect
方法一
<R, A> R collect(Collector<? super T, A, R> collector);
参数
-
collector
收集器
// 将流中元素收集到list中
List<Integer> list = Stream.of(1, 2, 3, 4)
.collect(Collectors.toList());
System.out.println(list);
// 输出 [1, 2, 3, 4]
方法二
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
参数
-
supplier
创建容器的函数 -
accumulator
累加器,用于将元素添加到容器中的函数 -
combiner
组合器,用于并行流合并 fork join 结果,串行流不起作用
// 将流中元素收集到list中
ArrayList<Integer> result = Stream.of(1, 2, 3, 4)
.collect(
() -> new ArrayList<>(),
(list, item) -> list.add(item),
(list1, list2) -> list1.addAll(list2));
System.out.println(result);
// 输出 [1, 2, 3, 4]
Collector 工具库:Collectors
以下示例中使用的数据:
class User {
private Integer id;
private String username;
private Boolean sex;
private Integer type;
...
}
List<User> users = Arrays.asList(
new User(1, "user1", true, 1),
new User(2, "user2", false, 3),
new User(3, "user3", true, 2),
new User(4, "user2", false, 1));
归集(toList/toSet/toMap)
Collectors.toList()
List<Integer> list = users.stream().map(User::getId).collect(Collectors.toList());
System.out.println(list);
// 输出:[1, 2, 3, 4]
Collectors.toSet()
Set<String> set = users.stream().map(User::getUsername).collect(Collectors.toSet());
System.out.println(set);
// 输出:[user1, user2, user3]
Collectors.toMap()
方法一
<T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier);
参数
-
keyMapper
把元素转成 key 的函数 -
valueMapper
把元素转成 value 的函数 -
mergeFunction
当 key 发生冲突时的合并函数 -
mapSupplier
创建 Map 容器的函数
示例:
// 创建用户ID到用户的映射,发生key冲突时取新的那个
HashMap<Integer, User> map = users.stream().collect(Collectors.toMap(
User::getId,
Function.identity(),
(a, b) -> b,
() -> new HashMap<>()));
System.out.println(map);
// 输出:{user1=User{id=1, ...}, user2=User{id=4, ...}, user3=User{id=3, ...}}
方法二
方法二调用了方法一
<T, K, U> Collector<T, ?, Map<K,U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
// 创建HashMap对象作为容器
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
示例:
// 创建用户名到用户的映射,发生key冲突时取新的那个
Map<String, User> map = users.stream().collect(Collectors.toMap(
User::getUsername, Function.identity(), (a, b) -> b));
System.out.println(map);
// 输出:{user1=User{id=1, ...}, user2=User{id=4, ...}, user3=User{id=3, ...}}
方法三
方法三调用了方法一
<T, K, U> Collector<T, ?, Map<K,U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
// 发现重复元素时抛异常
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
示例:
// 创建用户ID到用户的映射
Map<Integer, User> map = users.stream()
.distinct()
.collect(Collectors.toMap(User::getId, Function.identity()));
System.out.println(map);
// 输出:{1=User{id=1, ...}, 2=User{id=2, ...}, 3=User{id=3, ...}, 4=User{id=4, ...}}
统计(count/averaging)
Collectors 提供了一系列用于数据统计的静态方法:
计数:count
平均值:averagingInt
、averagingLong
、averagingDouble
最值:maxBy
、minBy
求和:summingInt
、summingLong
、summingDouble
统计以上所有:summarizingInt
、summarizingLong
、summarizingDouble
分组(groupingBy/partitioningBy)
Collectors.groupingBy()
方法一
<T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(
Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream);
参数
-
classifier
分组器,把元素转换成分组 key 的函数 -
mapFactory
创建 Map 容器的函数 -
downstream
分组后结果流的收集器
示例:
// 把用户列表按照类型分组,再按照性别进行分组
HashMap<Integer, Map<Boolean, List<User>>> map = users.stream()
.collect(Collectors.groupingBy(
User::getType,
() -> new HashMap<>(),
Collectors.groupingBy(User::getSex)));
printJson(map);
输出:
{
"1" : {
"false" : [ {"id" : 4, "username" : "user2", "sex" : false, "type" : 1} ],
"true" : [ {"id" : 1, "username" : "user1", "sex" : true, "type" : 1} ]
},
"2" : {
"true" : [ {"id" : 3, "username" : "user3", "sex" : true, "type" : 2} ]
},
"3" : {
"false" : [ {"id" : 2, "username" : "user2", "sex" : false, "type" : 3} ]
}
}
方法二
方法二调用了方法一
<T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(
Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
// 创建HashMap对象作为容器
return groupingBy(classifier, HashMap::new, downstream);
}
示例:
// 把用户列表按照类型分组,然后再按照性别分组
Map<Integer, Map<Boolean, List<User>>> map = users.stream()
.collect(Collectors.groupingBy(User::getType,
Collectors.groupingBy(User::getSex)));
printJson(map);
输出:与方法一的示例一样
方法三
方法三调用了方法二
<T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(
Function<? super T, ? extends K> classifier) {
// 把分组后的元素存到List中
return groupingBy(classifier, toList());
}
示例:
// 把用户列表按照类型分组
Map<Integer, List<User>> map = users.stream()
.collect(Collectors.groupingBy(User::getType));
System.out.println(map);
输出:
{
"1" : [ {"id" : 1, ..., "type" : 1}, {"id" : 4, ..., "type" : 1} ],
"2" : [ {"id" : 3, ..., "type" : 2} ],
"3" : [ {"id" : 2, ..., "type" : 3} ]
}
Collectors.partitioningBy()
partitioningBy
方法相当于 groupingBy
的特例,只能把元素分为两个组 true
和 false
方法一
<T, D, A> Collector<T, ?, Map<Boolean, D>> partitioningBy(
Predicate<? super T> predicate,
Collector<? super T, A, D> downstream);
参数
-
predicate
分区器,把元素转换成 true/false 的函数 -
downstream
分区后结果流的收集器
示例:
// 将用户列表按照性别分成两个区,然后再按照类型是否大于1分成两个区
Map<Boolean, Map<Boolean, List<User>>> map = users.stream()
.collect(Collectors.partitioningBy(User::getSex,
Collectors.partitioningBy(user -> user.getType() > 1)));
printJson(map);
输出:
{
"false" : {
"false" : [ {"id" : 4, "username" : "user2", "sex" : false, "type" : 1} ],
"true" : [ {"id" : 2, "username" : "user2", "sex" : false, "type" : 3} ]
},
"true" : {
"false" : [ {"id" : 1, "username" : "user1", "sex" : true, "type" : 1} ],
"true" : [ {"id" : 3, "username" : "user3", "sex" : true, "type" : 2} ]
}
}
方法二
方法二调用了方法一
<T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(
Predicate<? super T> predicate) {
// 把分组后的元素存到List中
return partitioningBy(predicate, toList());
}
示例:
// 将用户列表按照性别分成两个区
Map<Boolean, List<User>> map = users.stream()
.collect(Collectors.partitioningBy(User::getSex));
printJson(map);
输出:
{
"false" : [ {"id" : 2, "sex" : false, ...}, {"id" : 4, "sex" : false, ...} ],
"true" : [ {"id" : 1, "sex" : true, ...}, {"id" : 3, "sex" : true, ...} ]
}
字符串拼接(joining)
Collectors.joining()
方法一
Collector<CharSequence, ?, String> joining(
CharSequence delimiter,
CharSequence prefix,
CharSequence suffix);
参数
-
delimiter
分隔符 -
prefix
前缀 -
suffix
后缀
示例:
String s = users.stream().map(User::getUsername)
.collect(Collectors.joining(",", "(", ")"));
System.out.println(s);
// 输出:(user1,user2,user3,user2)
方法二
方法二调用了方法一
Collector<CharSequence, ?, String> joining(
CharSequence delimiter) {
// 前缀和后缀为空
return joining(delimiter, "", "");
}
示例:
String s = users.stream().map(User::getUsername)
.collect(Collectors.joining(","));
System.out.println(s);
// 输出:user1,user2,user3,user2
方法三
Collector<CharSequence, ?, String> joining()
示例:
String s = users.stream().map(User::getUsername)
.collect(Collectors.joining());
System.out.println(s);
// 输出:user1user2user3user2
应用例子
例子一
将用户列表转换成 VO 列表
List<User> users = Arrays.asList(
new User(1, "user1", true, 0),
new User(2, "user2", false, 1),
new User(3, "user3", true, 2),
new User(4, "user4", true, 2)
);
{
// 传统方式
List<UserVO> userVOList = new ArrayList<>(users.size());
for (User user : users) {
userVOList.add(toUserVo(user));
}
System.out.println(userVOList);
}
{
// Stream方式
List<UserVO> userVOList = users.stream()
.map(user -> toUserVo(user))
.collect(Collectors.toList());
System.out.println(userVOList);
}
例子二
将 User 列表转换成 id -> username 的映射
List<User> users = Arrays.asList(
new User(1, "user1", true, 0),
new User(2, "user2", true, 0),
new User(3, "user3", true, 0),
new User(4, "user4", true, 0)
);
{
// 传统方式
Map<Integer, String> map = new HashMap<>();
for (User user : users) {
map.put(user.getId(), user.getUsername());
}
System.out.println(map);
}
{
// Stream方式
Map<Integer, String> map = users.stream().collect(Collectors.toMap(
User::getId, User::getUsername));
System.out.println(map);
}
例子三
将用户列表的 username 按照在列表中的顺序用逗号拼接起来,并去重
List<User> users = Arrays.asList(
new User(2, "user2", true, 0),
new User(1, "user1", false, 1),
new User(3, "user3", true, 2),
new User(1, "user1", true, 2)
);
{
// 传统方式
StringBuilder usernames = new StringBuilder();
Set<String> set = new HashSet<>();
for (User user : users) {
if (set.add(user.getUsername())) {
usernames.append(user.getUsername()).append(',');
}
}
if (usernames.length() != 0) {
usernames.deleteCharAt(usernames.length() - 1);
}
System.out.println(usernames);
}
{
// Stream方式
String usernames = users.stream()
.map(User::getUsername)
.distinct()
.collect(Collectors.joining(","));
System.out.println(usernames);
}
参考
- Java 8 Stream | 菜鸟教程 (runoob.com)
- findFirst 和 findAny · java 学习笔记 · 看云 (kancloud.cn)
- Java 函数式编程中归约 reduce()的使用教程 (jdon.com)
- JDK8 并行流 parallelStream - 简书 (jianshu.com)