美文网首页
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

    了解Stream ​ Java8中有两个最为重要的改变,一个是Lambda表达式,另一个就是Stream AP...

  • java8-stream API

    1. 前言 Lambda 是JAVA8中最为重要的改变之一, 而Stream API(java.util.stre...

  • Java 8 Stream 知识分享

    本文原文[https://getaoning.com/archives/java8-stream/] 综述 Jav...

  • Java8-Stream

    Java的传统迭代方式没什么不好,但是与Stream相比,它很难被并行运算。 将一个集合 调用stream方法改成...

  • java8-stream流

    首先,Stream流有一些特性:1 .Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作...

  • Java8-Stream流式计算

    什么是Stream流式计算 大数据:存储 + 计算 集合、MySQL 本质就是存储东西的; 计算都应该交给流来操作...

  • java8-Stream集合操作快速上手

    目录 Stream简介 为什么要使用Stream 实例数据源 Filter Map FlatMap Reduce ...

  • (2)智能合约开发:API 参考大纲

    Chain API - 链API Database API - 数据库API Action API - 操作API...

  • java8-stream 4 聊聊串行与并行

    在进行接下来的话题前,我们来谈谈并行,串行的问题。 我们本节要弄明白的问题是,串行和并行,执行的流程是什么样的。不...

  • 06JavaScript-同步、异步、回调、Promise、as

    同步API, 异步API 同步API:只有当前API执行完成后,才能继续执行下一个API 异步API:当前API的...

网友评论

      本文标题:java8-stream API

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