美文网首页
java8-stream API

java8-stream API

作者: Equals__ | 来源:发表于2020-04-12 22:51 被阅读0次

    1. 前言

    Lambda 是JAVA8中最为重要的改变之一, 而Stream API(java.util.stream.*)则是另外一个重要的改变.Stream是JAVA8中处理集合的一个抽象的概念,通过Steam来处理集合中的数据,被处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等,就像使用Sql执行数据库数据处理一样简单,Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

    2. 什么是Stream.

    Stream(流)是一个来自数据源(Set,List等)的元素队列并支持聚合操作. 集合主要是为了存储其中的数据, 而Steam流 则是为了去计算.
    需要注意:

    • Stream并不会存储对象.
    • Stream也不会改变数据源,Srream的中间操作(filter,map等操作)都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。
    • Stream 是通过内部迭代的方式,无需程序员像在操作Collection的时候使用for循环或者迭代器手动去遍历.

    3. Stream的操作步骤 以及对应API

    Stream操作步骤分为三步,

    1. 创建Stream
      对一个数据源获取其流.
    2. 中间操作
      对数据源数据进行相应的处理(filter,map,sorted等).
    3. 终止操作
      执行中间操作,并产生结果.
    stream.png

    3.1 Stream 创建操作

    Collection,Arrays 在JDK1.8 中均存在Stream()方法. (PS: JDK1.8后接口中default修饰的方法可以有方法体)


    Collection中的stream().png Arrays中的stream().png
      1. stream() − 为集合创建串行流。
            List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
            numbers.stream()  //创建串行流
                    .forEach(System.out::println); //  遍历结果
    //result: 1,2,3,4,5
    
      1. parallelStream() − 为集合创建并行流。
            List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6);
            integers.parallelStream()  //parallelStream提供了流的并行处理,它是Stream的另一重要特性,其底层使用Fork/Join框架实现。
                    .forEach(num ->System.out.println(Thread.currentThread().getName()+">>>>>"+num));
    
    //result :
    //      main>>>>>4
    //      main>>>>>6
    //      main>>>>>5
    //      ForkJoinPool.commonPool-worker-9>>>>>1
    //      ForkJoinPool.commonPool-worker-9>>>>>3
    //      main>>>>>2
    
    

    3.2 中间操作

    中间操作分为这三类:

    1. 映射


      映射.png
    2. 筛选与切片


      筛选与切片.png
    3. 排序


      排序.png

    3.2.1 映射

    map——接收 Lambda , 将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

        //要求map(Function)中的Function 是传入T 返回R型的 
        <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    

    flatMap——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流.

        //要求flatMap(Function) 中的Function 是传入T 返回Stream<R>的
        <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    

    接下来进行具体的操作

            //map 操作
            List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
            
            strList.stream()
                   .map(String::toUpperCase)    // 使用map 将每个小写字母变为大写
                    .forEach(System.out::println);;
    
    // result:: AAA,BBB,CCC,DDD,EEE
    

    下面我们新建一个 filterCharacter 函数,将一个字符串中的每个字符加入到List中.

        public static Stream<Character> filterCharacter(String str){
            List<Character> list = new ArrayList<>();
            for (Character ch : str.toCharArray()) {
                list.add(ch);
            }
            return list.stream();  //注意返回的是 Stream<Stream<Character>> 
        }
    
            Stream<Stream<Character>> stream2 = strList.stream()
                   .map(TestStreamAPI1::filterCharacter); // map 使用该函数返回Stream<Stream<Character>>  ! ,外面多包了一层Stream
            
           //因此foreach 遍历 也需要两层
            stream2.forEach((sm) -> {
                sm.forEach(System.out::println);
            });
    

    然后使用flatMap 来操作

            Stream<Character> stream3 = strList.stream()
                   .flatMap(TestStreamAPI1::filterCharacter); //返回的就是我们Function 中的返回值
    
            stream3.forEach(System.out::println);
    

    由上面我们可以得出map会将所有的返回值再重新放入一个Stream 中,所以当我的函数返回值是Stream<R>时,由map得到的返回值是Stream<Stream<R>>.


    map()图示.png

    但是flatMap 不同,由于他的function要求返回的是Stream<R>,他会将所有的R 全部拿出来,在放入到一个Steam 之中.


    faltMap.png

    3.2.2 筛选与切片

    filter——接受Predicate 断言型 lambda,从流中排除某些元素

        // 断言true 则保留, false 则剔除
        Stream<T> filter(Predicate<? super T> predicate);
    

    distinct——通过流所生成元素的hashCOde()和equals()去除重复元素

    //一定要判断为同一对象才能去除
    Stream<T> distinct();
    

    limit —— 截断流,使元素不超过给定的数量
    skip —— 返回一个扔掉了前n个元素的流, 若流中元素不足n个,则返回空流,与limit 互补.

        @Test
        public void test1(){
    
            List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9,1,1,1,1);
    
            Stream<Integer> limit = integers.stream()
                    .filter(x -> x < 5)  //筛选出小于5的
                    .distinct()  //去重,会根据hashcode去重
                    .limit(3);   //只返回前三条
            limit.forEach(System.out::println);
        }
    
    //result: 1,2,3
    
        @Test
        public void test2(){
    
            List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9,1,1,1,1);
    
            Stream<Integer> limit = integers.stream()
                    .filter(x -> x < 5)
                    .distinct()
                    .skip(3);  // 跳过前三条
            limit.forEach(System.out::println);
    
        }
    //result : 4
    

    3.2.3 排序

    sorted —— 产生一个新流,按照自然排序.
    sorted(Comparator comp) —— 产生一个新流,按照我们自定义的比较器排序

        @Test
        public void test3(){
    
            List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9,1,1,1,1);
    
            Stream<Integer> sorted = integers.stream()
                    .sorted();
            sorted.forEach(x -> System.out.print(x+ ","));
        }
    //reult : 1,1,1,1,1,2,3,4,5,6,7,8,9,
    
    
        //定制排序,倒叙排数字
        @Test
        public void test4(){
    
            List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9,1,1,1,1);
    
            Stream<Integer> sorted = integers.stream()
                    .sorted((x,y) -> -Integer.compare(x,y));  //注意 Integer.compare前面的 - 号
            sorted.forEach(x -> System.out.print(x+ ","));
        }
    //reult : 9,8,7,6,5,4,3,2,1,1,1,1,1,
    

    3.3 终止操作

    Stream的终止操作会从流中生成结果,其结果不会再是任何流,而是List,Integer,甚至是void 等.主要分为一下三类:

    1. 查找与匹配


      查找与匹配1.png
      查找与匹配2.png
    2. 归约


      归约.png
    3. 收集


      收集.png

    3.3.1 查找与匹配操作

    其中流使用了终止操作之后就不能再操作流了.

        @Test
        public void test8() {
            List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6);
            boolean bl0 = integers.stream()
                    .allMatch(x -> x > 5);
    
            System.out.println("allMatch——检查是否匹配所有元素: " + bl0);
    
            boolean bl1 = integers.stream()
                    .anyMatch(x -> x > 5);
    
            System.out.println("anyMatch——检查是否至少匹配一个元素: " + bl1);
    
            boolean bl2 = integers.stream()
                    .noneMatch(x -> x > 5);
    
            System.out.println("noneMatch——检查是否没有匹配的元素: " + bl2);
    
            Optional<Integer> first = integers.stream()
                    .filter(x -> x > 5)
                    .findFirst();  //findFirst 返回Optional 对象, 因为值有可能为空
    
            System.out.println("findFirst——返回第一个元素:" + first.get());
    
            long count = integers.stream()
                    .filter(x -> x > 5)
                    .count();
    
            System.out.println("count——返回流中元素的总个数:" + count);
    
    
            Optional<Integer> max = integers.stream()
                    .max(Integer::compareTo); //制定compare方式 返回option
    
            System.out.println("max——返回流中最大值:" + max);
    
            Optional<Integer> min = integers.stream()
                    .min(Integer::compareTo);
    
            System.out.println("min——返回流中最小值: " + min);
    
        }
    //    allMatch——检查是否匹配所有元素: false
    //    anyMatch——检查是否至少匹配一个元素: true
    //    noneMatch——检查是否没有匹配的元素: false
    //    findFirst——返回第一个元素:6
    //    count——返回流中元素的总个数:1
    //    max——返回流中最大值:Optional[6]
    //    min——返回流中最小值: Optional[1]
    

    3.3.2 归约操作

    归约
    reduce(T identity, BinaryOperator) / reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。

        @Test
        public void test1(){
            List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
            
            Integer sum = list.stream()
                .reduce(0, (x, y) -> x + y); // 这里是将初始值设置为0 , x+y的得到的值赋值给x , y是每次从List中遍历出来的值.
            
            System.out.println(sum);
        }
    //result: 55
    
        //reduce -map 相结合实现 计数功能
        @Test
        public void test_ReduceMap(){
            //查询大于数组中大于6的值得个数
            List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
            Optional<Integer> sum = list.stream()
                    .map((i) -> {
                        if(i > 6)
                            return 1;
                        else
                            return 0;
                    }).reduce(Integer::sum);
    
            System.out.println(sum.get());
        }
    

    3.3.3 收集

    collect——将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法,通常用于转化为List Set等,也可以用来做分组等操作.

       //在Test 中创建一个苹果list, 有name, weight,price 三个属性
        List<Apple> apples = Arrays.asList(
                new Apple("红苹果", 3, 33.33),
                new Apple("黄苹果", 4, 44.44),
                new Apple("绿苹果", 5, 55.55),
                new Apple("毒苹果", 6, 66.66)
        );
    
        /**
         * 转化为集合类操作
         */
        @Test
        public void test5(){
            List<String> list = apples.stream()
                    .map(Apple::getName)
                    .collect(Collectors.toList());
    
            list.forEach(System.out::println);
    
            System.out.println("----------------------------------");
    
            Set<String> set = apples.stream()
                    .map(Apple::getName)
                    .collect(Collectors.toSet());
    
            set.forEach(System.out::println);
    
            System.out.println("----------------------------------");
    
            HashSet<String> hs = apples.stream()
                    .map(Apple::getName)
                    .collect(Collectors.toCollection(HashSet::new));  //可以指定为任意Collection 类
    
            hs.forEach(System.out::println);
        }
    //result:
    //        红苹果
    //        黄苹果
    //        绿苹果
    //        毒苹果
    //        ----------------------------------
    //        毒苹果
    //        黄苹果
    //        红苹果
    //        绿苹果
    //        ----------------------------------
    //        毒苹果
    //        黄苹果
    //        红苹果
    //        绿苹果
    
    

    gourpingBy方法对苹果使用苹果名字进行分组

        @Test
        public void testGroupBy(){
            Map<String, List<Apple>> map = apples.stream()
                    .collect(Collectors.groupingBy(Apple::getName));
    
            System.out.println(map);
        }
    
    //result:  {毒苹果=[Apple(name=毒苹果, weight=6, price=66.66)], 黄苹果=[Apple(name=黄苹果, weight=4, price=44.44)], 绿苹果=[Apple(name=绿苹果, weight=5, price=55.55)], 红苹果=[Apple(name=红苹果, weight=3, price=33.33)]}
    

    甚至可以多级分组,因为gourpingBy 方法中可以在传递一个Collector对象,只要你愿意可以无限分组

    Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                                              Collector<? super T, A, D> downstream)
    
        //多级分组
        @Test
        public void test6(){
            Map<String, Map<String, List<Apple>>> map = apples.stream()
                    .collect(Collectors.groupingBy(Apple::getName, Collectors.groupingBy((e) -> {
                        if(e.getPrice() >= 60)
                            return "高档苹果";
                        else if(e.getPrice() >= 35)
                            return "中档苹果";
                        else
                            return "低档苹果";
                    })));
    
            System.out.println(map);
        }
    //result: {毒苹果={高档苹果=[Apple(name=毒苹果, weight=6, price=66.66)]}, 黄苹果={中档苹果=[Apple(name=黄苹果, weight=4, price=44.44)]}, 绿苹果={中档苹果=[Apple(name=绿苹果, weight=5, price=55.55)]}, 红苹果={低档苹果=[Apple(name=红苹果, weight=3, price=33.33)]}}
    

    还可以使用Collectors.partitioningBy 方法来进行分区

        @Test
        public void test7(){
            Map<Boolean, List<Apple>> map = apples.stream()
                    .collect(Collectors.partitioningBy((apple) -> apple.getPrice() >= 50)); //根据苹果价格是否大于50 来分区
    
            System.out.println(map);
        }
    //result :  {false=[Apple(name=红苹果, weight=3, price=33.33), Apple(name=黄苹果, weight=4, price=44.44)], true=[Apple(name=绿苹果, weight=5, price=55.55), Apple(name=毒苹果, weight=6, price=66.66)]}
    

    4 总结

    java8 中的Stream 搭配四大内置核心函数式接口使用起来,基本上取代了很多我们以前需要自己手动实现的方法,使得代码逻辑清楚,语言表达能力更强,通过Stream 也体会到了lambda 表达式这种函数的方便之处, 更重要的是他可以帮助我们处理以前很多在sql中完成的操作,大大减小了数据库压力,数据库所需要做的只是拿出数据,我们可以将大量的逻辑放在语言中实现,其中更是有parallelStream()这种底层采用fork/join的并行流,大大增加了数据的处理速度.通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序.

    相关文章

      网友评论

          本文标题:java8-stream API

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