美文网首页
Java新特性之Java8的那些事儿-Stream

Java新特性之Java8的那些事儿-Stream

作者: I_Jesus | 来源:发表于2019-03-23 11:33 被阅读0次

    什么是流?

    ​ Java Se中对于流的操作有输入输出IO流,而Java8中引入的Stream 属于Java API中的一个新成员,它允许你以声明性方式处理数据集合,Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。 注意这里的流操作可以看做是对集合数据的处理。

    ​ 简单来说,是一种数据渠道,用于操作数据源(集合、数组)所生产的元素序列。

    • 元素序列—就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。
    • 源—流会使用一个提供数据的源,如集合、数组或输入/输出资源。 请注意,从有序集 合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致
    • 数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中 的常用操作,如filter、 map、 reduce、 find、 match、 sort等。流操作可以顺序执 行,也可并行执行。
    • 流水线—很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大 的流水线。
    • 内部迭代—与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

    流操作

    1553245800186.png

    整个流操作就是一条流水线,将元素放在流水线上一个个地进行处理。需要注意的是:很多流操作本身就会返回一个流,所以多个操作可以直接连接起来, 如下图这样,操作可以进行链式调用,实现数据流并行处理操作。

    1553246053766.png

    流与集合

    计算的时机

    ​ Stream 和集合的其中一个差异在于什么时候进行计算,集合,它会包含当前数据结构中所有的值,你可以随时增删,但是集合里面的元素毫无疑问地都是已经计算好了的。 流则是按需计算,按照使用者的需要计算数据,你可以想象我们通过搜索引擎进行搜索,搜索出来的条目并不是全部呈现出来的,而且先显示最符合的前 10 条或者前 20 条,只有在点击 “下一页” 的时候,才会再输出新的 10 条。

    1553246376167.png
    外部迭代与内部迭代

    ​把集合比作一个工厂的仓库,一开始工厂比较落后,要对货物作什么修改,只能工人亲自走进仓库对货物进行处理,有时候还要将处理后的货物放到一个新的仓库里面。在这个时期,我们需要亲自去做迭代,一个个地找到需要的货物,并进行处理,这叫做外部迭代
    后来工厂发展了起来,配备了流水线作业,只要根据需求设计出相应的流水线,然后工人只要把货物放到流水线上,就可以等着接收成果了,而且流水线还可以根据要求直接把货物输送到相应的仓库。这就叫做内部迭代,流水线已经帮你把迭代给完成了,你只需要说要干什么就可以了(即设计出合理的流水线)。
    ​Java 8 引入 Stream 很大程度是因为,流的内部迭代可以自动选择一种合适你硬件的数据表示和并行实现;而以往程序员自己进行 foreach 之类的时候,则需要自己去管理并行等问题。

    1553246403730.png

    创建流

    在 Java 8 中, 集合接口有两个方法来生成流:

    • stream() − 为集合创建串行流。
    • parallelStream() − 为集合创建并行流。

    示例代码如下:

    public static void main(String[] args) {
        /**
             * 定义集合l1 并为集合创建串行流
             */
        List<String> l1 = Arrays.asList("周星驰", "周杰伦", "周星星", "周润发");
        // 返回串行流
        l1.stream();
        // 返回并行流
        l1.parallelStream();
    }
    

    上述操作得到的流是通过原始数据转换过来的流,除了这种流创建的基本操作外,对于流的创建还有以下几种方式。

    值创建流

    ​ Stream.of(T...) : Stream.of("aa", "bb") 生成流

    //值创建流 生成一个字符串流
    Stream<String> stream = Stream.of("java8", "Spring", "SpringCloud");
    stream.forEach(System.out::println);
    
    数组创建流

    根据参数的数组类型创建对应的流。

    • Arrays.stream(T[ ])

    • Arrays.stream(int[ ])

    • Arrays.stream(double[ ])

    • Arrays.stream(long[ ])

     // 只取索引第 1 到第 2 位的:
     int[] a = {1, 2, 3, 4};
     Arrays.stream(a, 1, 3).forEach(System.out :: println);
    
    文件生成流
    //每个元素是给定文件的其中一行
    Stream<String> stream02 = Files.lines(Paths.get("data.txt"));
    
    函数生成流

    两个方法:

    • iterate : 依次对每个新生成的值应用函数
    • generate :接受一个函数,生成一个新的值
    //生成流,首元素为 0,之后依次加 2
    Stream.iterate(0, n -> n + 2)
    //生成流,为 0 到 1 的随机双精度数
    Stream.generate(Math :: random)
    //生成流,元素全为 1
    Stream.generate(() -> 1)
    

    流的中间操作

    ​ 常见的流的中间操作,归为以下三大类:筛选和切片流操作、元素映射操作、元素排序操作:

    操作 描述
    筛选和切片 filter(T -> boolean):保留 boolean 为 true 的元素
    limit(long n):返回前 n 个元素
    skip(long n):去除前 n 个元素
    distinct():去除重复元素,这个方法是通过类的 equals 方法来判断两个元素是否相等的
    映射 map(T -> R):将流中的每一个元素 T 映射为 R(类似类型转换)
    flatMap(T -> Stream<R>): 将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流
    排序 sorted() / sorted((T, T) -> int):如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如 Stream<Integer>
    筛选和切片

    定义订单Order对象

    public class Order {
        // 订单id
        private Integer id;
        // 订单用户id
        private Integer userId;
        // 订单编号
        private  String orderNo;
        // 订单日期
        private Date orderDate;
        // 收货地址
        private String address;
        // 创建时间
        private Date createDate;
        // 更新时间
        private Date updateDate;
        // 订单状态  0-未支付  1-已支付  2-代发货  3-已发货  4-已接收  5-已完成
        private Integer status;
        // 是否有效  1-有效订单  0-无效订单
        private Integer isValid;
        
        //订单总金额
        private  Double total;
        /**
           此处省略getter/setter方法
        */
    }
    
    public static void main(String[] args) {
            Order order01 = new Order(1,10,"20190301",
                    new Date(),"上海市-浦东区",new Date(),new Date(),4,1,100.0);
            Order order02 = new Order(2,30,"20190302",
                    new Date(),"北京市四惠区",new Date(),new Date(),1,1,2000.0);
            Order order03 = new Order(3,20,"20190303",
                    new Date(),"北京市-朝阳区",new Date(),new Date(),4,1,500.0);
            Order order04 = new Order(4,40,"20190304",
                    new Date(),"北京市-大兴区",new Date(),new Date(),4,0,256.0);
            Order order05 = new Order(5,40,"20190304",
                    new Date(),"上海市-松江区",new Date(),new Date(),4,0,1000.0);
            List<Order> ordersList= Arrays.asList(order01,order02,order03,order04);
            // 过滤订单集合 有效订单 并打印到控制台
            ordersList.stream().filter((order)->order.getIsValid()==1).forEach(System.out::println);
            // 过滤订单集合有效订单 取前两条有效订单 并打印到控制台
            ordersList.stream().filter((order)->order.getIsValid()==1).limit(2).forEach(System.out::println);
        }
            // 过滤订单集合有效订单 取最后一条记录
            ordersList.stream().filter((order)->order.getIsValid()==1)
                    .skip(ordersList.size()-2).forEach(System.out::println);
    
    // 去除订单编号重复的无效订单记录 此时因为比较的为Object Order对象需要重写HashCode 与Equals 方法
    /**
         * 重写 equals 方法
         * @param obj
         * @return
         */
        @Override
        public boolean equals(Object obj) {
            boolean flag = false;
            if (obj == null) {
                return flag;
            }
            Order order = (Order) obj;
            if (this == order) {
                return true;
            } else {
                return (this.orderNo.equals(order.orderNo));
            }
        }
    
        /**
         * 重写hashcode方法
         * @return
         */
        @Override
        public int hashCode() {
            int hashno = 7;
            hashno = 13 * hashno + (orderNo == null ? 0 : orderNo.hashCode());
            return hashno;
        }
     // 过滤订单集合无效订单 去除订单号重复记录
      ordersList.stream().filter((order)->order.getIsValid()==0).distinct().forEach(System.out::println);
    
    映射
    //过滤订单集合有效订单  获取所有订单订单编号
    ordersList.stream().filter((order)->order.getIsValid()==1).map((order)->order.getOrderNo()).forEach(System.out::println);
    
    // 过滤有效订单  并分离每个订单下收货地址市区信息
    ordersList.stream().map(o->o.getAddress().split("-")).flatMap(Arrays::stream).forEach(System.out::println);
    
    排序
     //过滤有效订单 并根据用户id 进行排序
     ordersList.stream().filter((order)->order.getIsValid()==1)
     .sorted((o1,o2)->o1.getUserId()-o2.getUserId()).forEach(System.out::println);
    //或者等价写法
    ordersList.stream().filter((order)->order.getIsValid()==1)
                    .sorted(Comparator.comparingInt(Order::getUserId)).forEach(System.out::println);
    
    // 定制排序规则
    /*过滤有效订单
     * 定制排序:如果订单状态相同 根据订单创建时间排序 反之根据订单状态排序
    */
    ordersList.stream().filter((order)->order.getIsValid()==1).sorted((o1,o2)->{
       if(o1.getStatus().equals(o2.getStatus())){
            return o1.getCreateDate().compareTo(o2.getCreateDate());
        }else{
            return o1.getStatus().compareTo(o2.getStatus());
        }}).forEach(System.out::println);
    

    流的终止操作

    终止操作会从流的流水线生成结果。其结果是任何不是流的值,比如常见的List、 Integer,甚 至void等结果。
    对于流的终止操作,分为一下三类:

    操作 描述
    查找与匹配 allMatch:检查是否匹配所有元素
    anyMatch(T -> boolean): 流中是否有一个元素匹配给定的 T -> boolean 条件
    noneMatch(T -> boolean): 流中是否没有元素匹配给定的 T -> boolean 条件
    findAny():找到其中一个元素 (使用 stream() 时找到的是第一个元素;使用 parallelStream() 并行时找到的是其中一个元素)
    findFirst():找到第一个元素
    max():返回流中最大值
    min():返回流中最小值
    count():返回流中元素的总个数
    归约: 将流中元素繁复结合起来,得到一个值 reduce((T, T) -> T) 和 reduce(T, (T, T) -> T): 用于组合流中的元素,如求和,求积,求最大值等
    收集: 将流转换为其他形式,接收一个Collertor接口的实现,用于给Stream中元素做汇总的方法 collect()
    查找与匹配
      // 筛选所有有效订单  匹配用户id =20 的所有订单
    System.out.println("allMatch匹配结果:"+ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).allMatch((o) -> o.getUserId() == 20));
    System.out.println("anyMatch匹配结果:"+ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).anyMatch((o) -> o.getUserId() == 20));
    System.out.println("noneMatch匹配结果:"+ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).noneMatch((o) -> o.getUserId() == 20));
    
    //  筛选所有有效订单 返回订单总数
    System.out.println("count结果:"+ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).count());
    // 筛选所有有效订单 返回金额最大订单值
    Optional<Double> max=ordersList.stream().filter((order) -> order.getIsValid() == 1)
        .map(Order::getTotal).max(Double::compare);
    System.out.println("订单金额最大值:"+max.get());
    // 筛选所有有效订单 返回金额最小订单值
    Optional<Double> min=ordersList.stream().filter((order) -> order.getIsValid() == 1)
        .map(Order::getTotal).min(Double::compare);
    System.out.println("订单金额最小值:"+min.get());
    
    归约

    ​ 将流中元素繁复结合起来,得到一个值的操作。

    // 归约操作  计算有效订单总金额
    System.out.println("有效订单总金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1).map(Order::getTotal).reduce(Double::sum).get());
    
    Collector收集数据
    收集

    ​ 将流转换为其他形式,coollect 方法作为终端操作, 接收一个Collector接口的实现,用于给Stream中元素做汇总的方法。最常用的方法,把流中所有元素收集到一个 List, Set 或 Collection 中

    • toList
    • toSet
    • toCollection
    • toMap
    // 收集操作
    // 筛选所有有效订单 并收集订单列表
    List<Order> orders= ordersList.stream().filter((order) -> order.getIsValid() == 1).collect(Collectors.toList());
    orders.forEach(System.out::println);
    // 筛选所有有效订单 并收集订单号 与 订单金额
    Map<String,Double> map=ordersList.stream().filter((order) -> order.getIsValid() == 1).
        collect(Collectors.toMap(Order::getOrderNo, Order::getTotal));
    // java8 下对map 进行遍历操作 如果 Map 的 Key 重复了,会报错
    map.forEach((k,v)->{
        System.out.println("k:"+k+":v:"+v);
    });
    
    汇总
    • countintg():用于计算总和
    • count():用于计算总和(推荐使用,写法更简洁)
    • summingInt() ,summingLong(),summingDouble():用于计算总和
    • averagingInt(),averagingLong(),averagingDouble()用于平均
    • summarizingInt,summarizingLong,summarizingDouble 同样可以实现计算总和,平均等操作,比如summarizingInt 结果会返回IntSummaryStatistics 类型 ,然后通过get方法获取对应汇总值即可
    // 汇总操作
    //筛选所有有效订单 返回订单总数
    System.out.println("count结果:"+ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).collect(Collectors.counting()));
    System.out.println("count结果:"+ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).count());
    
    //  返回订单总金额
    System.out.println("订单总金额:"+ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).collect(Collectors.summarizingDouble(Order::getTotal)));
    System.out.println("订单总金额:"+ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).mapToDouble(Order::getTotal).sum());
    System.out.println("订单总金额:"+ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).map(Order::getTotal).reduce(Double::sum).get());
    
    // 返回 用户id=20 有效订单平均每笔消息金额
    System.out.println("用户id=20 有效订单平均每笔消费金额:"+ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).
                       filter((order -> order.getUserId()==20))
                       .collect(Collectors.averagingDouble(Order::getTotal)));
    System.out.println("用户id=20 有效订单平均每笔消费金额:"+
                       ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).
                       filter((order -> order.getUserId()==20))
                       .mapToDouble(Order::getTotal).average().getAsDouble());
    
    System.out.println("用户id=20 有效订单平均每笔消费金额:"+
                       ordersList.stream().
                       filter((order) -> order.getIsValid() == 1).
                       filter((order -> order.getUserId()==20))
                       .collect(Collectors.summarizingDouble(Order::getTotal)).getAverage());
    
    // 筛选所有有效订单 并计算订单总金额
    System.out.println("订单总金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1)
                       .collect(Collectors.summingDouble(Order::getTotal)));
    // 筛选所有有效订单 并计算最小订单金额
    System.out.println("最小订单金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1)
                       .map(Order::getTotal).collect(Collectors.minBy(Double::compare)));
    // 筛选所有有效订单 并计算最大订单金额
    System.out.println("最大订单金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1)
                       .map(Order::getTotal).collect(Collectors.maxBy(Double::compare)));
    
    最值

    ​ maxBy,minBy 两个方法,需要一个 Comparator 接口作为参数,实现最大 最小值获取操作

    // 取最会
    // 筛选所有有效订单 并计算最小订单金额
    System.out.println("最小订单金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1)
                       .map(Order::getTotal).collect(Collectors.minBy(Double::compare)));
    // 筛选所有有效订单 并计算最大订单金额
    System.out.println("最大订单金额:"+ordersList.stream().filter((order) -> order.getIsValid() == 1)
                       .map(Order::getTotal).collect(Collectors.maxBy(Double::compare)));
    
    分组

    groupingBy 用于将数据分组,最终返回一个 Map 类型

    groupingBy 可以接受一个第二参数实现多级分组

    // 分组-根据有效订单支付状态进行分组操作
    Map<Integer,List<Order>> g01=ordersList.stream().filter((order) -> order.getIsValid() == 1)
        .collect(Collectors.groupingBy(Order::getStatus));
    g01.forEach((status,order)->{
        System.out.println("----------------");
        System.out.println("订单状态:"+status);
        order.forEach(System.out::println);
    });
    
    // 分组-查询有效订单 根据用户id 和 支付状态进行分组
    Map<Integer,Map<String,List<Order>>> g02= ordersList.stream().filter((order) -> order.getIsValid() == 1)
        .collect(Collectors.groupingBy(Order::getUserId,Collectors.groupingBy((o)->{
            if(o.getStatus()==0){
                return "未支付";
            }else if (o.getStatus()==1){
                return "已支付";
            }else if (o.getStatus()==2){
                return "待发货";
            }else if (o.getStatus()==3){
                return "已发货";
            }else if (o.getStatus()==4){
                return "已接收";
            } else{
                return "已完成";
            }
        })));
    g02.forEach((userId,m)->{
        System.out.println("用户id:"+userId+"-->有效订单如下:");
        m.forEach((status,os)->{
            System.out.println("状态:"+status+"---订单列表如下:");
            os.forEach(System.out::println);
        });
        System.out.println("-----------------------");
    });
    
    partitioningBy 分区

    ​ 分区与分组的区别在于,分区是按照 true 和 false 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean

    // 分区操作  筛选订单金额>1000 的有效订单
    Map<Boolean,List<Order>> g03= ordersList.stream().filter((order) -> order.getIsValid() == 1)
        .collect(Collectors.partitioningBy((o)->o.getTotal()>1000));
    g03.forEach((b,os)->{
        System.out.println("分区结果:"+b+"--列表结果:");
        os.forEach(System.out::println);
    });
    
    // 拼接操作 筛选有效订单 并进行拼接
    String orderStr=ordersList.stream().filter((order) -> order.getIsValid() == 1).map(Order::getOrderNo)
        .collect(Collectors.joining(","));
    System.out.println(orderStr);
    

    相关文章

      网友评论

          本文标题:Java新特性之Java8的那些事儿-Stream

          本文链接:https://www.haomeiwen.com/subject/emojvqtx.html