JDK1.8 新特性(二)【Stream 流】

前言

        上节我们学了 lambda 表达式,很快我就在 Flink 的学习中用到了,我学的是 Java 版本的 Flink,一开始会以为代码会很复杂,但事实上 Flink 中很多地方都用到了 函数接口,这也让我们在编写 Flink 程序的时候可以使用 lambda 表达式非常地简洁地实现匿名函数。

        今天再来学习一个新的特性,Stream 流,光是看名字就觉得和大数据能扯上关系,我们的 Spark、Flink 当中不就都是这种流的概念嘛。

1、什么是 Strem 流

        Stream 是 JDK1.8 中处理集合的关键抽象概念, Lambda 表达式 和 Stream 是JDK1.8 新增的函数式编程中最有亮点的特性了,它可以指定你希望对集合进行操作,可以执行非常复杂的查询过滤和映射等操作。使用 Stream API 对集合数据进行操作,就类似于使用 SQL 来执行对 Java 集合运算和表达的高阶抽象。

        Stream API 可以极大地提高 Java 程序员的生产力,让程序员写出更加高效、干净、简洁的代码。那对我在大数据开发中更是如此。

        这种风格将要处理的元素集合看做一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如过滤、排序、聚合等。

2、Stream 创建方式

1、创建串行 Stream

Stream<User> userStream = list.stream();

2、创建并行 Stream

Stream<User> userStream = list.parallelStream();

3、关闭

在Java中,Stream只能被操作一次,一旦你对其进行了一次操作(比如forEach, collect等),它就会被关闭,再次操作就会报错:stream has already been operated upon or closed。

3、Stream 将 List 转换为 Set

1、创建 List 集合

Stream 是通过集合创建出来的,所以我们先创建一个集合,而集合内我们需要存放实体,所以先创建一个实体类 User:

public class User {
    public String name;
    public int age;

    public User(){}

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age && Objects.equals(name, user.name);
    }


    @Override
    public int hashCode() {
        return Objects.hash(name, 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;
    }
}

创建集合

List<User> list = new ArrayList<>();
        list.add(new User("燕双鹰",28));
        list.add(new User("李大喜",20));
        list.add(new User("李元芳", 30));
        list.add(new User("李元芳", 30));

重写 equals

注意:这里我们对实体类的 equals 和 hashcode 方法进行了重写,这在之前我是不会去重写的。重写和不重写的区别就是:
        重写后,当两个实体对象的属性相同时,equals 方法返回 true,如果没有重写,则 equals 返回 false。

== 和 equals

        == 用于比较基本数据类型的值是否相等或者对象的引用地址是否相同。

int a = 10;
int b = 10;
System.out.println(a==b);    //true

String str1 = "hello";
String str2 = "hello";
System.out.println(str1==str2);    //false

equals 用于比较两个对象的内容是否相等。在Object类中,默认的“equals()”实现使用“==”操作符比较对象的引用。但是,许多类(如String、Integer等)重写了“equals()”方法,以便根据类的特定属性比较对象的内容。

set 去重底层原理

set 去重底层依赖于 map 集合实现放重复的 key,map 集合底层基于 equals ,它先比较 key 的hashcode 是否相同,相同情况下再调用 equals 方法判断是否真的相等。

所以一个实体类是否重写 equals 方法区别很大。

User u1 = new User("s",1);
        User u2 = new User("s",1);
        System.out.println(u1.equals(u2));

上面的代码,如果我们以 User 对象作为 key,如果我们的 User 没有重写 equals 方法,那么返回的就是 false,因为默认使用 == ,引用地址不同;如果重写了 equals 方法,那么返回的就是 true,因为使用重写后的 equals ,两个对象属性相同返回 true。

注意:对象的比较不会去比较 hashcode。 

HashMap<User, String> map = new HashMap<>();
        map.put(u1,"a");
        map.put(u2,"b");
        System.out.println(map.get(u1).equals(map.get(u2)));

上面的代码,如果我们没有重写 hashcode 的情况下,那么返回的就是 true,因为 map 的底层是通过 hashcode 来比较两个 key 是否相同;如果重写了 hashcode ,那么返回的就是 true。

2、List 转为 Set

public static void main(String[] args) {
        List<User> list = new ArrayList<>();
        list.add(new User("燕双鹰",28));
        list.add(new User("李大喜",20));
        // 下面是两个属性相同的两个对象(我们已经重写了 equals 和 hashcode 方法)
        list.add(new User("李元芳", 30));
        list.add(new User("李元芳", 30));

        // todo 创建 Stream 的两种方式
        // 1. 串行流 stream() 单线程
        Stream<User> stream = list.stream();
        Set<User> set = stream.collect(Collectors.toSet());
        set.forEach(user->{
            System.out.println(user.toString());
        });
}

运行结果:

User{name='李元芳', age=30}
User{name='燕双鹰', age=28}
User{name='李大喜', age=20}

可以看到,重写 equals 和 hashcode 方法后,虽然相同属性的两个对象的内存地址不同,但也被去除重复了。

4、Stream 将 List 转为 Map

1、创建 List

注意:List 转为 Map 的时候,由于 Map 集合不允许存在重复的 key,所以我们必须保证 list 集合中作为 key 字段的属性值唯一。

List<User> list = new ArrayList<>();
        list.add(new User("燕双鹰",28));
        list.add(new User("李大喜",20));
        list.add(new User("李元芳", 30));

 2、List 转为 Map

 Stream<User> stream = list.stream();
        // list 集合是没有 key 的,所以不能直接转为 map 集合,需要指定 key(指定对象的某个字段作为key)
        Map<String, User> collect = stream.collect(Collectors.toMap(new Function<User, String>() {  // 第一个参数 list中的类型,第二个参数是key类型: String
            @Override
            public String apply(User user) {
                return user.getName();
            }
        }, new Function<User, User>() { // 第一个参数 list中的类型,第二个参数是value类型: User
            @Override
            public User apply(User user) {
                return user;
            }
        }));

        collect.forEach(new BiConsumer<String, User>() {
            @Override
            public void accept(String key, User user) {
                System.out.println(key+","+user.toString());
            }
        }); 

使用 lambda 表达式简化一下代码:

        // 用lambda表达式
        Map<String, User> collect = stream.collect(Collectors.toMap(User::getName, user -> user));
        collect.forEach((key,user)-> System.out.println(key+","+user.toString()));

运行结果:

李元芳,User{name='李元芳', age=30}
李大喜,User{name='李大喜', age=20}
燕双鹰,User{name='燕双鹰', age=28}

5、Strem 通过 reduce 方法求和

1、简单求和

这里我们通过 Stream.of() 方法来进行数据的构造(这让我想到了最近 Flink)。

Stream<Integer> stream = Stream.of(10, 50, 30, 10);
        Optional<Integer> res = stream.reduce(new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer integer, Integer integer2) {
                return integer + integer2;
            }
        });

使用 lamda 表达式 

 Optional<Integer> res = stream.reduce(Integer::sum);

关于结果的打印,我们后面讲到 Optional 类的时候再详细说,一般直接:

System.out.println(res.get());

2、对象属性和

我们构造一个 List 集合,然后转为 Stream 调用 reduce 方法进行求和。

注意:reduce 方法的返回结果类型必须和 Stream 的类型一致(就像我们 Hadoop 中的 WordCount)。

List<User> list = new ArrayList<>();
        list.add(new User("燕双鹰",28));
        list.add(new User("李大喜",20));
        list.add(new User("李元芳", 30));

        Stream<User> stream = list.stream();
        Optional<User> sum = stream.reduce(new BinaryOperator<User>() {
            @Override
            public User apply(User user1, User user2) {
                return new User("sum",user1.getAge()+ user2.getAge());
            }
        });

        System.out.println(sum.get());

lambda 表达式简化:

        Stream<User> stream = list.stream();
        Optional<User> sum = stream.reduce(((user1, user2) -> new User("sum", user1.getAge() + user2.getAge())));
        System.out.println(sum.get());//78

6、Strem 查找集合最大值和最小值

1、创建集合

List<User> list = new ArrayList<>();
list.add(new User("燕双鹰",28));
list.add(new User("李大喜",20));
list.add(new User("李元芳", 30));

2、查找最大 age 属性对象

Optional<User> max = stream.max(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getAge() - o2.getAge();
            }
        });
        System.out.println(max.get());

lambda表达式简化:

Optional<User> max = stream.max((user1, user2) -> user1.getAge() - user2.getAge());
        System.out.println(max.get());    //30

3、查找最小 age 属性对象

Optional<User> min = stream.min((user1, user2) -> user1.getAge() - user2.getAge());
        System.out.println(min.get());    //20

7、Stream 中 Match 用法

anyMatch 表示,任意一个元素满足条件返回 true。

allMatch 表示,所有元素满足条件才会返回 true。

noMatch 表示,所有条件都不满足这个条件才会返回 true。

1、创建集合

List<User> list = new ArrayList<>();
        list.add(new User("燕双鹰",28));
        list.add(new User("李大喜",20));
        list.add(new User("李元芳", 30));
        Stream<User> stream = list.stream();

2、anyMatch

判断集合中是否存在 age 属性大于 25 的对象。

 boolean res = stream.anyMatch(new Predicate<User>() {
            @Override
            public boolean test(User user) {
                return user.getAge() > 25;
            }
        });
        System.out.println(res);

 lambda 表达式:

boolean res = stream.anyMatch(user -> user.getAge() > 25);
        System.out.println(res);    //true

3、allMatch

判断是否所有对象的 age属性都大于 30

boolean res = stream.allMatch(user -> user.getAge() > 30);
        System.out.println(res);    //false

4、noMatch

判断是否用户都不满足 name 为 “光头强 ”

boolean res = stream.noneMatch(user -> user.getName().equals("光头强"));
        System.out.println(res); //true

8、Stream 过滤器

和我们 Flink 的 DataStream API 中的转换算子 filter 很像,它们都是把 判断条件结果为 true 的数据留下,false 则丢掉。

1、创建集合

List<User> list = new ArrayList<>();
        list.add(new User("燕双鹰",28));
        list.add(new User("李大喜",20));
        list.add(new User("李元芳", 30));
        Stream<User> stream = list.stream();

2、过滤

留下 年龄>25 岁的 User 对象。
Stream<User> filterStream = stream.filter(new Predicate<User>() {
            @Override
            public boolean test(User user) {    //为 true 则留下
                return user.getAge()>25;
            }
        });

        filterStream.forEach(new Consumer<User>() {
            @Override
            public void accept(User user) {
                System.out.println(user);
            }
        });

运行结果: 

User{name='燕双鹰', age=28}
User{name='李元芳', age=30}

lambda表达式:

Stream<User> filterStream = stream.filter(user -> user.getAge() > 25);
        filterStream.forEach(System.out::println);

9、Stream Limit 和 Skip

同样,Stream 需要通过集合来创建。

List<User> list = new ArrayList<>();
list.add(new User("燕双鹰",28));
list.add(new User("李大喜",20));
list.add(new User("李元芳", 30));
list.add(new User("熊大",15));
list.add(new User("熊二",14));
list.add(new User("光头强",20));
Stream<User> stream = list.stream();

1、取出前2条数据

// 在mysql中limit(start,end)需要传两个参数,但在这里只允许传入一个long类型的 maxSize
        // 取前2条数据
        stream.limit(2).forEach(System.out::println);

运行结果:

User{name='燕双鹰', age=28}
User{name='李大喜', age=20}

 

2、取出第 [3,6) 条数据

注意,这里的索引是从 0 开始的。

// 取 [3,6)条数据 想要分页从先 skip 再 limit
stream.skip(2).limit(3).forEach(System.out::println);

运行结果:
 

User{name='李元芳', age=30}
User{name='熊大', age=15}
User{name='熊二', age=14}

10、Stream 排序 sorted

下面用到的数据。

List<User> list = new ArrayList<>();
list.add(new User("燕双鹰",28));
list.add(new User("李大喜",20));
list.add(new User("李元芳", 30));
list.add(new User("熊大",15));
list.add(new User("熊二",14));
list.add(new User("光头强",20));
Stream<User> stream = list.stream();

1、直接排序

对于数值型的数据可以直接进行排序

Stream<Integer> integerStream = Stream.of(1, 5, 8, 3, 7);
integerStream.sorted().forEach(System.out::println);    //1 3 5 7 8

2、根据对象字段进行升序

stream.sorted(new Comparator<User>() {
    @Override
    public int compare(User o1, User o2) {
        return o1.getAge()-o2.getAge();
    }
}).forEach(System.out::println);

lambda 表达式:

stream.sorted((o1, o2) -> o1.getAge()-o2.getAge()).forEach(System.out::println);

运行结果: 

User{name='熊二', age=14}
User{name='熊大', age=15}
User{name='李大喜', age=20}
User{name='光头强', age=20}
User{name='燕双鹰', age=28}
User{name='李元芳', age=30}

JDK1.8 提供的函数接口

都在包 java.util.function 包下。

并行流

案例1 - 500亿次求和

1、使用单线程

Instant start = Instant.now();
        long sum = 0;
        for (long i = 0; i <= 50000000000L; i++) {
            sum+=i;
        }
        Instant end = Instant.now();
        System.out.println(sum);
        System.out.println("500亿次求和花费时间: "+ Duration.between(start,end).toMillis()+"ms");   // 单线程 11s左右  多线程 6s左右

2、使用并行流

Instant start = Instant.now();
        LongStream longStream = LongStream.rangeClosed(0,50000000000L);
        OptionalLong result = longStream.parallel().reduce(new LongBinaryOperator() {
            @Override
            public long applyAsLong(long left, long right) {
                return left + right;
            }
        });
        Instant end = Instant.now();
        System.out.println(result.getAsLong());
        System.out.println("500亿次求和花费时间: "+ Duration.between(start,end).toMillis()+"ms");   // 单线程 11s左右  多线程 6s左右

可以发现,多线程明显要快很多。

总结

        本次学习收获非常大,函数接口的思想在 Flink 中随处可见,的确,这样一种能够使得代码简洁高效的技术在大数据开发中是非常重要的。

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

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

相关文章

2023亚太杯数学建模思路 - 案例:ID3-决策树分类算法

文章目录 0 赛题思路1 算法介绍2 FP树表示法3 构建FP树4 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模…

在线协作工具都有哪些?推荐这10款

如今&#xff0c;互联网的快速发展不仅改变了我们的生活方式&#xff0c;也改变了我们的工作方式。 特别是对于一些与产品设计相关的公司或团体&#xff0c;网络不仅为其设计提供了稳定的资源和灵感&#xff0c;而且为成员之间的沟通和合作提供了更大的便利。 如果您也需要为…

【Proteus仿真】【51单片机】公交车报站系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用LCD12864显示模块、DS18B20温度传感器、DS1302时钟模块、按键、LED蜂鸣器、ULN2003、28BYJ48步进电机模块等。 主要功能&#xff1a; 系统运行后&…

【Spring】依赖注入方式,DI的方式

这里写目录标题 1. setter注入在一个类中注入引用类型在一个类中注入简单类型 2. 构造器注入在一个类中注入引用类型在一个类中注入简单类型 3. 依赖注入方式选择4. 依赖自动装配按类型注入按名称注入 5. 集合注入 1. setter注入 在一个类中注入引用类型 回顾一下之前setter注…

【深度学习实验】网络优化与正则化(七):超参数优化方法——网格搜索、随机搜索、贝叶斯优化、动态资源分配、神经架构搜索

文章目录 一、实验介绍二、实验环境1. 配置虚拟环境2. 库版本介绍 三、优化算法0. 导入必要的库1. 随机梯度下降SGD算法a. PyTorch中的SGD优化器b. 使用SGD优化器的前馈神经网络 2.随机梯度下降的改进方法a. 学习率调整b. 梯度估计修正 3. 梯度估计修正&#xff1a;动量法Momen…

万宾科技智能井盖传感器,提升市政井盖健康

市政井盖就是城市里不可或缺的基础设施之一&#xff0c;关于它的监测工作可马虎不得。它承载着保护市民的交通安全以及城市正常运转的重要使命。虽然现在城市化的速度很快&#xff0c;但是传统的市政井盖管理方式变得有些力不从心了。井盖的覆盖范围很广&#xff0c;如果单单依…

Python爬虫动态ip代理防止被封的方法

目录 前言 一、什么是动态IP代理&#xff1f; 二、如何获取代理IP&#xff1f; 1. 付费代理IP 2. 免费代理IP 3. 自建代理IP池 三、如何使用代理IP爬取数据&#xff1f; 1. 使用requests库设置代理IP 2. 使用urllib库设置代理IP 3. 使用selenium库设置代理IP 四、常…

实现MQTT协议的服务器端和客户端的双向交互

公司和第三方合作开发一个传感器项目&#xff0c;想要通过电脑或者手机去控制项目现场的传感器控制情况。现在的最大问题在于&#xff0c;现场的边缘终端设备接入的公网方式是无线接入&#xff0c;无法获取固定IP&#xff0c;所以常规的HTTP协议通信就没法做&#xff0c;现在打…

群晖7.2安装Jellyfin+alist+CloudDriver搭建无盘影院中心

群晖7.2安装JellyfinalistCloudDriver搭建无盘影音中心。 实现思路如下&#xff1a; Jellyfin&#xff1a;提供个人影院功能。 alist&#xff08;xiaoya&#xff09;&#xff1a;给影院提供海量影音资源。 CloudDriver2&#xff1a;alist的资源为网络资源&#xff0c;通过C…

lvgl 画圆弧时进入 HardFault

目录 一、现象描述 lvgl 版本 二、问题分析 lvgl 需要的资源新建mcu 工程时默认分配的资源问题解决 一、现象描述 移植完lvgl 之后&#xff0c;能正常显示label&#xff0c;但是button arc 等复杂的控件都不能正常显示。调用官方的画圆弧demo 时&#xff0c;在多次调用 _lv…

业务流程图用什么软件画?这10款流程图软件,好用到飞起!

业务流程图是什么&#xff1f; 业务流程图是一种用于表示业务过程中活动流向的图形表示方法&#xff0c;它使用标准化的图形元素&#xff08;如箭头、椭圆、方框等&#xff09;来表达一个过程中各个环节之间的关系。在这个图形表示中&#xff0c;每个元素都有特定的含义和功能…

C#WPF用户控件及自定义控件实例

本文演示C#WPF自定义控件实例 用户控件(UserControl)和自定义控件(CustomControl)都是对UI控件的一种封装方式,目的都是实现封装后控件的重用。 只不过各自封装的实现方式和使用的场景上存在差异。 1 基于UserControl 创建 创建控件最简单一个方法就是基于UserControl …

Docker在Centos7下的安装

1、卸载旧版本 执行如下指令对旧版本进行卸载&#xff1a; sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine 执行完毕后&#xff0c;如果输入docker version发现do…

【MySQL学习笔记-001】- 创建表、插入数据、查看数据库结构

创建employees表 当创建一个表时&#xff0c;需要指定表的名称和每个列的名称和数据类型。以下是一个示例SQL语句&#xff0c;用于创建一个名为"employees"的表&#xff0c;其中包含员工ID、姓名、职位和工资等列&#xff1a; CREATE TABLE employees (employee_id…

单稳态中间继电器\UEG/A-2H/220V 8A导轨安装 JOSEF约瑟

UEG系列中间继电器 UEG/A-2H2D中间继电器UEG/A-4H4D中间继电器UEG/A-2D中间继电器 UEG/A-2H中间继电器UEG/A-4H中间继电器UEG/A-4D中间继电器 UEG/A-6H中间继电器UEG/A-6D中间继电器UEG/A-8H中间继电器 UEG/A-10D中间继电器UEG/A-10H中间继电器UEG/A-2DPDT中间继电器 UEG/A-4DP…

23111710[含文档+PPT+源码等]计算机毕业设计基于SpringBoot的体育馆场地预约赛事管理系统的设计

文章目录 **软件开发环境及开发工具&#xff1a;****功能介绍&#xff1a;****论文截图&#xff1a;****数据库&#xff1a;****实现&#xff1a;****代码片段&#xff1a;** 编程技术交流、源码分享、模板分享、网课教程 &#x1f427;裙&#xff1a;776871563 软件开发环境及…

国民技术Cortex-M0系列单片机IAP升级

考虑到设备部署到现场后有可能需要进行软件升级&#xff0c;之前做过PIC系列单片机的升级&#xff0c;现在想做个国民技术N32G031系列Cortex-M0内核的单片机IAP方案。 因为国民技术系列单片机在很多大程度上都模仿了STM32&#xff0c;所以我想其升级方案极有可能差不多。于是在…

C#开发的OpenRA游戏之属性BodyOrientation(6)

C#开发的OpenRA游戏之属性BodyOrientation(6) 在顶层定义里会发现这个属性: ^SpriteActor: BodyOrientation: QuantizeFacingsFromSequence: RenderSprites: SpriteActor是用来定义角色的基本属性,它的第一个属性就是BodyOrientation,这个属性主要用来描述角色的身体的…

DOA估计算法——Capon算法

1.波速形成基本思想 在理解Capon算法之前&#xff0c;我们有必要先了解波束形成的基本思想以及原理到底是什么。这有助于我们更好的理解Capon算法的思想。 图 1 如图1展示了均匀阵列波束导向的示意图。图中wm表示加权值&#xff0c;波速形成(DBF)的基本思想就是将各阵元输出进…