java零基础教学篇之Stream流超级详细

Posted bug菌¹

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java零基础教学篇之Stream流超级详细相关的知识,希望对你有一定的参考价值。

👨‍🎓作者:bug菌 

✏️博客:CSDN掘金

💌公众号:猿圈奇妙屋

🚫特别声明:原创不易,转载请附上原文出处链接和本文声明,谢谢配合。

🙏版权声明:文章里可能部分文字或者图片来源于互联网或者百度百科,如有侵权请联系bug菌处理。

【开发云】年年都是折扣价,不用四处薅羊毛

一、前言🔥

环境:springboot2.3.1.RELEASE + jdk1.8

二、正文🔥

        谈到Stream,你们第一眼想到了啥?I/O Stream?其实我在接触它之前看到第一眼也以为是io流那一块的东西,不是的哈。没有谁规定io就一定是io流,在java8中,由于Lambda的诞生与崛起,链式编程跑上热潮,从而也引入了一个对Stream的新概念,主要作用于解决当前集合类库的一些弊端。

1️⃣引言

        传统集合中,可以说几乎所有的集合(Collection 或 Map等)都支持直接或间接的遍历操作。而我们对集合的操作除了必要的插入、删除、获取外,操作最多就是对集合进行遍历了。那么你有没有想过,循环遍历是否有啥弊端?

        首先问大家个问题,为什么要使用循环?因为要遍历。但循环是遍历的唯一操作方式吗?遍历是对每一个元素逐一进行处理,而并不是从第一个到最后一个顺序处理的循环。总而言之就是前者是目的,而后者是方式。

        再问大家一个问题,如果对集合中的元素进行有条件的筛选,你们打算怎么做?

        我具体给大家设计个场景,比如一个集合stus中存储了全班同学的姓名,现在要求你把姓罗且两个字名称的同学名单筛选出来,可能在java8之前,大部分人可能会这么干!

1、传统for循环法:

具体演示代码如下:

    @Test
    public void test() 

        //存放全班同学姓名集合
        List<String> stus = new ArrayList<>();
        stus.add("罗志祥");
        stus.add("罗成");
        stus.add("黄忠");
        stus.add("马超");
        stus.add("后羿");

        //存储指定数据集合
        List<String> luoList = new ArrayList<>();
        for (String name : stus) 
            if (name.length() == 2 && name.startsWith("罗")) 
                luoList.add(name);
            
        

        //打印姓罗且名字两个字的同学姓名
        for (String name : luoList) 
            System.out.print(name + " ");
        
    

代码运行截图:

很明显输出答案就唯有【罗成】符合题目要求,对吧。那看完上述这段代码,我们再来解析一下,它共有3个循环,分别解释如下:

  1. 循环1负责存放全班同学姓名。
  2. 循环2负责筛选出含有符合题意的姓名。
  3. 循环3打印出符合符合题意的姓名。

        当我们需要对集合进行操作时,对集合进行遍历是避免不了的,而且一次还可能不够,循环再循环...那么是否有更优雅的实现方式呢?那么就用到今天我给大家提到的java8新特性之一Stream流。

2、Stream流写法

        下面请大家看看,我是如何借助java8的Stream API来实现上述需求的,什么才叫简洁优雅。

具体演示代码如下:

    @Test
    public void test1() 

        //存放全班同学姓名集合
        List<String> stus = new ArrayList<>();
        stus.add("罗志祥");
        stus.add("罗成");
        stus.add("黄忠");
        stus.add("马超");
        stus.add("后羿");

        //筛选+输出打印一步到位
        stus.stream()
                .filter(s -> s.startsWith("罗"))
                .filter(s -> s.length() == 2)
                .forEach(System.out::print);
    

代码运行截图:

         仔细阅读代码,从获取流、过滤姓、过滤姓名长度、逐一打印。代码全程中并没有体现使用线性循环或者其他任何算法进行遍历,而我们真正要做的事内容被更好的在代码中进行体现。怎么样,感受到了Stream流的优雅了么,写法那是非常的简洁啊,绝绝子。

2️⃣流式思想概述

        整体来看,流就类似工厂车间的“流水线”,当我们要对一个集合进行多步操作时,考虑到性能及整体便捷性,我们首要应该是拼好一个“流水线”模型,一步一步执行下去,比如:

         如上这张图中展示了Stream流中的过滤、映射、跳过、计数多步操作后得出答案,这是对集合的一种处理方案,反过来看,方案就是“流水线”模型。

        上图中每个框都是一个“流”,调用专门的方法,可以从一个流模型转换为另一个“流”模型。最终的3就是答案。

注意:Stream流就是一个集合元素的函数模型,它不是集合,也不是数据结构,其本身并不存储任何元素,请大家不要误解了。

        Stream流是一个来自数据源的元素队列。其中数据源可以是一个集合或者数组等。和以前的Collection集合操作不同,Stream流它还有两个基础特征,具体如下:

  • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluentstyle)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式来处理, 显示的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法。

        当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)—> 数据转换—>执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

三、获取流🔥

1️⃣根据Collection获取流

        首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。

如下我分别举例来获取Stream流,具体如下:

    @Test
    public void test2()

        List<String> arrayList = new ArrayList<>();
        //获取流
        Stream<String> stream1 = arrayList.stream();

        Set<String> hashSet = new HashSet<>();
        //获取流
        Stream<String> stream2 = hashSet.stream();

        Vector<String> vector = new Vector<>();
        //获取流
        Stream<String> stream = vector.stream();

    

2️⃣根据Map获取流

        java.util.Map 接口不是 Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:
具体演示如下:

    @Test
    public void test3() 
        Map<String, String> hashMap = new HashMap<>();
        //获取流有以下几种
        Stream<String> stream1 = hashMap.keySet().stream();
        Stream<String> stream2 = hashMap.values().stream();
        Stream<Map.Entry<String, String>> stream3 = hashMap.entrySet().stream();
    

3️⃣根据数组获取流

        如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法of(),使用很简单:

具体演示代码如下:

    /**
     * 根据数组获取流
     */
    @Test
    public void test4() 

        String [] arr = new String[10];
        Stream<String> stream = Stream.of(arr);
    

备注: of 方法的参数其实是一个可变参数,所以支持数组。 

四、常用方法🔥

        流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

  • 延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
  • 终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。本小节中,终结方法包括 count 和 forEach 方法。

1️⃣逐一处理:forEach

        虽然方法名字叫 forEach ,但是与for循环中的[ for-each ]昵称不同,方法签名如下:

void forEach(Consumer<? super T> action);

        该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。

基本使用:

    /**
     * forEach 使用
     */
    @Test
    public void test5() 
        Stream<String> stream = Stream.of("a", "b", "c", "d");
        //遍历打印
        stream.forEach(name ->System.out.print(name));
    

2️⃣过滤:filter

        可以通过 filter() 方法将一个流转换成另一个子集流。方法签名如下:

Stream<T> filter(Predicate<? super T> predicate);

        该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

基本使用:

        Stream流中的 filter 方法基本使用的代码如:

    /**
     * filter 使用
     */
    @Test
    public void test6() 
        Stream<String> stream = Stream.of("a", "b", "c", "d");
        //使用filter过滤得到一个新Stream
        Stream<String> result = stream.filter(name -> name.startsWith("a"));
    

3️⃣映射:map

        如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名如下:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

        该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

 基本使用:

        Stream流中的 map 方法基本使用的代码如:

    /**
     * map使用
     */
    @Test
    public void test7() 
        Stream<String> stream = Stream.of("1", "2", "3", "4");
        //使用map得到一个新Stream
        Stream<Integer> result = stream.map(name -> Integer.parseInt(name));
    

        这段代码中, map 方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为 Integer 类对象)。

4️⃣统计个数:count

        正如旧集合 Collection 当中的 size 方法一样,流提供 count() 方法来数一数其中的元素个数,方法签名如下:

long count();

基本使用:

        该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。

    /**
     * count使用
     */
    @Test
    public void test8() 
        Stream<String> stream = Stream.of("1", "2", "3", "4");
        //使用map得到一个新Stream
        Stream<Integer> result = stream.map(name -> Integer.parseInt(name));
        //获取个数
        System.out.println(result.count());
    

5️⃣取用前几个:limit

        limit 方法可以对流进行截取,只取用前n个。方法签名如下:

Stream<T> limit(long maxSize);

        参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用:

基本使用:

    /**
     * limit使用
     */
    @Test
    public void test9() 
        Stream<String> stream = Stream.of("1", "2", "3", "4");
        //使用map得到一个新Stream
        Stream<Integer> result = stream.map(name -> Integer.parseInt(name));
        //limit截取
        Stream<Integer> limit = result.limit(2);
        //获取个数
        System.out.println(limit.count());
    

6️⃣跳过前几个:skip

        如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流。方法签名如下:

Stream<T> skip(long n);

        如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。

基本使用:

    /**
     * skip使用
     */
    @Test
    public void test10() 
        Stream<String> stream = Stream.of("1", "2", "3", "4","5");
        //使用map得到一个新Stream
        Stream<Integer> result = stream.map(name -> Integer.parseInt(name));
        //limit截取
        Stream<Integer> limit = result.skip(2);
        //获取个数
        System.out.println(limit.count()); //3
    

7️⃣组合:concat

        如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat()。方法签名如下:

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

注意:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。
基本使用:

    /**
     * concat使用
     */
    @Test
    public void test11() 
        Stream<String> stream1 = Stream.of("1", "2");
        Stream<String> stream2 = Stream.of("a", "b");
        //concat合并两个流
        Stream<String> concatStream = Stream.concat(stream1, stream2);

        //进行元素打印
        concatStream.forEach(str ->System.out.print(str)); //12ab
    

实际运行结果截图:

        如果你还想学习更多有关Stream流的知识点,你也可以看看我写的这期内容《Java8 StreamAPI的详细用法》,非常的详细,希望对有需要的同学有帮助。

 ... ... 

        好啦,以上就是本期的所有内容啦。如果对你有所帮助,还请不要忘记给bug菌[三连支持]哟。如果想获得更多的学习资源或者想和更多的技术爱好者一起交流,可以关注我的公众号『猿圈奇妙屋』,后台回复关键词领取学习资料、大厂面经、面试模板等海量资源,就等你来拿。

五、文末🔥

        如果你还想要学习更多,小伙伴们大可关注bug菌专门为你们创建的专栏《java实战教学》,都是我一手打下的江山,持续更新中,希望能帮助到更多小伙伴们。

【开发云】年年都是折扣价,不用四处薅羊毛

        我是bug菌,一名想走👣出大山改变命运的程序猿。接下来的路还很长,都等待着我们去突破、去挑战。来吧,小伙伴们,我们一起加油!未来皆可期,fighting!

        最后送大家两句我很喜欢的话,与诸君共勉!


☘️做你想做的人,没有时间限制,只要愿意,什么时候都可以start。

🍀你能从现在开始改变,也可以一成不变,这件事,没有规矩可言,你可以活出最精彩的自己。


​​​​​

💌如果文章对您有所帮助,就请留下您的吧!(#^.^#);

💝如果喜欢bug菌分享的文章,就请给bug菌点个关注吧!(๑′ᴗ‵๑)づ╭❤~;

💗如果对文章有任何疑问,还请文末留言或者加群吧【QQ交流群:708072830】;

💞鉴于个人经验有限,所有观点及技术研点,如有异议,请直接回复参与讨论(请勿发表攻击言论,谢谢);

💕版权声明:原创不易,转载请附上原文出处链接和本文声明,版权所有,盗版必究!!!谢谢。

以上是关于java零基础教学篇之Stream流超级详细的主要内容,如果未能解决你的问题,请参考以下文章

Ubuntu零基础教学-GParted磁盘分区工具使用|超级详细,手把手教学

Ubuntu零基础教学-VMware删除快照教程|超级详细

Ubuntu零基础教学-Ubuntu20.04安装Gradle|超级详细,建议收藏!

Ubuntu零基础教学-Redis介绍及安装 | 超级详细,建议收藏

Ubuntu零基础教学-史上最详Ubuntu20.04安装教程,超级详细,强烈建议收藏!

Ubuntu零基础教学-史上最详Ubuntu20.04安装教程,超级详细,强烈建议收藏!