Java 8 中的 Stream
是对集合(Collection
)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。 Stream API
借助于同样新出现的 Lambda
表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 java7 引入的 fork/join
并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API
无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。
在深入了解流之前,我们先了解 Stream
中几个常用的函数式接口:
函数名 | 入参类型 | 返回类型 | 接口描述 |
---|---|---|---|
Predicate | T | boolean | 判断入参是否符合要求,用于流元素的匹配和过滤 |
Function | T | R | 将T对象转换成R对象返回,用于流元素类型转换 |
Comparator | (T, T) | int | 比较传入的两个对象,用于流元素排序,求极值 |
Consumer | T | void | 消费流元素,用于流元素的聚合和遍历操作 |
Supplier | None | T | 生产流元素,根据提供的Supplier生成无限流 |
BinaryOperator | (T, T) | T | 传入两个同类型对象返回一个同类型对象,用于reduce操作 |
UnaryOperator | T | T | 传入一个对象返回一个同类型对象,用于iterate操作 |
1. 如何创建 Stream
所有流的基类是 BaseStream
,主要子类有 Stream
(最常用的流),IntStream
(Int型流),DoubleStream
(Double型流),LongStream
(Long型流),创建流有很多种方式,总结如下:
1.1 collection 生成流方法
常用的 Collection
子类有 List
、Set
、Queue
(单向队列)、Deque
(双向队列),Collection
类有两个生成流的方法:
1)生成串行流方法:stream()
default Stream stream() {
return StreamSupport.stream(spliterator(), false);
}
2)生成并行流方法:parallelStream()
default Stream parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
Collection
的子类可以直接调用这两个方法来生成适合的流,一般如果是基本类型的流类型,建议直接使用串行流来进行数据操作。
示例:
// 获取字符串集合的串行流
List strList = new ArrayList<>();
Stream stream = strList.stream();
// 获取实体类集合的并行流
List persons = new ArrayList<>();
Stream stream2 = persons.parallelStream();
Stream stream = strList.stream().parallel();
1.2 Array 生成流方法
使用 Arrays
的静态方法生成流:
String [] strArray = {"aa", "bb"};
Stream stream = Arrays.stream(strArray);
1.3 使用 Stream 类的方法
Stream
类可以直接使用原生方法来生成各种流,也可以通过构建器来进行构建,用法如下:
1)of()
方法:
通过传入一个实体或者实体数组来生成对应的流:
// 传入一个实体
public static Stream of(T t) {
return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
// 传入一个实体数组
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static Stream of(T... values) {
return Arrays.stream(values);
}
示例:
List strs = new ArrayList(){
{
add("aaa");
add("bbb");
add("ccc");
}
};
Stream> strs1 = Stream.of(strs);
// 注意上边返回的并不是Stream类型的流,下边的构造方式更为常用
Stream aaa = Stream.of("aaa", "bbb", "ccc");
2)通过 generate()
和 iterate()
生成无限流
generate()
方法通过通过一个 supplier
来生成无限流,supplier
定义一个生成规则,比方说生成随机数:Math.random()
,或者直接返回一个字符串:() -> "aa"
,由于生成的流是无限流,所以经常跟limit()
方法合用,返回前n个数据,示例:
// 生成随机数流,并返回前100条数据
Stream generate1 = Stream.generate(() -> Math.random()).limit(100);
//生成包含100个元素是“aaa”的流
Stream generate = Stream.generate(() -> "aaa").limit(100);
3)使用builder()
构建器生成流
Stream
类含有一个builder()
,该方法返回一个构造器对象,该对象是 Stream
类的一个内部类,结构如下:
/**
* A mutable builder for a {@code Stream}. This allows the creation of a
* {@code Stream} by generating elements individually and adding them to the
* {@code Builder} (without the copying overhead that comes from using
* an {@code ArrayList} as a temporary buffer.)
*
* A stream builder has a lifecycle, which starts in a building
* phase, during which elements can be added, and then transitions to a built
* phase, after which elements may not be added. The built phase begins
* when the {@link #build()} method is called, which creates an ordered
* {@code Stream} whose elements are the elements that were added to the stream
* builder, in the order they were added.
*
* @param the type of stream elements
* @see Stream#builder()
* @since 1.8
*/
public interface Builder extends Consumer {
/**
* Adds an element to the stream being built.
*
* @throws IllegalStateException if the builder has already transitioned to
* the built state
*/
@Override
void accept(T t);
/**
* Adds an element to the stream being built.
*
* @implSpec
* The default implementation behaves as if:
* {@code
* accept(t)
* return this;
* }
*
* @param t the element to add
* @return {@code this} builder
* @throws IllegalStateException if the builder has already transitioned to
* the built state
*/
default Builder add(T t) {
accept(t);
return this;
}
/**
* Builds the stream, transitioning this builder to the built state.
* An {@code IllegalStateException} is thrown if there are further attempts
* to operate on the builder after it has entered the built state.
*
* @return the built stream
* @throws IllegalStateException if the builder has already transitioned to
* the built state
*/
Stream build();
}
使用示例:
// 使用 add() 方法链式添加流元素
Stream
4)IntStream
和LongStream
的range()
方法
// 返回范围为 1-10 的整数流,含头不含尾
IntStream range = IntStream.range(1, 10);
// 返回范围为 1-10 的整数流,含头且含尾
IntStream range2 = IntStream.rangeClosed(1, 10);
2. Stream 的基本使用
对于流的操作基本可以分为两类,一类是中间操作,一类是终止操作。中间操作输出的还是流对象,例如过滤排序映射转换操作,中间操作是惰性操作的,并不会立即执行,当终止操作出发后才会执行之前的所有中间操作,类似于sprak
处理数据流;终止操作执行后流不可再使用,输出一个对象或对元素进行一系列操作,例如归约聚合遍历操作,下边详细介绍各个方法。
2.1 中间操作
中间操作执行后,输出的是一个新流,可以在新流的基础上再次执行中间操作,或者使用终止操作来结束流。
筛选切片
方法 | 入参类型 | 描述 |
---|---|---|
filter | Predicate | 根据过滤条件筛选符合要求的数据流 |
distinct | None | 去重操作 |
limit | int | 根据输入值,返回前 n 条数据 |
skip | int | 根据输入值,忽略前 n 条数据 |
示例:
Stream.of("aa", "bbb", "cc", "abb", "ac", "bcc", "abc", "aa")
.distinct() // 输出:{"aa", "bbb", "cc", "abb", "ac", "bcc", "abc",}
.filter(str -> str.length() > 2) // 输出:{"bbb", "abb", "bcc", "abc"}
.limit(3) // 输出:{"bbb", "abb", "bcc"}
.skip(2) // 输出:{"bcc"}
.collect(Collectors.toList());
映射转换
方法 | 入参类型 | 描述 |
---|---|---|
map | Function | 作用于流中的每个元素,按照函数接口将 A 对象转换成 B 对象 |
mapToInt | ToIntFunction | 作用于流中的每个元素,按照函数接口返回 IntStream |
mapToLong | ToLongFunction | 作用于流中的每个元素,按照函数接口返回 LongStream |
mapToDouble | ToDoubleFunction | 作用于流中的每个元素,按照函数接口返回 DoubleStream |
flatMap | Function | 作用于流中的每个元素,将每个元素转换成另一个流,然后合并所有流返回 |
flatMapToInt | Function | 作用于流中的每个元素,将每个元素转换成一个 IntStream 流,然后合并所有流返回 |
flatMapToLong | Function | 作用于流中的每个元素,将每个元素转换成一个 LongStream 流,然后合并所有流返回 |
flatMapToDouble | Function | 作用于流中的每个元素,将每个元素转换成一个 DoubleStream 流,然后合并所有流返回 |
对于 flatMap 的理解:
flatMap 可以理解为,将原有流元素中的元素再次流化,然后将生成的所有流再次合并成一个新流,经常用于第一次流化时,流元素并不是我们想要的基本类型元素,比方说第一次流化后的流元素是数组或者集合类型,我们可以使用 flatMap 来扁平化元素类型。
示例:
Stream stream = Stream.of(new String [] {"1", "2", "3"}, new String [] {"2", "4", "6"}) // Stream 类型
//示例1
List list = stream.flatMap(Arrays::stream) // Stream 类型
.map(Integer::valueOf) // Stream 类型
.collect(Collectors.toList()); // List 类型,输出:[1, 2, 3, 2, 4, 6]
// 示例2
stream.flatMap(Arrays::stream) // Stream 类型
.mapToInt(Integer::parseInt) // IntStream 类型
.max() // OptionalInt 类型,获取最大值
.getAsInt(); // int 类型
// 示例3
Stream.of("1,2,3,4", "5,6,7,8")
.flatMap(e -> Arrays.stream(e.split(","))) // 将 ["1,2,3,4", "5,6,7,8"] -> ["1", "2", "3", "4", "5", "6", "7", "8"]
.map(Integer::valueOf)
.collect(Collectors.toList()); // List 类型,输出:[1, 2, 3, 4, 5, 6, 7, 8]
排序
方法 | 入参类型 | 描述 |
---|---|---|
sorted | None | 按照流元素默认比较器进行排序,返回新流 |
sorted | Comparator | 按照传入比较器对流元素排序,返回新流 |
示例:
// 示例 1
Stream.of(new String [] {"1", "2", "3"}, new String [] {"2", "4", "6"}) // Stream 类型
.flatMap(Arrays::stream) // Stream 类型
.map(Integer::valueOf) // Stream 类型
.sorted() // Stream 类型
.collect(Collectors.toList()); // List 类型,输出:[1, 2, 2, 3, 4, 6]
// 示例 2,自定义比较器
Stream.of(new String[]{"1", "2", "3"}, new String[]{"2", "4", "6"})
.flatMap(Arrays::stream) // Stream 类型
.map(Integer::valueOf) // Stream 类型
.sorted((a, b) -> a < b ? 1 : a == b ? 0 :-1) // Stream 类型
.collect(Collectors.toList()); // List 类型,输出:[6, 4, 3, 2, 2, 1]
// 示例 3,传入 Comparator,Comparator内置了常见的比较器
Stream.of(new String[]{"1", "2", "3"}, new String[]{"2", "4", "6"})
.flatMap(Arrays::stream) // Stream 类型
.map(Integer::valueOf) // Stream 类型
.sorted(Comparator.reverseOrder()) // Stream 类型
.collect(Collectors.toList()); // List 类型,输出:[6, 4, 3, 2, 2, 1]
其他
方法 | 入参类型 | 描述 |
---|---|---|
peek | Consumer | 作用于每个元素,对每个元素进行操作,然后返回原流,常用于 debug 打印日志 |
empty | None | 返回一个空的流对象 |
示例:
// 示例 1
Stream.of("aa", "bbb", "cc", "abb", "ac", "bcc", "abc", "aa")
.filter(str -> str.length() > 2)
.peek(e -> System.out.println("first filter: " + e))
.distinct()
.peek(e -> System.out.println("second filter: " + e))
.limit(3)
.peek(e -> System.out.println("third filter: " + e))
.skip(2)
.peek(e -> System.out.println("fourth filter: " + e))
.collect(Collectors.toList());
// 输出:
first filter: bbb
second filter: bbb
third filter: bbb
first filter: abb
second filter: abb
third filter: abb
first filter: bcc
second filter: bcc
third filter: bcc
fourth filter: bcc
// 示例 2
Stream
2.2 终止操作
终止操作执行后,流结束,只有当终止操作触发,中间操作才会开始执行,也叫懒惰执行,常见的终止操作有三类:查询匹配、归约、遍历和聚合,这里涉及到一个新类 Optional,详细细节之后我会详细分析。
查询匹配
方法 | 入参类型 | 返回类型 | 描述 |
---|---|---|---|
min | Comparator | Optional |
根据比较器,返回流中最小的元素,可看作是一种特殊的 reduce 操作 |
min | Comparator | Optional |
根据比较器,返回流中最大的元素,可看作是一种特殊的 reduce 操作 |
count | None | long | 统计流中元素总数,可看作是一种特殊的 reduce 操作 |
anyMatch | Predicate | boolean | 流中有任何元素符合条件返回true,否则返回false |
allMatch | Predicate | boolean | 流中所有元素符合条件返回true,否则返回false |
noneMatch | Predicate | boolean | 流中没有元素符合条件返回true,否则返回false |
findFirst | None | Optional |
返回流中第一个元素 |
findAny | None | Optional |
返回流中任意一个元素 |
示例:
Stream s = Stream.of("aa", "bbb", "cc", "abb", "ac", "bcc", "abc", "aa");
// 根据字符串长度求最小值
String min = s.min(Comparator.comparingInt(String::length)).get();
// 根据字符串长度求最大值
String min = s.max(Comparator.comparingInt(String::length)).get();
// 统计总数
long min = s.count();
// 若有任何元素包含 a 则返回 true
boolean result1 = s..anyMatch(e -> e.contains("a"));
// 若所有元素包含 a 则返回 true
boolean result1 = s.allMatch(e -> e.contains("a"));
// 若没有有元素包含 a 则返回 true
boolean result1 = s.noneMatch(e -> e.contains("a"));
// 返回第一个元素
String first = s.findFirst().get();
// 返回任意元素
String any = s.findAny().get();
归约
方法 | 入参类型 | 返回类型 | 描述 |
---|---|---|---|
reduce | (T, BinaryOperator |
T | 指定一个初始值,根据 BinaryOperator 对初始值及流元素执行归约操作 |
reduce | BinaryOperator |
Optional |
根据 BinaryOperator 对流元素执行归约操作 |
reduce | (U, BiFunction,BinaryOperator) | U | 指定一个初始值,根据 BiFunction 将 T类型的流数据元素转换成 U 类型,并根据根据 BinaryOperator 对新的流元素执行归约操作 |
示例:
// 示例 1
Stream.of(new String[]{"1", "2", "3"}, new String[]{"2", "4", "6"})
.flatMap(Arrays::stream)
.map(Integer::valueOf)
.sorted(Comparator.reverseOrder())
.reduce(2, Integer::sum); // 输出:20(2+1+2+3+2+4+6)
// 示例 2
Stream.of(new String[]{"1", "2", "3"}, new String[]{"2", "4", "6"})
.flatMap(Arrays::stream)
.map(Integer::valueOf)
.sorted(Comparator.reverseOrder())
.reduce(Integer::sum); // 输出:18(1+2+3+2+4+6)
// 示例 3:求科目总分数
@Data
@NoArgsConstructor
@AllArgsConstructor
class Course{
private String name;
private String course;
private int score;
}
@Data
@NoArgsConstructor
class Score{
private String course;
private int total;
}
@Test
public void testReduce(){
List courses = new ArrayList(){
{
add(new Course("LiHua", "YuWen", 90));
add(new Course("XiaoMing", "YuWen", 98));
add(new Course("XiaoGang", "YuWen", 85));
}
};
courses.stream().reduce(new Score(), (s, c) -> {
s.setCourse(c.getCourse());
s.setTotal(c.getScore());
return s;
}, (a, b) -> {a.setTotal(a.getTotal() + b.getTotal()); return a;});
// 输出:
{
"course":"YunWen",
"total": 273
}
}
遍历
方法 | 入参类型 | 返回类型 | 描述 |
---|---|---|---|
forEach | Consumer | void | 遍历流元素,并对流元素执行 Consumer 操作 |
forEachOrdered | Consumer | void | 遍历流元素,如果流具有定义的遇到顺序,则以流的遇到顺序对流元素执行 Consumer 操作 |
示例:
// 打印流元素
Stream.of("aa", "bbb", "cc", "abb", "ac", "bcc", "abc", "aa").foreach(System.out::println);
聚合
关于Collectors
Collectors
是 java1.8
加入的一个配合 stream
使用的工具类,功能十分强大便利,主要用来生成各种 Collector
,可以帮助我们把流转换成几乎任何我们想要的数据格式。
示例:
// 示例 1
Object [] obj = Stream.of(new String[]{"1", "2", "3"}, new String[]{"2", "4", "6"})
.flatMap(Arrays::stream)
.map(Integer::valueOf)
.sorted(Comparator.reverseOrder())
.toArray();
// 示例 2
Person[] men = people.stream()
.filter(p -> p.getGender() == MALE)
.toArray(Person[]::new);
// 示例 3
List asList = stringStream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
// 示例 4
String concat = stringStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
// 示例 5,聚合成List
List asList = stringStream.collect(Collectors.toList());
// 示例 6,单层分组
Map> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity));
// 示例 7,多层分组
Map>> peopleByStateAndCity = personStream
.collect(Collectors.groupingBy(Person::getState, Collectors.groupingBy(Person::getCity)));
// 示例 8,聚合成Map
Map map = persons.stream().collect(Collectors.toMap(Person::getName, Person::getSex));