当前位置 博文首页 > 方志朋的博客:玩转Java8中的 Stream 之从零认识 Stream

    方志朋的博客:玩转Java8中的 Stream 之从零认识 Stream

    作者:[db:作者] 时间:2021-08-04 15:06

    点击上方“方志朋”,选择“设为星标”

    回复”666“获取新整理的面试文章

    作者:litesky

    www.jianshu.com/p/11c925cdba50

    相信Java8的Stream 大家都已听说过了,但是可能大家不会用或者用的不熟,文章将带大家从零开始使用,循序渐进,带你走向Stream的巅峰。

    操作符

    什么是操作符呢?操作符就是对数据进行的一种处理工作,一道加工程序;就好像工厂的工人对流水线上的产品进行一道加工程序一样。

    Stream的操作符大体上分为两种:中间操作符和终止操作符

    中间操作符

    对于数据流来说,中间操作符在执行制定处理程序后,数据流依然可以传递给下一级的操作符。

    中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作):

    • map(mapToInt,mapToLong,mapToDouble) 转换操作符,把比如A->B,这里默认提供了转int,long,double的操作符。

    • flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了3个数据,这里默认提供了拍平成int,long,double的操作符。

    • limit 限流操作,比如数据流中有10个 我只要出前3个就可以使用。

    • distint 去重操作,对重复元素去重,底层使用了equals方法。

    • filter 过滤操作,把不想要的数据过滤。

    • peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。

    • skip 跳过操作,跳过某些元素。

    • sorted(unordered) 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。

    终止操作符

    数据经过中间加工操作,就轮到终止操作符上场了;终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。

    • collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。

    • count 统计操作,统计最终的数据个数。

    • findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为Optional。

    • noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。

    • min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。

    • reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。

    • forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。

    • toArray 数组操作,将数据流的元素转换成数组。

    这里只介绍了Stream,并没有涉及到IntStream、LongStream、DoubleStream,这三个流实现了一些特有的操作符,我将在后续文章中介绍到。Java知音公众号内回复“面试题聚合”,送你一份各大公司面试汇总宝典。

    说了这么多,只介绍这些操作符还远远不够;俗话说,实践出真知。那么,Let‘s go。

    代码演练

    Stream 的一系列操作必须要使用终止操作,否者整个数据流是不会流动起来的,即处理操作不会执行。

    • map,可以看到 map 操作符要求输入一个Function的函数是接口实例,功能是将T类型转换成R类型的。

    map操作将原来的单词 转换成了每个单的长度,利用了String自身的length()方法,该方法返回类型为int。这里我直接使用了lambda表达式,关于lambda表达式 还请读者们自行了解吧。

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ????????Stream.of("apple","banana","orange","waltermaleon","grape")
    ????????????????.map(e->e.length())?//转成单词的长度?int
    ????????????????.forEach(e->System.out.println(e));?//输出
    ????}
    }
    

    当然也可以这样,这里使用了成员函数引用,为了便于读者们理解,后续的例子中将使用lambda表达式而非函数引用。

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ?????????Stream.of("apple","banana","orange","waltermaleon","grape")
    ????????????????.map(String::length)?//转成单词的长度?int
    ????????????????.forEach(System.out::println);
    ????}
    }
    

    结果如图:

    • mapToInt 将数据流中得元素转成Int,这限定了转换的类型Int,最终产生的流为IntStream,及结果只能转化成int。

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ?????????Stream.of("apple",?"banana",?"orange",?"waltermaleon",?"grape")
    ????????????????.mapToInt(e?->?e.length())?//转成int
    ????????????????.forEach(e?->?System.out.println(e));
    ????}
    }
    

    mapToInt如图:

    • mapToLong、mapToDouble 与mapToInt 类似

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ?????????Stream.of("apple",?"banana",?"orange",?"waltermaleon",?"grape")
    ????????????????.mapToLong(e?->?e.length())?//转成long?,本质上是int?但是存在类型自动转换
    ????????????????.forEach(e?->?System.out.println(e));
    ????}
    }
    

    mapToLong 如图:

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ?????????Stream.of("apple",?"banana",?"orange",?"waltermaleon",?"grape")
    ????????????????.mapToDouble(e?->?e.length())?//转成Double?,自动类型转换成Double
    ????????????????.forEach(e?->?System.out.println(e));
    ????}
    }
    

    mapToDouble如图:

    • flatmap 作用就是将元素拍平拍扁 ,将拍扁的元素重新组成Stream,并将这些Stream 串行合并成一条Stream

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ????????Stream.of("a-b-c-d","e-f-i-g-h")
    ????????????????.flatMap(e->Stream.of(e.split("-")))
    ????????????????.forEach(e->System.out.println(e));
    
    ????}
    }
    

    flatmap 如图:

    • flatmapToInt、flatmapToLong、flatmapToDouble 跟flatMap 都类似的,只是类型被限定了,这里就不在举例子了。

    • limit 限制元素的个数,只需传入 long 类型 表示限制的最大数

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ????????Stream.of(1,2,3,4,5,6)
    ????????????????.limit(3)?//限制三个
    ????????????????.forEach(e->System.out.println(e));?//将输出?前三个?1,2,3
    ????}
    }
    

    limit如图:

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    
    ????????Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
    ????????????????.distinct()?//去重
    ????????????????.forEach(e->System.out.println(e));
    
    ????}
    }
    

    distinct 如图:

    • filter 对某些元素进行过滤,不符合筛选条件的将无法进入流的下游

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ????????Stream.of(1,2,3,1,2,5,6,7,8,0,0,1,2,3,1)
    ????????????????.filter(e->e>=5)?//过滤小于5的
    ????????????????.forEach(e->System.out.println(e));
    ????}
    }
    

    filter 如图:

    • peek 挑选 ,将元素挑选出来,可以理解为提前消费

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    
    ????????User?w?=?new?User("w",10);
    ????????User?x?=?new?User("x",11);
    ????????User?y?=?new?User("y",12);
    
    ????????Stream.of(w,x,y)
    ????????????????.peek(e->{e.setName(e.getAge()+e.getName());})?//重新设置名字?变成?年龄+名字
    ????????????????.forEach(e->System.out.println(e.toString()));
    
    ????}
    
    ????static?class?User?{
    
    ????????private?String?name;
    
    ????????private?int?age;
    
    ????????public?User(String?name,?int?age)?{
    ????????????this.name?=?name;
    ????????????this.age?=?age;
    ????????}
    
    ????????public?String?getName()?{
    ????????????return?name;
    ????????}
    
    ????????public?void?setName(String?name)?{
    ????????????this.name?=?name;
    ????????}
    
    ????????public?int?getAge()?{
    ????????????return?age;
    ????????}
    
    ????????public?void?setAge(int?age)?{
    ????????????this.age?=?age;
    ????????}
    
    ????????@Override
    ????????public?String?toString()?{
    ????????????return?"User{"?+
    ????????????????????"name='"?+?name?+?'\''?+
    ????????????????????",?age="?+?age?+
    ????????????????????'}';
    ????????}
    ????}
    
    }
    

    peek 如图:

    • skip 跳过 元素

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ????????Stream.of(1,2,3,4,5,6,7,8,9)
    ????????????????.skip(4)?//跳过前四个
    ????????????????.forEach(e->System.out.println(e));?//输出的结果应该只有5,6,7,8,9
    ????}
    }
    

    skip 如图:

    • sorted 排序 底层依赖Comparable 实现,也可以提供自定义比较器

    这里Integer 实现了比较器

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ????????Stream.of(2,1,3,6,4,9,6,8,0)
    ????????????????.sorted()
    ????????????????.forEach(e->System.out.println(e));
    ????}
    }
    

    sorted 默认比较器如图:

    这里使用自定义比较,当然User 可以实现Comparable 接口

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    
    ????????User?x?=?new?User("x",11);
    ????????User?y?=?new?User("y",12);
    ????????User?w?=?new?User("w",10);
    
    ????????Stream.of(w,x,y)
    ????????????????.sorted((e1,e2)->e1.age>e2.age?1:e1.age==e2.age?0:-1)
    ????????????????.forEach(e->System.out.println(e.toString()));
    
    ????}
    
    ????static?class?User?{
    
    ????????private?String?name;
    
    ????????private?int?age;
    
    ????????public?User(String?name,?int?age)?{
    ????????????this.name?=?name;
    ????????????this.age?=?age;
    ????????}
    
    ????????public?String?getName()?{
    ????????????return?name;
    ????????}
    
    ????????public?void?setName(String?name)?{
    ????????????this.name?=?name;
    ????????}
    
    ????????public?int?getAge()?{
    ????????????return?age;
    ????????}
    
    ????????public?void?setAge(int?age)?{
    ????????????this.age?=?age;
    ????????}
    
    ????????@Override
    ????????public?String?toString()?{
    ????????????return?"User{"?+
    ????????????????????"name='"?+?name?+?'\''?+
    ????????????????????",?age="?+?age?+
    ????????????????????'}';
    ????????}
    ????}
    
    }
    

    如图:

    • collect 收集,使用系统提供的收集器可以将最终的数据流收集到List,Set,Map等容器中。

    这里我使用collect 将元素收集到一个set中

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    ????????Stream.of("apple",?"banana",?"orange",?"waltermaleon",?"grape")
    ????????????????.collect(Collectors.toSet())?//set?容器
    ????????????????.forEach(e?->?System.out.println(e));
    ????}
    }
    

    咦?,不是说终止操作符只能使用一次吗,为什么这里调用了forEach 呢?forEach不仅仅是是Stream 中得操作符还是各种集合中得一个语法糖,不信咋们试试。Java知音公众号内回复“面试题聚合”,送你一份各大公司面试汇总宝典。

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    
    ????????Set<String>?stringSet?=?Stream.of("apple",?"banana",?"orange",?"waltermaleon",?"grape")
    ????????????????.collect(Collectors.toSet());?//收集的结果就是set
    ????????stringSet.forEach(e->System.out.println(e));?set的语法糖forEach
    }
    

    结果如图:

    • count 统计数据流中的元素个数,返回的是long 类型

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    
    ????????long?count?=?Stream.of("apple",?"banana",?"orange",?"waltermaleon",?"grape")
    ????????????????.count();
    
    ????????System.out.println(count);
    ????}
    }
    

    count 如图:

    • findFirst 获取流中的第一个元素

    这里找到第一个元素 apple

    public?class?FindFirst?{
    
    ????public?static?void?main(String[]?args)?{
    ????????Optional<String>?stringOptional?=?Stream.of("apple",?"banana",?"orange",?"waltermaleon",?"grape")
    ????????????????.findFirst();
    ????????stringOptional.ifPresent(e->System.out.println(e));
    ????}
    }
    

    findFirst 结果如图:

    • findAny 获取流中任意一个元素

    public?class?FindAny?{
    
    ????public?static?void?main(String[]?args)?{
    ????????Optional<String>?stringOptional?=?Stream.of("apple",?"banana",?"orange",?"waltermaleon",?"grape")
    ????????????????.parallel()
    ????????????????.findAny();?//在并行流下每次返回的结果可能一样也可能不一样
    ????????stringOptional.ifPresent(e->System.out.println(e));
    ????}
    }
    

    findAny 在并行流下 使用结果:

    输出了orange

    输出了banana

    • noneMatch 数据流中得没有一个元素与条件匹配的

    这里 的作用是是判断数据流中 一个都没有与aa 相等元素 ,但是流中存在 aa ,所以最终结果应该是false

    public?class?NoneMatch?{
    
    ????public?static?void?main(String[]?args)?{
    ????????boolean?result?=?Stream.of("aa","bb","cc","aa")
    ????????????????.noneMatch(e->e.equals("aa"));
    ????????System.out.println(result);
    ????}
    }
    

    noneMatch 如图:

    • allMatch和anyMatch 一个是全匹配,一个是任意匹配 和noneMatch 类似,这里就不在举例了。

    • min 最小的一个,传入比较器,也可能没有(如果数据流为空)

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    
    ????????Optional<Integer>?integerOptional?=?Stream.of(0,9,8,4,5,6,-1)
    ????????????????.min((e1,e2)->e1.compareTo(e2));
    
    ????????integerOptional.ifPresent(e->System.out.println(e));
    
    ????}
    

    min如图:

    • max 元素中最大的,需要传入比较器,也可能没有(流为Empty时)

    public?class?Main?{
    
    ????public?static?void?main(String[]?args)?{
    
    ????????Optional<Integer>?integerOptional?=?Stream.of(0,9,8,4,5,6,-1)
    ????????????????.max((e1,e2)->e1.compareTo(e2));
    
    ????????integerOptional.ifPresent(e->System.out.println(e));
    
    ????}
    }
    

    max 如图:

    • reduce 是一个规约操作,所有的元素归约成一个,比如对所有元素求和,乘啊等。

    这里实现了一个加法,指定了初始化的值

    public?class?Main?{
    ????public?static?void?main(String[]?args)?{
    
    ????????int?sum?=?Stream.of(0,9,8,4,5,6,-1)
    ??????????????.reduce(0,(e1,e2)->e1+e2);
    ????????System.out.println(sum);
    ????}
    }
    

    reduce 如图:

    • forEach

    forEach 其实前就已经见过了,对每个数据遍历迭代

    • forEachOrdered 适用用于并行流的情况下进行迭代,能保证迭代的有序性

    这里通过并行的方式输出数字

    public?class?ForEachOrdered?{
    ????public?static?void?main(String[]?args)?{
    ????????Stream.of(0,2,6,5,4,9,8,-1)
    ????????????????.parallel()
    ????????????????.forEachOrdered(e->{
    ????????????????????System.out.println(Thread.currentThread().getName()+":?"+e);});
    ????}
    }
    

    forEachOrdered 如图:

    • toArray 转成数组,可以提供自定义数组生成器

    public?class?ToArray?{
    ????public?static?void?main(String[]?args)?{
    ????????Object[]?objects=Stream.of(0,2,6,5,4,9,8,-1)
    ????????????????.toArray();
    
    ????????for?(int?i?=?0;?i?<?objects.length;?i++)?{
    ????????????System.out.println(objects[i]);
    ????????}
    ????}
    }
    

    toArray 如图:

    总结

    Java8 Stream就带大家认识到这里,如果你能跟着我的文章把每一个例子都敲一遍,相信都能掌握这些操作符的初步用法。

    热门内容:四连问:API 接口应该如何设计?如何保证安全?如何签名?如何防重?
    再见,FastJson...技巧:MyBatis 中的trim标签,好用!
    求求你们了,别再写满屏的 try catch 了!!
    
    最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复?666?领取,更多内容陆续奉上。
    明天见(。・ω・。)ノ?
    
    cs