Stream 用来处理集合数据的,通过 stream 操作可以实现 SQL 的拥有的大部分查询功能
下面借助例子,演示 stream 操作
Java userList 列表
1 | private List<User> userList = Arrays.asList( |
2 | new User(101, "小明", 10, "男", "青海省", "西宁市"), |
3 | new User(102, "小青", 12, "女", "宁夏回族自治区", "银川市"), |
4 | new User(103, "小海", 8, "男", "西藏自治区", "拉萨市"), |
5 | new User(108, "阿刁", 18, "女", "西藏自治区", "拉萨市"), |
6 | new User(104, "小阳", 9, "女", "新疆维吾尔自治区", "乌鲁木齐市"), |
7 | new User(105, "小强", 14, "男", "陕西省", "西安市"), |
8 | new User(106, "小帅", 15, "男", "河北省", "石家庄市"), |
9 | new User(107, "小云", 15, "女", "河北省", "石家庄市") |
10 | ); |
MySQL user 表数据
1 | DROP TABLE IF EXISTS `user`; |
2 | CREATE TABLE `user` ( |
3 | `id` int(11) PRIMARY KEY, |
4 | `name` varchar(20), |
5 | `age` int(2), |
6 | `gender` varchar(10), |
7 | `province` varchar(100), |
8 | `city` varchar(100) |
9 | ) ; |
10 | |
11 | INSERT INTO `user` VALUES (101, '小明', 10, '男', '青海省', '西宁市'); |
12 | INSERT INTO `user` VALUES (102, '小青', 12, '女', '宁夏回族自治区', '银川市'); |
13 | INSERT INTO `user` VALUES (103, '小海', 8, '男', '西藏自治区', '拉萨市'); |
14 | INSERT INTO `user` VALUES (104, '小阳', 9, '女', '新疆维吾尔自治区', '乌鲁木齐市'); |
15 | INSERT INTO `user` VALUES (105, '小强', 14, '男', '陕西省', '西安市'); |
16 | INSERT INTO `user` VALUES (106, '小帅', 15, '男', '河北省', '石家庄市'); |
17 | INSERT INTO `user` VALUES (107, '小云', 15, '女', '河北省', '石家庄市'); |
查询字段 select - map
1 | // select id from user |
2 | userList.stream() |
3 | .map(e -> e.getId()) |
4 | .forEach(System.out::println); |
至于如何实现 select id, name from user 查询多字段在下面 collector 收集器会详细讲解
条件 where - filter
1 | // select * from user where age<10 |
2 | userList.stream() |
3 | .filter(e-> e.getAge() < 10) |
4 | .forEach(System.out::println); |
5 | |
6 | // select * from user where age<10 and gender='男' |
7 | userList.stream() |
8 | .filter(e->e.getAge() < 10) |
9 | .filter(e->e.getGender()=="男") |
10 | .forEach(System.out::println); |
最值、总和、数量、均值(max, min, sum, count, average)
1 | // select max(age), min(age), sum(age), count(age), avg(age) from user |
2 | // max |
3 | Optional<Integer> maxAge = userList.stream() |
4 | .map(e -> e.getAge()) |
5 | .max(Comparator.comparingInt(x -> x)); |
6 | // 等同于 |
7 | // Optional<Integer> maxAge = userList.stream() |
8 | // .map(e -> e.getAge()) |
9 | // .max((x, y) -> x-y); |
10 | |
11 | // min |
12 | Optional<Integer> minAge = userList.stream() |
13 | .map(e -> e.getAge()) |
14 | .min(Comparator.comparingInt(x -> x)); |
15 | // sum |
16 | Optional<Integer> sumAge = userList.stream() |
17 | .map(e -> e.getAge()) |
18 | .reduce((e, u) -> e + u); |
19 | // count |
20 | long count = userList.stream() |
21 | .map(e -> e.getAge()) |
22 | .count(); |
23 | // 平均值=总和/数量 |
排序 order by - sorted
1 | // select * from user order by age |
2 | userList.stream() |
3 | .sorted(Comparator.comparingInt(User::getAge)) |
4 | .forEach(System.out::println); |
分页 limit - skip、limit
1 | // select * from user limit 5 |
2 | userList.stream() |
3 | .limit(5) |
4 | .forEach(System.out::println); |
5 | |
6 | // select * from user limit 5, 5 |
7 | userList.stream() |
8 | .skip(5) |
9 | .limit(5) |
10 | .forEach(System.out::println); |
11 | |
12 | // select * from user order by age limit 1 |
13 | userList.stream() |
14 | .sorted(Comparator.comparingInt(User::getAge)) |
15 | .limit(1) |
16 | .forEach(System.out::println); |
17 | // 或者 |
18 | Optional<User> minAgeUser = userList.stream() |
19 | .sorted(Comparator.comparingInt(User::getAge)) |
20 | .findFirst(); |
是否存在 exists - anymatch
1 | // select exists(select * from user where name='小海') |
2 | // 有没有名字叫“小海”的用户 |
3 | boolean exists0 = userList.stream() |
4 | .anyMatch(e -> e.getName().equals("小海")); |
5 | |
6 | // select not exists(select * from user where name='小海') |
7 | // 是不是没有名字叫“小海”的用户 |
8 | boolean exists1 = userList.stream() |
9 | .noneMatch(e -> e.getName().equals("小海")); |
10 | |
11 | // 是不是所有用户年龄都小于10岁 |
12 | boolean exists2 = userList.stream() |
13 | .allMatch(e -> e.getAge() < 10); |
收集操作 collect
收集操作就是遍历 stream 中的元素,并进行累加处理,即归约 reduction
A reduction operation (also called a fold) takes a sequence of input elements and combines them into a single summary result by repeated application of a combining operation, such as finding the sum or maximum of a set of numbers, or accumulating elements into a list.
前面提到的 max() min() count() reduce() 都属于 reduction operation
但 collect() 又和前面这几种归约操作有所区别,它是 Mutable reduction 动态归约
A mutable reduction operation accumulates input elements into a mutable result container, such as a
CollectionorStringBuilder, as it processes the elements in the stream
区别:动态归约将结果放进 Collection StringBuilder 这样的动态容器中,所以称为动态归约。
Stream 接口提供了两个 collect() 方法
1 | <R> R collect(Supplier<R> supplier, |
2 | BiConsumer<R, ? super T> accumulator, |
3 | BiConsumer<R, R> combiner); |
4 | |
5 | <R, A> R collect(Collector<? super T, A, R> collector); |
我们只需理解了第一个方法,第二个方法就手到擒来了
理解第一个 collect 方法,强烈建议阅读文档 动态归约的定义,下面只简单的介绍一下它
三个参数:
- 供给者 supplier:负责提供动态容器,例如 Collectors、StringBuilder
- 累加器 accumulator:负责将流中的元素做累加处理
- 合并者 combiner :负责将两个容器的元素合并在一起
在串行流中,combiner 根本没有执行,所以随便写点啥满足参数对象就行。
如果说串行流是单线程,那么并行流就是多线程了
举个例子:
1 | |
2 | ArrayList<String> strings = new ArrayList<>(); |
3 | for (T element : stream) { |
4 | strings.add(element.toString()); |
5 | } |
6 | // 等同于 |
7 | ArrayList<String> strings = stream.collect(() -> new ArrayList<>(), |
8 | (c, e) -> c.add(e.toString()), |
9 | (c1, c2) -> c1.addAll(c2)); |
与其传递三个参数这么麻烦,还不如直接传递一个对象呢!
这就是第二个 collect() 方法的由来,使用收集器 Collector 来替代三个参数
实际上,我们一般不需要自己创建 Collector 对象,Java8 提供了一个 Collectors 类,专门提供收集器 Collector 对象。毕竟我们平时能够使用到的收集操作也就那几种:转为集合对象、分组、统计。
下面以例子演示
在初看 stream 操作的时候,我被什么创建、中间操作、终止操作、不会改变原对象给弄晕了,我根本不关心这些,我的第一想法是怎么将操作后的数据导出来,重新变成集合对象。
toCollection
不使用收集器的情况下:
1 | List<User> subUserList1 = userList.stream() |
2 | .filter(e -> e.getAge() < 10) |
3 | .filter(e -> e.getGender() == "男") |
4 | .collect(() -> new ArrayList<>(), |
5 | (c, e) -> c.add(e), |
6 | (c1, c2) -> c1.addAll(c2)); |
在 collect() 方法第二个参数累加器 accumulator (c, e) -> c.add(e) 这里,对流中元素进行了遍历,所以可以把流中元素添加到任意的集合容器中,List、Set、Map 等等
使用 Collectors 工具类提供的收集器:
1 | // toList() |
2 | List<User> list = userList.stream() |
3 | .filter(e -> e.getAge() < 10) |
4 | .filter(e -> e.getGender() == "男") |
5 | .collect(Collectors.toList()); |
6 | |
7 | // toSet() |
8 | Set<User> set = userList.stream() |
9 | .filter(e -> e.getAge() < 10) |
10 | .filter(e -> e.getGender() == "男") |
11 | .collect(Collectors.toSet()); |
12 | |
13 | // toCollection(),想要返回什么容器,就 new 一个 |
14 | ArrayList<User> collection = userList.stream() |
15 | .filter(e -> e.getAge() < 10) |
16 | .filter(e -> e.getGender() == "男") |
17 | .collect(Collectors.toCollection( |
18 | () -> new ArrayList<>() |
19 | )); |
这里插播一条新闻:如何将流转为数组?
Stream 提供了方法 toArray()
1 | Object[] toArray(); |
2 | <A> A[] toArray(IntFunction<A[]> generator); |
小试牛刀:
1 | Object[] nameArray = userList.stream() |
2 | .map(e -> e.getName()) |
3 | .toArray(); |
4 | Arrays.stream(nameArray) |
5 | .forEach(System.out::println); |
6 | // 转为 User 对象数组 |
7 | User[] users = userList.stream() |
8 | .filter(e -> e.getGender() == "女") |
9 | .toArray(User[]::new); |
10 | Arrays.stream(users) |
11 | .forEach(System.out::println); |
toStringBuilder
不使用收集器的情况下:
1 | StringBuilder joinName = userList.stream() |
2 | .map(e -> e.getName()) |
3 | .collect(StringBuilder::new, |
4 | (s, e) -> s = s.length() > 0 ? s.append("-" + e) : s.append(e), |
5 | (s1, s2) -> s1.append(s2) |
6 | ); |
谁能告诉我在Java中怎么单独使用三元运算符?s = s.length() > 0 ? s.append("-" + e) : s.append(e) 我想把 s = 省略掉,但 Java 中不行
使用 Collectors 类提供的收集器:
1 | String joinName1 = userList.stream() |
2 | .map(e -> e.getName()) |
3 | .collect(Collectors.joining()); |
4 | |
5 | String joinName2 = userList.stream() |
6 | .map(e -> e.getName()) |
7 | .collect(Collectors.joining("-")); |
8 | |
9 | String joinName3 = userList.stream() |
10 | .map(e -> e.getName()) |
11 | .collect(Collectors.joining("-", "[", "]")); |
至于 Collectors.joining() 参数分别代表什么含义,看一下它们的参数名称,就明白了
1 | public static Collector<CharSequence, ?, String> joining(CharSequence delimiter, // 分隔符 |
2 | CharSequence prefix, // 前缀 |
3 | CharSequence suffix) // 后缀 |
toMap
在 Collectors 中一共有3个 toMap(),它们用来处理不同的问题
两个参数的 toMap
1 | Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, |
2 | Function<? super T, ? extends U> valueMapper) { |
3 | return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new); |
4 | } |
参数
keyMapper用来获取key;valueMapper用来获取 value它的内部调用了四个参数的 toMap() 方法
例子
1 | Map<Integer, User> map1 = userList.stream() |
2 | .collect(Collectors.toMap(e -> e.getId(), Function.identity())); |
3 | System.out.println(map1); |
4 | // Function.identity() 等价于 e -> e |
5 | |
6 | // select id, name, gender from user |
7 | Map<Integer, Map<String, Object>> map2 = userList.stream() |
8 | .collect(Collectors.toMap(e -> e.getId(), e -> { |
9 | Map<String, Object> map = new HashMap<>(); |
10 | map.put("gender", e.getGender()); |
11 | map.put("name", e.getName()); |
12 | map.put("id", e.getId()); |
13 | return map; |
14 | })); |
15 | System.out.println(map2); |
你:如果 key 冲突了咋办?
Java8:你想咋办就咋办
三个参数的 toMap
1 | Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, |
2 | Function<? super T, ? extends U> valueMapper, |
3 | BinaryOperator<U> mergeFunction) { |
4 | return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); |
5 | } |
第三个参数
mergeFunction就是用来处理 key 键冲突的内部也是调用了四个参数的 toMap() 方法
例子
1 | // 如果 key 冲突,那么将冲突的 value 值拼接在一起 |
2 | Map<String, String> map3 = userList.parallelStream() |
3 | .collect(Collectors.toMap( |
4 | e -> e.getGender(), |
5 | e -> e.getName(), |
6 | (o1, o2) -> o1 + ", " + o2 |
7 | ) |
8 | ); |
9 | System.out.println(map3); |
你:我想自己 new 一个 Map 对象
四个参数的 toMap
1 | Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, |
2 | Function<? super T, ? extends U> valueMapper, |
3 | BinaryOperator<U> mergeFunction, |
4 | Supplier<M> mapSupplier) |
参数
mapSupplier用来提供返回容器
例子
1 | LinkedHashMap<String, String> map4 = userList.parallelStream() |
2 | .collect(Collectors.toMap( |
3 | e -> e.getGender(), |
4 | e -> e.getName(), |
5 | (o1, o2) -> o1 + ", " + o2, |
6 | LinkedHashMap::new |
7 | ) |
8 | ); |
9 | System.out.println(map4); |
reducing
单参数和两参数的 reducing()
1 | Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) |
2 | Collector<T, ?, U> reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op) |
以例子具体解释这两个方法
1 | Optional<String> names1 = userList.stream() |
2 | .map(User::getName) |
3 | .collect(Collectors.reducing((e1, e2) -> e1 + "," + e2)); |
4 | System.out.println(names1.get()); |
5 | |
6 | // 等同于 |
7 | String names2 = userList.stream() |
8 | .collect(Collectors.reducing( |
9 | "", |
10 | (e) -> e.getName(), |
11 | (e1, e2) -> e1 + "," + e2) |
12 | ); |
13 | System.out.println(names2); |
输出结果:
1 | 小明,小青,小海,阿刁,小阳,小强,小帅,小云 |
2 | ,小明,小青,小海,阿刁,小阳,小强,小帅,小云 |
参数
identity表示返回结果的初始值
三参数的 reducing()
1 | reducing(U identity, Function<? super T,? extends U> mapper, BinaryOperator<U> op) |
identity 是初始值,mapper 会对元素先进行一次处理,然后 op 对元素进行归约操作
注意: 返回类型要和参数
identity的一致。
你也许会纳闷,为什么有的返回一个Optional<String>类型数据,而有的就返回了String
因为含有参数identity的 reduing 方法中返回值有初始值,也就是 identity,所以不会出现空的情况
下面Collectors 提供的一些常用归约收集器
1 | // minBy、maxBy |
2 | Optional<User> minAgeUser = userList.stream() |
3 | .collect(Collectors.minBy((o1, o2) -> o1.getAge() - o2.getAge())); |
4 | |
5 | // counting |
6 | Long count = userList.stream() |
7 | .collect(Collectors.counting()); |
8 | |
9 | // summingInt、summingLong、summingDouble、 |
10 | Integer sumAge = userList.stream() |
11 | .collect(Collectors.summingInt(User::getAge)); |
12 | |
13 | // averagingInt、averagingLong、averagingDouble |
14 | // 平均值内部是总值/数量,所以返回值是浮点数 dobule |
15 | Double avgAge = userList.stream() |
16 | .collect(Collectors.averagingInt(User::getAge)); |
你也许觉得每次都要执行一遍 minBy、maxBy、counting、summingXxx、averagingXxx 这些太麻烦了,有没有一次执行就获取所有这些方法结果?
有的。这就是 summarizingXxx
1 | Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) |
2 | Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper) |
3 | Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper) |
这里不演示了,实际上你看一下 XxxSummaryStatistics 这些类就明白了,比如
1 | public class IntSummaryStatistics implements IntConsumer { |
2 | private long count; |
3 | private long sum; |
4 | private int min = Integer.MAX_VALUE; |
5 | private int max = Integer.MIN_VALUE; |
6 | ... |
7 | } |
group by
最最激动人心的时候到了,我们要使用分组了!!!
1 | Map<String, List<User>> map = userList.stream() |
2 | .collect(Collectors.groupingBy(User::getGender)); |
SQL 中的 group by 结果集中只能包含分组字段和聚合函数计算结果,这段代码比它更加全面
我们使用如下语句输出结果
1 | map.keySet().stream() |
2 | .forEach((e) -> { |
3 | System.out.println(e + "=" + map.get(e)); |
4 | }); |
显示结果:
1 | 女=[User{id=102, name='小青', age=12, gender='女', province='宁夏回族自治区', city='银川市'}, User{id=108, name='阿刁', age=18, gender='女', province='西藏自治区', city='拉萨市'}, User{id=104, name='小阳', age=9, gender='女', province='新疆维吾尔自治区', city='乌鲁木齐市'}, User{id=107, name='小云', age=15, gender='女', province='河北省', city='石家庄市'}] |
2 | 男=[User{id=101, name='小明', age=10, gender='男', province='青海省', city='西宁市'}, User{id=103, name='小海', age=8, gender='男', province='西藏自治区', city='拉萨市'}, User{id=105, name='小强', age=14, gender='男', province='陕西省', city='西安市'}, User{id=106, name='小帅', age=15, gender='男', province='河北省', city='石家庄市'}] |
它真的分组了!!这是真正的分组
那怎么对分组中的元素进行操作呢,像 SQL 那样??
完全不用担心,Collectors 提供了三个 groupBy 方法返回分组收集器
1 | Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T,? extends K> classifier) |
2 | |
3 | Collector<T, ?, Map<K, D>> groupingBy(Function<? super T,? extends K> classifier, |
4 | Collector<? super T,A,D> downstream) |
5 | |
6 | Collector<T, ?, M> groupingBy(Function<? super T,? extends K> classifier, |
7 | Supplier<M> mapFactory, |
8 | Collector<? super T,A,D> downstream) |
让我们放飞想象的翅膀,思考一下这几个参数分别有什么用。
downstream ?有 down 就表示有 up。那么谁是 upstream,很明显是 userList.stream,那么 downstream 就是分组集合的流喽。猜测 downstream 收集器是对分组中的元素进行归约操作的,就像是分组 SQL 语句字段中的聚合操作一样。
1 | // select gender, count(*) from user group by gender |
2 | Map<String, Long> map2 = userList.stream() |
3 | .collect(Collectors.groupingBy(User::getGender, Collectors.counting())); |
4 | System.out.println(map2); |
输出结果确实不出所料!这就是证明参数 downstream 确实是分组集合元素的收集器。
Supplier<M> mapFactory 这函数式接口方法不会有参数传入,所以不会操作集合元素;它只是返回一个变量。同志们,注意观察三个方法返回值,前二者都指定了 Map 作为归约操作的返回类型,而第三个要我们自己定义,使用 mapFactory 提供返回的数据容器
两参数的 groupingBy 方法其实是调用了三参数的 groupingBy 方法(而单参数 groupingBy 调用了两参数的 groupingBy)
1 | Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, |
2 | Collector<? super T, A, D> downstream) { |
3 | return groupingBy(classifier, HashMap::new, downstream); |
4 | } |
groupingBy 经常使用 Collectors.mapping() 处理分组集合
1 | Map<String, List<Map<String, Object>>> map4 = userList.stream() |
2 | .collect(Collectors.groupingBy( |
3 | User::getGender, |
4 | Collectors.mapping(e -> { |
5 | Map<String, Object> m = new HashMap<>(); |
6 | m.put("name", e.getName()); |
7 | m.put("id", e.getId()); |
8 | return m; |
9 | }, Collectors.toList()) |
10 | )); |
11 | System.out.println(map4); |
输出结果:
1 | {女=[{name=小青, id=102}, {name=阿刁, id=108}, {name=小阳, id=104}, {name=小云, id=107}], 男=[{name=小明, id=101}, {name=小海, id=103}, {name=小强, id=105}, {name=小帅, id=106}]} |
partitionBy
实际上也是分组,只不过 partitionBy 是按照布尔值(真假)来分组
1 | Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) { |
2 | return partitioningBy(predicate, toList()); |
3 | } |
4 | |
5 | Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, |
6 | Collector<? super T, A, D> downstream) |
例子:大于10岁为一组,小于等于10的为一组
1 | Map<Boolean, List<User>> map1 = userList.stream() |
2 | .collect(Collectors.partitioningBy(e -> e.getAge() > 10)); |
例子:统计大于10岁的有多少人,小于等于10岁的有多少人
1 | Map<Boolean, Long> map2 = userList.stream() |
2 | .collect(Collectors.partitioningBy(e -> e.getAge() > 10, Collectors.counting())); |
第二个参数 downstream 用来处理分组集合
结语
Java8 提供的 stream 几乎是穷尽了所有集合元素能有的操作,起码是穷尽了我脑海里对集合元素操作的所有想象
这篇文章也列举了 stream 绝大部分的功能,尽量写得通俗易懂,但读者理解起来可能还是有模糊的地方,这时建议大家参考 Java8 API 官方文档 ,多做几个 Demo 加深理解
不要过度使用
stream 是为了方便集合操作,简化代码而推出的,提升代码执行效率并不是它的目的。
虽然,并行流会对代码的执行效率有较大的提升(尤其是数据量非常大的时候),但也依赖于计算机的CPU配置。
Stream 能实现的功能,for 循环都能实现,只是 Stream 代码一般比较简洁,可读性强。但在某些情况下,使用 for 循环要比 Stream 要简洁代码逻辑清晰
举个例子:
1 | private List<Order> orderList = Arrays.asList( |
2 | new Order(103), |
3 | new Order(106), |
4 | new Order(107), |
5 | new Order(104), |
6 | new Order(102), |
7 | new Order(103), |
8 | new Order(102), |
9 | new Order(101), |
10 | new Order(104), |
11 | new Order(102), |
12 | new Order(105) |
13 | ); |
14 | // 现根据 userId 设置 Order 对象的 name 属性 |
15 | |
16 | // 使用 stream |
17 | List<Order> newOrderList = orderList.stream() |
18 | .map(o -> userList.stream() |
19 | .filter(u -> u.getId() == o.getUserId()) |
20 | .findFirst() |
21 | .map(u -> { |
22 | o.setUserName(u.getName()); |
23 | return o; |
24 | }) |
25 | .orElse(o)) |
26 | .collect(Collectors.toList()); |
27 | newOrderList.stream().forEach(System.out::println); |
28 | |
29 | // 使用 for 循环 |
30 | for (Order o : orderList) { |
31 | for (User u : userList) { |
32 | if (o.getUserId() == u.getId()) { |
33 | o.setUserName(u.getName()); |
34 | break; |
35 | } |
36 | } |
37 | } |
38 | orderList.stream().forEach(System.out::println); |
在这个例子中,使用 for 循环要比 使用 stream 干净利落的多,代码逻辑清晰简明,可读性也比 stream 好。