Java Stream 流?看这一篇就够了!

在这里插入图片描述
大家好,从我开始写博客也过去半年多了,c 站陪我走过了学习 Java 最艰苦的那段时光,也非常荣幸写的博客能得到这么多人的喜欢。
某一天当我开始学习分布式的时候突然想到这可能是补充 Java 知识拼图的最后几块部分了,为了将前面的知识更好的精进,我准备在完成分布式学习后将 Java 按照学习路线的顺序整理属于我和大家的博客上去,顺便写一下这一路的经验和感受,这篇博客算是一个先导吧,如果对我这个企划感兴趣的朋友可以关注我一下,我们重新起航,一起拥抱更好的未来!

文章目录

      • 01. 什么是 Stream 流?
        • <1> 初始 stream 流
        • <2> stream 的思想
      • 02. 得到 stream 流
      • 03. Stream 流的中间方法
      • 04. Stream 流的终端方法
      • 05. 收集方法 collect

01. 什么是 Stream 流?

💡 Stream 是 Java 8 中引入的一个新的抽象概念,它提供了一种更为便捷、高效的方式来处理 集合 数据。Stream 可以让开发者以声明式的方式对集合进行操作,而不需要显式地使用循环或者条件语句。

<1> 初始 stream 流

🍀 给一个 List<String> 链表,包含六个数字字符串,请找出以数字 1 开头并且长度为 3 的字符串,并且将其 输出 出来。

❓ 看到这道题目,第一想法肯定是借助两次遍历将符合这两种情况的字符串分别筛选成新的集合,再将其打印出来,来看一下代码实现。

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("123");
        list.add("12");
        list.add("15");
        list.add("266");
        list.add("171");
        list.add("13");

        List<String> list1 = new ArrayList<>();
        for (String s : list) {
            if (s.startsWith("1")) {
                list1.add(s);
            }
        }

        List<String> list2 = new ArrayList<>();
        for (String s : list1) {
            if (s.length() == 3) {
                list2.add(s);
            }
        }

        for (String s : list2) {
            System.out.println(s);
        }
    }
} 

那如果使用 Stream 流会有什么效果呢?

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("123");
        list.add("12");
        list.add("15");
        list.add("266");
        list.add("171");
        list.add("13");

        list.stream().filter(x -> x.startsWith("1")).filter(x -> x.length() == 3).forEach(x -> System.out.println(x));
    }
}

💡 大家看着这段代码来尝试体会一下 Stream 流处理集合数据的方式:

将集合中的数据想象成一些商品,将它们放到一个流水线上。

这时候老板突然提要求了:不是从 1 开始的我们不要。

那就加一道工序,把从 1 开始的全部筛出来。

此时你看老板眉头紧锁又在思考怎么筛,你抓紧把筛出来的部分 再放到流水线 上,静候老板的指示。
> 老板一拍脑门:长度不是 3 的也不要了,说完就走了,聪明的你立马就懂了,这是最后一道工序,于是筛选完最后一次之后就将其产出了(输出)。

<2> stream 的思想

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

💡 Stream 的操作可以链接在一起形成一个流水线。这个流水线包括一系列中间操作和一个终端操作

  • 所谓的中间操作就是筛选完了之后再将其放到流水线上。
  • 而终端操作就是直接结束流程,产出商品了。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 中间操作:过滤出偶数
Stream<Integer> evenNumbersStream = numbers.stream().filter(n -> n % 2 == 0);
// 终端操作:打印偶数
evenNumbersStream.forEach(System.out::println);

🍀 空口无凭没有说服力,直接来看一个中间操作的返回值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

🍀 再来看终端操作的返回值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一个返回的是一个新的 Stream 流,另一个则是 void

02. 得到 stream 流

💡 要想在流水线上操纵商品,得先将其放到流水线上,这里先来看 Stream 能操纵什么元素,以及提供了怎样的 API 来得到 Stream 流。

获取方式方法名说明
单列集合stream()Collection 提供的默认方法
双列集合无法直接使用,需要将其转为单列集合
数组Arrays.stream()使用 Arrays 工具类提供的 static 方法
一些零散数据Stream.of()Stream 接口中的 static 方法

🍀 单列集合可以通过父类 Collection 提供的接口默认实现方法来直接得到 stream

list.stream();

🍀 双列集合,例如 HashMap,这时候就只能将其转化为单列集合,回顾一下,对于 Map 可以使用 keySet() 方法得到 keySet 集合,使用 values 得到 value 的实现 Collection 接口的集合。

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();

        map.put("aaa", 1);
        map.put("bbb", 2);
        map.put("ccc", 3);

        map.keySet().stream().filter(x -> x.startsWith("a")).forEach(x -> System.out.println(x));
        System.out.println("---------");
        map.values().forEach(x -> System.out.println(x));
    }
}
aaa
---------
1
3
2

🍀 数组,使用 Arrays 工具类提供的 stream 方法,相信经常刷算法的同学一定不陌生,在处理数组的时候有时可以使用这种方法来简化代码。

public class Main {
    public static void main(String[] args) {
        int[] nums = new int[]{1, 2, 3, 5};

        Arrays.stream(nums).filter(x -> x >= 2).forEach(x -> System.out.println(x));
    }
}
2
3
5

🍀 Stream 还可以处理一些一些零散的数据,但注意这些数据必须是同种类型的。

public class Main {
    public static void main(String[] args) {
        Stream.of(1, 2, 3, 4, 5).filter(x -> x >= 3).forEach(x -> System.out.println(x));
        System.out.println("---------------");
        Stream.of("1", "2", "3", "4", "5").filter(x -> x.startsWith("1")).forEach(x -> System.out.println(x));
    }
}
3
4
5
---------------
1

💡 注意:第四种方法中的 可变参数 可以处理一些数组类型,但必须是 引用数组

  • 这是一位内泛型在编译时会被擦除,这意味着泛型信息在运行时是不可用的。
  • 如果你传入一个数组作为泛型参数,编译器在编译时不会保留数组的元素类型信息,而只是将其视为一个 Object 类型的数组。当你尝试输出这个泛型数组时,只能获取到数组的地址而不是内容。
public class Main {
    public static void main(String[] args) {
        int[] nums = new int[] {1, 2, 3};
        Stream.of(nums).forEach(x -> System.out.println(x));
    }
}
[I@404b9385

03. Stream 流的中间方法

💡 中间方法返回的是一个新的 Stream 流;修改 Stream 流中的数据对原本的集合或者数组没有任何影响。除了比较特殊的 map() 方法,其他其实都是对数据的增和删。

名称说明
filter()过滤
limit()获取前几个元素
skip()跳过前几个元素,获取后面的
distinct()元素去重,依赖 hashCodeequals 方法
concat()将两个流合并为一个流
map()转换流中的数据类型

🍀 增和删的方法的含义比较明确也没有什么特别注意的地方,这里就直接上一个完整的代码演示和输出案例。

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 1. 使用 filter 过滤出偶数
        numbers.stream().filter(x -> x % 2 == 0).forEach(x -> System.out.print(x + " "));
        System.out.println("\n----------");
        // 2. 使用 limit 得到前三个元素
        numbers.stream().limit(3).forEach(x -> System.out.print(x + " "));
        System.out.println("\n----------");
        // 3. 使用 skip 方法跳过前三个元素
        numbers.stream().skip(3).forEach(x -> System.out.print(x + " "));
    }
}
2 4 6 8 10 
----------
1 2 3 
----------
4 5 6 7 8 9 10 
public class Main {
    public static void main(String[] args) {
        // 对两个链表进行合并和去重操作
        List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> list2 = Arrays.asList(4, 5, 6, 7, 8);

        Stream<Integer> concat = Stream.concat(list1.stream(), list2.stream());
        concat.distinct().forEach(x -> System.out.print(x + " "));
    }
}
1 2 3 4 5 6 7 8

💡 在 Java 中,Stream API 的 distinct() 方法底层是通过哈希集合(HashSet LinkedHashMap)来实现去除重复元素的功能的。

  • 具体来说,当调用 distinct() 方法时,Stream 会使用一个哈希集合来保存已经遇到过的元素。当遍历流中的元素时,每遇到一个新元素,就会将其添加到哈希集合中。如果集合中已经存在相同的元素,则不会重复添加。最终,返回的流中只包含不重复的元素。
  • 这里再啰嗦几句关于 equalshashCode 的重写问题,在默认情况下,也就是 Object 类提供的 default 方法,hashCode 方法默认是映射内存地址,equals 方法默认比较的也是内存地址。
    • 比如说我们想要实现一个 不允许存放相同元素的集合,那肯定是优先去比较 hashCode,而哈希映射得到的内容是有限的,如果碰到恰好相同的情况就会出现哈希冲突
    • 这时候再去调用 equals 方法去比较,一比较,好家伙,不一样,那就放进去吧,但此时可以看出来比较的是内存地址,两个对象的内存地址肯定是不相同的,此时做的就是无效的比较。
    • 那之重写 equals() 方法可行吗?因为在存入时候优先比较的是 hash 码,逻辑上的相等 equals 不等于 hash 相等,这时候就会出现问题。
    • 那只重写 hashCode() 呢?那这就和上面的哈希冲突情况相同了,哈希值相同的时候比较却发现不同(比较的是内存地址),这时候也会存入相同的元素。
      • 总结一下,其实重写两个方法就是对去重的两个阶段的逻辑达到统一,这样才不会出现某一阶段筛选漏掉的情况。

🍀 map 方法它接受一个 函数 作为参数,该函数会被应用到流中的每个元素上,从而将原始流中的元素映射成新的元素,最终返回一个包含映射结果的新流。

<R> Stream<R> map(Function<? super T, ? extends R> mapper)
public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("alice", "bob", "charlie");

        // 将每个名字转换为大写
        List<String> upperCaseNames = names.stream()
                               .map(name -> name.toUpperCase())
                               .collect(Collectors.toList());

        System.out.println(upperCaseNames); // 输出 [ALICE, BOB, CHARLIE]
    }
}

04. Stream 流的终端方法

💡 终端方法就是取出流水线中元素的操作,再取出的时候,可以选择采用何种方式包装。

名称说明
void forEach()遍历
long count()统计
toArray()包装到数组中
collect()包转到集合中

💡 可以看出返回值均不是 Stream,执行完终端操作流水线就被停掉了,无法继续使用。

🍀 forEach() 就是对流水线上现有的元素的一个遍历,传入一个函数来指定对每个元素的操作。

public class Main {
    public static void main(String[] args) {
        // 对两个链表进行合并和去重操作
        List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> list2 = Arrays.asList(4, 5, 6, 7, 8);
        Stream<Integer> concat = Stream.concat(list1.stream(), list2.stream());
        Stream<Integer> stream =  concat.distinct();
//            stream.forEach(new Consumer<Integer>() {
//            @Override
//            public void accept(Integer integer) {
//                System.out.println(integer);
//            }
//       	 });
        stream.forEach(x -> System.out.print(x + " "));
    }
}
1 2 3 4 5 6 7 8

🍀 count() 方法就是统计现有元素的总数,返回值为 long

public class Main {
    public static void main(String[] args) {
        // 对两个链表进行合并和去重操作
        List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
        List<Integer> list2 = Arrays.asList(4, 5, 6, 7, 8);
        Stream<Integer> concat = Stream.concat(list1.stream(), list2.stream());
        Stream<Integer> stream =  concat.distinct();
        System.out.println(stream.count());
    }
}
8

🍀 toArray() 方法可以选择是否传参,如果不传参返回的就是一个 Object[] 数组;但一般是会得到一个具体类型的数组,这时候就要进行传参操作了。

  • 传递的参数是一个函数,它的作用是产生一个指定类型的数组,toArray 方法的底层会依次将流里的元素放到这个数组中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

        String[] array = stream.toArray(new IntFunction<>() {
            @Override
            public String[] apply(int value) {
                return new String[value];
            }
        });
        //String[] array = stream.toArray(value -> new String[value]);

05. 收集方法 collect

💡 集合的种类有很多,同时也有一些注意事项,这里单独来讲解一下 collect 方法

  • 因为可转化的集合有很多,所以需要一个参数来指定选择的是哪种集合,这个参数就是 CollectorImpl
  • 系统提供了工具类 Collectors 来规划的创建 CollectorImpl

🍀 收集到 List 或者 Set

// 收集到 List
List<String> list = stream.collect(Collectors.toList());
// 收集到 Set
Set<String> set = stream.collect(Collectors.toSet());

🍀 Mapkeyvalue 两个字段,比较特殊。

  • Collectors.toMap() 需要传入两个函数作为参数

    • 第一个函数的两个泛型分别指定流中的数据类型和新 Map 集合中键的数据类型

      • 重写的方法中参数是流中的每个元素,返回值是放置到 Map 集合中的元素的形式。
    • 第二个函数中的两个泛型分别指定流中的数据类型和新 Map 集合中值的数据类型

比如说处理很多以 姓名-年龄 为格式的字符串,将其映射为姓名为键的 Map 集合可以通过如下的方式实现:

    Map<String, Integer> collect = list.stream().collect(Collectors.toMap(
            new Function<String, String>() {
        @Override
        public String apply(String s) {

            return s.split("-")[0];
        }
    }, new Function<String, Integer>() {
        @Override
        public Integer apply(String s) {
            return Integer.parseInt(s.split("-")[1]);
        }
    }));

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/419155.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《springcloud alibaba》 三 sentinel流量控制

目录 sentinel准备流控规则 qpspom.xmlapllication.yml启动类controller查看结果流控提示不太友好 流控规则 线程数全局异常处理pom.xmlapplication.yml启动类实体类controller类异常类测试 关联流控模式关联jmeter 链路servicecontroller代码调整 流控效果Warm UP 熔断降级规则…

idea项目中文乱码

背景&#xff1a; 从gitee下download了项目发现配置值文件application.properies中出现了乱码&#xff0c;如下 其他文件都正常&#xff0c;例如 解决&#xff1a; 不要 忘记 ok 解决后配置文件 application.properties

2.1 表结构数据

1、表结构数据 字段&#xff1a;整列数 记录&#xff1a;整行数 维度&#xff1a;业务角度 度量&#xff1a;业务行为结果 维度字段&#xff1a;文本型&#xff08;状态&#xff09; 度量字段&#xff1a;数值型&#xff08;交易结果&#xff09; 2、事实表&维度表 维度表…

ubuntu22.04安裝mysql8.0

官网下载mysql&#xff1a;MySQL :: Download MySQL Community Server 将mysql-server_8.0.20-2ubuntu20.04_amd64.deb-bundle.tar上传到/usr/local/src #解压压缩文件 tar -xvf mysql-server_8.0.20-2ubuntu20.04_amd64.deb-bundle.tar解压依赖包依次输入命令 sudo dpkg -i m…

基于springboot+vue的纺织品企业财务管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

顶刊 Radiology2023 Top10文章排行榜发布:ChatGPT霸占5席!医学前沿必读

顶刊 Radiology2023 Top10文章排行榜发布&#xff1a;ChatGPT霸占5席&#xff01;医学前沿必读 期刊基本信息 期刊名称&#xff1a;RADIOLOGY 期刊ISSN: 0033-8419 影响因子/SCI分区&#xff1a;19.7/1区 出版周期&#xff1a;Monthly Radiology 是医学放射学领域的顶级期刊&am…

【算法】最小生成树—Prim算法与Kruskal算法

Prim算法和Kruskal算法都是解决最小生成树问题的经典算法。最小生成树是原图的最小连通子图&#xff0c;它包含原图的全部结点&#xff0c;且保持图连通的所有边代价和最小。一个连通图可能有多个最小生成树。 一、Prim算法 含义 Prim算法&#xff0c;也被称为普里姆算法&…

Unity(第十七部)Unity自带的角色控制器

组件Character Controller 中文角色控制器 using System.Collections; using System.Collections.Generic; using UnityEngine;public class player : MonoBehaviour {private CharacterController player;void Start(){player GetComponent<CharacterController>();}v…

Win11系统实现adb命令向安卓子系统安装APP

Win11系统实现通过adb命令向安卓子系统安装已下载好的apk包。 要实现以上目标&#xff0c;我们需要用到一个Android SDK 的组件Android SDK Platform-Tools &#xff01;这个组件呢其实是被包含在 Android Studio中的&#xff0c;如果你对安卓开发有所了解对此应该不会陌生&…

jmeter如何请求访问https接口

添加线程组http请求 新建线程组&#xff0c;添加http请求 填入协议&#xff0c;ip&#xff0c;端口&#xff0c;请求类型&#xff0c;路径&#xff0c;以及请求参数&#xff0c;查看结果树等。 然后最关键的一步来了。 导入证书 步骤&#xff1a;获取证书&#xff0c;重新生…

Linux磁盘性能方法以及磁盘io性能分析

Linux磁盘性能方法以及磁盘io性能分析 1. fio压测1.1. 安装fio1.2. bs 4k iodepth 1&#xff1a;随机读/写测试&#xff0c;能反映硬盘的时延性能1.3. bs 128k iodepth 32&#xff1a;顺序读/写测试&#xff0c;能反映硬盘的吞吐性能 2. dd压测2.1. 测试纯写入性能2.2. 测试…

【深度学习】Pytorch 教程(十五):PyTorch数据结构:7、模块(Module)详解(自定义神经网络模型并训练、评估)

文章目录 一、前言二、实验环境三、PyTorch数据结构1、Tensor&#xff08;张量&#xff09;1. 维度&#xff08;Dimensions&#xff09;2. 数据类型&#xff08;Data Types&#xff09;3. GPU加速&#xff08;GPU Acceleration&#xff09; 2、张量的数学运算1. 向量运算2. 矩阵…

《2023跨境电商投诉大数据报告》发布|亚马逊 天猫国际 考拉海购 敦煌网 阿里巴巴

2023年&#xff0c;跨境电商API接口天猫国际、京东国际和抖音全球购以其强大的品牌影响力和市场占有率&#xff0c;稳坐行业前三的位置。同时&#xff0c;各大跨境电商平台消费纠纷问题层出不穷。依据国内知名网络消费纠纷调解平台“电诉宝”&#xff08;315.100EC.CN&#xff…

C++设计模式_创建型模式_工厂方法模式

目录 C设计模式_创建型模式_工厂方法模式 一、简单工厂模式 1.1 简单工厂模式引入 1.2 简单工厂模式 1.3 简单工厂模式利弊分析 1.4 简单工厂模式的UML图 二、工厂方法模式 2.1 工厂模式和简单工厂模式比较 2.2 工厂模式代码实现 2.3 工厂模式UML 三、抽象工厂模式 3.1 战斗场景…

C语言可以干些什么?C语言主要涉及哪些IT领域?

C语言可以干些什么&#xff1f;C语言主要涉及哪些IT领域&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C语言的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家…

LangChain---大型语言模型(LLM)的标准接口和编程框架

1.背景说明 公司在新的一年规划中突然提出要搞生成式AI(GenAI)的相关东西&#xff0c;在公司分享的参考资料中了解到了一些相关的信息&#xff0c;之所以想到使用LangChain&#xff0c;是因为在应用中遇到了瓶颈问题&#xff0c;除了已经了解和研究过的OpenAI的ChatGpt&#xf…

分层解耦-三层架构(未完)

controller层——》service——》dao——》service——》controller 控制反转 依赖注入

阿里巴巴找黄金宝箱(I)【华为OD机试-JAVAPythonC++JS】

题目描述 一贫如洗的樵夫阿里巴巴在去砍柴的路上&#xff0c;无意中发现了强盗集团的藏宝地&#xff0c;藏宝地有编号从0~N的箱子&#xff0c;每个箱子上面贴有一个数字&#xff0c;箱子中可能有一个黄金宝箱。 黄金宝箱满足排在它之前的所有箱子数字和等于排在它之后的所有箱子…

Android 性能优化--APK加固(1)混淆

文章目录 为什么要开启混淆如何开启代码混淆如何开启资源压缩代码混淆配置代码混淆后&#xff0c;Crash 问题定位结尾 本文首发地址&#xff1a;https://h89.cn/archives/211.html 最新更新地址&#xff1a;https://gitee.com/chenjim/chenjimblog 为什么要开启混淆 先上一个 …

“智农”-高标准农田

高标准农田是指通过土地整治、土壤改良、水利设施、农电配套、机械化作业等措施&#xff0c;提升农田质量和生产能力&#xff0c;达到田块平整、集中连片、设施完善、节水高效、宜机作业、土壤肥沃、生态友好、抗灾能力强、与现代农业生产和经营方式相适应的旱涝保收、稳产高产…