java8-stream API

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 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容

  • 了解Stream ​ Java8中有两个最为重要的改变,一个是Lambda表达式,另一个就是Stream AP...
    龙历旗阅读 3,301评论 3 4
  • Stream API是Java8中处理集合的关键组件,提供了各种丰富的函数式操作。 Stream的创建 任何集合都...
    fengshunli阅读 500评论 0 2
  • 1. 概述 Java 8 引入的一个重要的特性无疑是 Stream API。Stream 翻译过来是“流”,突然想...
    BUG生产者阅读 312评论 0 0
  • 福利 现在关注微信公众号:码农小胖哥, 发送关键字【抽奖】进行抽奖,可有机会获取实体编程书籍。活动只剩下2天了抓紧...
    码农小胖哥阅读 202评论 0 0
  • 先看一段代码 Stream API的历史 在java8引入 受益于lambda表达式 lambda表达式 接口常被...
    ThomasYoungK阅读 625评论 0 0