java Stream API 用法分析与示例
前言
Java8 以来推出的 Stream API,让 流式编程 广泛应用了起来。这在某些场景下极大的简化了我们的代码,本章节对 Stream 的 API 做一个简单的梳理介绍,并给出具体的 demo 实例,旨在理解其运用
版本
JDK11
创建
创建一个 Stream 的方法有很多,简单列举几种
Collection#stream()
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
Collection 接口提供的 default方法,允许 Collection 转换为 Stream,也是我们最常用的方法
演示 demo
@Test
public void test1() {
new ArrayList<Integer>() {
{
add(1);
add(2);
}
}.stream();
}
Stream.of
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
Stream 接口提供的 静态方法,接受 T数组 参数转换为 Stream 返回
BaseStream 下提供了形如 IntStream DoubleStream 等类,分别维护对应 数据类型 的 Stream,其下也会提供各自特色的 API
比如 IntStream.range(int startInclusive, int endExclusive)
把指定(左开右开)区间内的 Integer 封装成 Stream,IntStream.rangeClosed(int startInclusive, int endInclusive)
把指定(左开右闭)区间内的 Integer 封装成 Stream
演示 demo
@Test
public void test1() {
IntStream.of(1, 2, 3).forEach(System.out::println);
Stream.of("1", "2").forEach(System.out::println);
IntStream.range(1, 5).forEach(System.out::println);
IntStream.rangeClosed(1, 5).forEach(System.out::println);
}
Stream.generate
public static<T> Stream<T> generate(Supplier<? extends T> s) {
Objects.requireNonNull(s);
return StreamSupport.stream(
new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
}
接受一个 Supplier,生成对应的 Stream,但是注意:生成的是个 无限流,需要自行处理,具体见 demo
关于 Supplier 及相关的 函数式接口,可以阅读下面文章
演示 demo
@Test
public void test2() {
// 无限流,必须以 limit 等方式限制长度
Stream.generate(new Random()::nextInt).limit(3).forEach(System.out::println);
IntStream.generate(new Random()::nextInt).limit(2).forEach(System.out::println);
}
注意对 无限流 的限制处理
Stream.iterate
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
Objects.requireNonNull(f);
Spliterator<T> spliterator = new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE,
Spliterator.ORDERED | Spliterator.IMMUTABLE) {
T prev;
boolean started;
@Override
public boolean tryAdvance(Consumer<? super T> action) {
Objects.requireNonNull(action);
T t;
if (started)
t = f.apply(prev);
else {
t = seed;
started = true;
}
// 将上一轮的 t 赋值给 prev
action.accept(prev = t);
return true;
}
};
return StreamSupport.stream(spliterator, false);
}
指定一个 seed
执行 f(seed)
,在以 f(seed)
的结果 t
,执行 f(t) 即 f(f(seed))
,返回对应的 无限流
还提供了一个 重载 的方法
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next) {
Objects.requireNonNull(next);
Objects.requireNonNull(hasNext);
Spliterator<T> spliterator = new Spliterators.AbstractSpliterator<>(Long.MAX_VALUE,
Spliterator.ORDERED | Spliterator.IMMUTABLE) {
T prev;
boolean started, finished;
@Override
public boolean tryAdvance(Consumer<? super T> action) {
Objects.requireNonNull(action);
if (finished)
return false;
T t;
if (started)
t = next.apply(prev);
else {
t = seed;
started = true;
}
// 当上一轮的结果 t 不满足 hasNext 时,停止
if (!hasNext.test(t)) {
prev = null;
finished = true;
return false;
}
action.accept(prev = t);
return true;
}
// 略
};
return StreamSupport.stream(spliterator, false);
}
该方法提供一个 Predicate<? super T> hasNext
参数,用于判断 next(t)
的结果是否有效。换句话说,提供了一个可限制 无限流 的条件
演示 demo
@Test
public void test3() {
// 无限流,必须以 limit 等方式限制长度
Stream.iterate("1", i -> i + i).limit(3).forEach(System.out::println);
// 添加了打断无限流的条件,因此可以不 limit
Stream.iterate("2", i -> i.length() < 3, i -> i + i).forEach(System.out::println);
}
结果:
1
11
1111
2
22 // 长度大于 3 时停止
过滤
提供了大量比如 filter skip limit distinct
等方法,直接看 demo 体会
@Test
public void test4() {
Stream.of(1, 2, 2, 3, 3, 4)
// 条件过滤
.filter(i -> i > 1)
// 去重
.distinct()
// 限制个数
.limit(2)
// 跳过
.skip(1)
.forEach(System.out::println);
}
结果:3
map 和 flatMap
它们都是对 Stream 元素的转换,前者将 目标对象 转换成 其他类型 的对象,而后者是将 目标对象 转换成 其他类型对象对应的 Stream
map 方法
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
转换显然由 Function<? super T, ? extends R> mapper
实现
flatMap 方法
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
转换成了对应的 Stream
示例 demo
@Test
public void test6() {
// map 后的本质是个 Stream<String[]>
Stream.of("aa", "bb", "cc")
.map(i -> i.split(""))
.forEach(System.out::println);
System.out.println("=================");
// flatMap 后的本质是个 Stream<String>
Stream.of("aa", "bb", "cc")
.flatMap(i -> Stream.of(i.split("")))
.forEach(System.out::println);
}
结果:
[Ljava.lang.String;@24a35978
[Ljava.lang.String;@16f7c8c1
[Ljava.lang.String;@2f0a87b3
=================
a
a
b
b
c
c
排序
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
对元素进行排序,如果目标类不是一个 Comparable,可以传入自定义的 Comparator
示例 demo
@Test
public void test7() {
class User {
String id;
public User(String id) {
this.id = id;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
'}';
}
}
Stream.of(new User("1"), new User("2"))
.sorted((a, b) -> Integer.parseInt(a.id) - Integer.parseInt(b.id))
.forEach(System.out::println);
}
结果:
User{id='1'}
User{id='2'}
终止
Stream 是 懒加载 的,也就是说,这之前的所有操作可以认为是 预定义,并没有被真正执行,直到调用 终止 方法,才会真正执行计算。该类方法有 allMatch anyMatch noneMatch findFirst findAny count max min toArray forEach forEachOrdered
等
我们结合具体示例 demo 说明
@Test
public void test8() {
// 是否全部匹配
System.out.println(Stream.of("a", "b", "c").allMatch(i -> i instanceof String)); // true
// 至少匹配一个
System.out.println(Stream.of("a", "b", "c").anyMatch(i -> "a".equals(i))); // true
// 全不匹配
System.out.println(Stream.of("a", "b", "c").noneMatch(i -> "d".equals(i))); // true
System.out.println("========================================");
// 获取第一个,返回的是个 Optional
System.out.println(Stream.of("a", "b", "c").findFirst().get()); // a
// 任意获取一个,返回的是个 Optional
System.out.println(Stream.of("a", "b", "c").findAny().get());
System.out.println("========================================");
// 计数
System.out.println(Stream.of("a", "b", "c").count()); // 3
System.out.println("========================================");
// 最大值,允许传入自定义的 Comparator
System.out.println(Stream.of(1, 2, 3).max(Integer::compareTo).get()); // 2
// 最小值,允许传入自定义的 Comparator
System.out.println(Stream.of(1, 2, 3).min(Integer::compareTo).get()); // 1
System.out.println("========================================");
// Stream 转换为 Array
for (Integer s : Stream.of(1, 2, 3).toArray(Integer[]::new)) {
System.out.print(s);
}
System.out.println();
System.out.println("========================================");
// unordered 无序遍历输出
Stream.of(1, 2, 3, 4, 5, 6).unordered().parallel().forEach(System.out::print);
// forEachOrdered 和 collect 都能保证有序输出
Stream.of(1, 2, 3, 4, 5, 6).unordered().parallel().forEachOrdered(System.out::print);
System.out.println();
Stream.of(1, 2, 3, 4, 5, 6).unordered().parallel().collect(Collectors.toList()).forEach(System.out::print);
}
reduce
Optional<T> reduce(BinaryOperator<T> accumulator);
T reduce(T identity, BinaryOperator<T> accumulator);
允许设置一个初始值 identity
,作为参数与对应元素执行 accumulator.apply
方法
示例 demo
@Test
public void test9() {
// 1 + 2 + 3 = 6
System.out.println(Stream.of(1, 2, 3).reduce((a, b) -> a + b).get());
// 6 + 1 + 2 + 3 = 12
System.out.println(Stream.of(1, 2, 3).reduce(6, (a, b) -> a + b));
}
Collectors
Collectors 提供了大量的方法,比如 转换 成对应的数据结构,比如 toList toSet toMap toCollection toConcurrentMap
等,同时也提供了 统计 相关方法比如 countig summingInt averagingInt summarizingInt
等方法,还有 分组 groupingBy
、分区 partitioningBy
、映射 mapping
等方法
转换
其中 toList toSet toCollection
方法的使用比较简单
Stream.of(1, 2, 3).collect(Collectors.toList()).forEach(System.out::println);
Stream.of(1, 2, 3).collect(Collectors.toSet()).forEach(System.out::println);
Stream.of(1, 2, 3).collect(Collectors.toCollection(ArrayList::new)).forEach(System.out::println);
但 toMap
涉及到 KV 键值对的转换,因此 toMap
有如下三个重载方法
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return new CollectorImpl<>(HashMap::new,
uniqKeysMapAccumulator(keyMapper, valueMapper),
uniqKeysMapMerger(),
CH_ID);
}
// 对于重复的 key,会抛出 duplicateKeyException 异常
private static <K, V, M extends Map<K,V>>
BinaryOperator<M> uniqKeysMapMerger() {
return (m1, m2) -> {
for (Map.Entry<K,V> e : m2.entrySet()) {
K k = e.getKey();
V v = Objects.requireNonNull(e.getValue());
V u = m1.putIfAbsent(k, v);
if (u != null) throw duplicateKeyException(k, u, v);
}
return m1;
};
}
// 允许自定义重复 key 的处理策略
public static <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);
}
// 指定转换 Map 类型,比如 LinkedHashMap 等
public static <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> mapFactory) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapFactory, accumulator, mapMerger(mergeFunction), CH_ID);
}
分别支持我们自定义 KV 映射策略,重复key 的解决策略和具体的 Map类型
示例 demo
@Test
public void test10() {
Stream.of(1, 2, 3)
.collect(Collectors.toMap(k -> k, v -> v))
.forEach((k, v) -> System.out.println(k +","+ v));
// 重复的 key 拼接对应的 value
Stream.of(1, 1, 3)
.collect(Collectors.toMap(String::valueOf, String::valueOf, (oldV, newV) -> oldV +""+ newV))
.forEach((k, v) -> System.out.println(k +","+ v));
// 指定 LinkedHashMap
Stream.of(1, 1, 3)
.collect(Collectors.toMap(String::valueOf, String::valueOf, (oldV, newV) -> oldV +""+ newV, LinkedHashMap::new))
.forEach((k, v) -> System.out.println(k +","+ v));
}
统计
统计相关直接看示例 demo
@Test
public void test10() {
// 计数
System.out.println(Stream.of(1, 2, 3).collect(Collectors.counting()));
// 求和
System.out.println(Stream.of(1, 2, 3).collect(Collectors.summingInt(Integer::intValue)));
// 求平均数
System.out.println(Stream.of(1, 2, 3).collect(Collectors.averagingInt(Integer::intValue)));
// IntSummaryStatistics 封装了各种统计数据
IntSummaryStatistics collect = Stream.of(1, 2, 3).collect(Collectors.summarizingInt(Integer::intValue));
System.out.println(collect.getAverage());
System.out.println(collect.getMax());
// 指定拼接符号、前缀、后缀
System.out.println(Stream.of("a", "b", "c")
.collect(Collectors.joining(",", ">>", "<<")));
// 获取最大值,以指定 Collector 排序,返回的是个 Optional
System.out.println(Stream.of(1, 2, 3).collect(Collectors.maxBy(Integer::compareTo)).get());
// 获取最小值,以指定 Collector 排序,返回的是个 Optional
System.out.println(Stream.of(1, 2, 3).collect(Collectors.minBy(Integer::compareTo)).get());
}
分组、分区、映射
这几个方法可以且经常组合使用,完成一些复杂的逻辑
分组 groupingBy
// 默认连接 toList
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
// 默认以 HashMap 存储分组数据
public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}
// 可以指定 downstream 和 mapFactory
public static <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
指定 分组属性,mapFactory
即承载分组数据的 Map类型,downstream
可以理解为链接一个 Collector 操作,比如 toList partitioningBy counting
等等
分区 partitioningBy
// 默认连接 toList
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}
public static <T, D, A>
Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
Collector<? super T, A, D> downstream) {
// 略
}
提供两个重载方法,以 predicate
分为 true false
两组,同样可以指定一个 downstream
映射 mapping
public static <T, U, A, R>
Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
Collector<? super U, A, R> downstream) {
BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
return new CollectorImpl<>(downstream.supplier(),
(r, t) -> downstreamAccumulator.accept(r, mapper.apply(t)),
downstream.combiner(), downstream.finisher(),
downstream.characteristics());
}
指定 mapper
定义映射逻辑,同时必须指定一个 downstream
示例 demo
@Test
public void test11() {
class Book {
int id;
int type;
public Book(int id, int type) {
this.id = id;
this.type = type;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", type=" + type +
'}';
}
}
List<Book> list = new ArrayList<>() {
{
add(new Book(1, 1));
add(new Book(2, 2));
add(new Book(3, 2));
}
};
// 以 type 分组
/**
* 结果
* 1:[Book{id=1, type=1}]
* 2:[Book{id=2, type=2}, Book{id=3, type=2}]
*/
list.stream()
.collect(Collectors.groupingBy(b -> b.type))
.forEach((k, v) -> System.out.println(k +":"+ v));
// 分组后连接一个 counting 用于计数
/**
* 结果
* 1:1
* 2:2
*/
list.stream()
.collect(Collectors.groupingBy(b -> b.type, Collectors.counting()))
.forEach((k, v) -> System.out.println(k +":"+ v));
System.out.println("=================================");
// 分区 type == 1 (true)和 type != 1 (false)
/**
* 结果
* false:[Book{id=2, type=2}, Book{id=3, type=2}]
* true:[Book{id=1, type=1}]
*/
list.stream()
.collect(Collectors.partitioningBy(i -> i.type == 1))
.forEach((k, v) -> System.out.println(k +":"+ v));
// 分区后连接 groupingBy,以 id 分组
/**
* 结果
* false:{2=[Book{id=2, type=2}], 3=[Book{id=3, type=2}]}
* true:{1=[Book{id=1, type=1}]}
*/
list.stream()
.collect(Collectors.partitioningBy(i -> i.type == 1
, Collectors.groupingBy(i -> i.id)))
.forEach((k, v) -> System.out.println(k +":"+ v));
// 分区 -> 映射 -> toList
/**
* 结果
* false:[Book{id=10, type=10}, Book{id=10, type=10}]
* true:[Book{id=10, type=10}]
*/
list.stream()
.collect(Collectors.partitioningBy(i -> i.type == 1
, Collectors.mapping(i -> new Book(10, 10)
, Collectors.toList())))
.forEach((k, v) -> System.out.println(k +":"+ v));
}
连接
提供了用于连接两个 Stream 的静态方法 concat
示例 demo
@Test
public void test12() {
System.out.println(Stream.concat(Stream.of(1, 2), Stream.of(3, 4))
.reduce(5, (a, b) -> a + b));
}
// 5 + 1 + 2 + 3 + 4 = 15
总结
可以看到,Stream API 相对来说还是十分庞大的,本文也只是介绍了部分方法的使用而已。在平时的开发工作中,Stream API 的使用也十分的频繁,因此掌握常用的方法时十分有必要的
参考
若想要了解更多更详细的 Stream API 相关知识,可以参考下文
【小家java】java8新特性之—Stream API 详解 (Map-reduce、Collectors收集器、并行流、groupby多字段分组)