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