行为抽象和Lambda流

举报
xcc-2022 发表于 2022/07/22 11:45:36 2022/07/22
【摘要】 文章目录前言一、什么是 Stream?二、我的偏见1.数据准备2.效率测试三、stream 流的用法1.数据准备2.API 的使用获取流对象foreachcollectfiltedistinctlimitskipmapflatMapmapToXxxsortedanyMatchallMatchnoneMatchfindAnyfindFirstreducecountpeekStream.of四、...



前言

今天聊聊 stream 流,其实之前我对 stream 流是有点的偏见,奈何公司用得比较多,我也开始使用起来,才感觉真香,在此做出分享。


一、什么是 Stream?

  1. Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

  2. Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

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

  4. Stream操作还有两个基础的特征:

    • Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
    • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

以上概念来源于:菜鸟教程 Java 8 Stream

先说说之前的偏见吧


二、我的偏见

1.数据准备

我这里准备了 Student 实体类和一个 TestStream 测试类,TestStream 类中现在什么内容都没有
在这里插入图片描述


在这里插入图片描述
Student 类中有 4 个属性,以及 一个无参、一个全参的构造方法,其次就是 get 和 set 方法 和 toString 方法。

Student.java 代码如下(示例):

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
    private Integer id;
    private String name; // 姓名
    private Integer age; // 年龄
    private Integer score; // 成绩
}

2.效率测试

接下来我使用传统的 for 循环和 stream 操纵集合进行对比看看效率

测试代码如下(示例):

    @Test
    public void performanceTesting() {

        // 先创建 1000 个 Student 对象
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            Student student = new Student();
            // 给学生对象设置成绩,就取 i 值
            student.setScore(i);
            students.add(student);
        }

        // 接下来我使用 for 循环获取 students 集合中每个学生的成绩,加入到集合之中,并记录执行时间
        long startByFor = System.currentTimeMillis();
        List<Integer> scoresByFor = new ArrayList<>();
        for (Student student : students) {
            scoresByFor.add(student.getScore());
        }
        long endByFor = System.currentTimeMillis();
        System.out.println("for 循环执行的时间:" + (endByFor - startByFor));

        System.out.println("====================================");

        // 接下来我使用 stream 流获取 students 集合中每个学生的成绩,加入到集合之中,并记录执行时间
        long startByStream = System.currentTimeMillis();
        List<Integer> scoresByStream = students.stream().map(Student::getScore).collect(Collectors.toList());
        long endByStream = System.currentTimeMillis();
        System.out.println("stream 流执行的时间:" + (endByStream - startByStream));
    }

运行该测试方法,结果如下:

在这里插入图片描述
可以看到 for 循环的效率是要比 stream 流高的,那可能你们要说,你才处理 1000 个对象,那十万个对象呢,一百万个对象呢

那我再试试处理 十万 个对象

在这里插入图片描述
处理十万个对象运行结果:

在这里插入图片描述
处理一千万对象运行结果:

在这里插入图片描述
都可以看出其实 for 循环的效率还是蛮高的,当初大概就是因为这个原因不怎么用 stream 流,感觉是有点较真了,虽然 stream 流在简单的集合操作中效率可能真不如 for 循环,但是差距几乎没有,而且不得不说 stream 处理集合是真的很方便,以下就是展示一些 stream 流常用的方法的用法展示(有部分是工作中学到的,有的是在网上看视频学到的)。


三、stream 流的用法

1.数据准备

在测试类中先添加一个初始化的方法,创建一些不同的集合,并且赋初值

    private List<String> strings;
    private List<Integer> ints;
    private List<String> names;
    private List<Student> students;

    // 数据准备,在执行测试方法之前先初始化数据
    @Before
    public void init() {
        strings = Arrays.asList("abc", "0", "bc", "for","bc", "e fg", "jse", "Ff", "jkl","886");
        ints = Arrays.asList(100,84,66,5,41,2,0,-7,88,-201);
        names = Arrays.asList("张三","李四","王二","麻子","翠花","壮壮","狗蛋","小红");
        students = new ArrayList<>();
        students.add(new Student(1,"米大傻",18,90));
        students.add(new Student(2,"米二傻",18,91));
        students.add(new Student(3,"米三傻",19,92));
        students.add(new Student(4,"米四傻",18,null));
        students.add(new Student(5,"米五傻",20,88));
        students.add(new Student(6,"米六傻",18,98));
        students.add(new Student(7,"米七傻",21,100));
        students.add(new Student(7,"米七傻",21,100));
        students.add(new Student(9,"米九傻",19,73));
        students.add(new Student(10,"米十傻",22,90));
    }

2.API 的使用

获取流对象

    /**
     * 获取流对象
     *      1. 获取串行流
     *      2. 获取并行流
     */
    @Test
    public void getStream() {
        // 1. 获取串行流
        Stream<String> stream = strings.stream();

        // 2. 获取并行流
        Stream<String> stringStream = strings.parallelStream();
    }

至于 串行流 和 并行流 有什么区别,这里我也不多说,以下链接有说明,平日里基本上都是获取串行流对象

链接:Java8的新特性–并行流与串行流

foreach

作用:遍历集合

    /**
     * foreach:遍历集合 -- 访问者模式
     */
    @Test
    public void foreach() {
        // 遍历下 names 集合
        names.forEach(System.out::println);
        System.out.println("====================================");
        // 遍历下 students 集合
        students.forEach(System.out::println);
    }

打印结果如下:

在这里插入图片描述

collect

作用:收集器,能够将流转换为其他形式,比如:list set map


    /**
     * collect:收集器。将流转换为其他形式 : list   set   map
     */
    @Test
    public void collect() {
        System.out.println("strings = " + strings);
        // 将 strings 集合转化成 set 集合
        Set<String> set = strings.stream().collect(Collectors.toSet());
        System.out.println("set = " + set);

        // 将 strings 转换成 map 集合,key 为 prod_ + 集合中的值,value 为集合中的值
        /**
         *   v -> "prod_" + v                              key
         *   v -> v                                        value
         *   (oldValue, newValue) -> newValue              遇到重复的选 newValue
         */
        Map<String, String> toMap = strings.stream().collect(Collectors.toMap(v -> "prod_" + v, v -> v, (oldValue, newValue) -> newValue));
        System.out.println("toMap = " + toMap);

        // 将 student 集合按照 id(唯一) 封装成 map 集合
        Map<String, Student> studentMap = students.stream().collect(Collectors.toMap(student -> student.getId().toString(), student -> student));
        System.out.println("studentMap = " + studentMap);

        // 将 students 集合按照 年龄 封装成 map 集合
        Map<String, List<Student>> studentListMap = students.stream().collect(Collectors.groupingBy(student -> String.valueOf(student.getAge())));
        System.out.println("studentListMap = " + studentListMap);
    }

打印结果如下:

在这里插入图片描述

这个方法一般跟 stream 中其它方法聚合使用,之后的例子都会使用到

filte

作用:用于通过设置的条件过滤出元素

    /**
     * filter:用于通过设置的条件过滤出元素
     */
    @Test
    public void filter() {
        // 将 strings 集合中带 f 的字符串筛选出来放如另外一个集合
        List<String> filterStrings = strings.stream().filter(str -> str.contains("f")).collect(Collectors.toList());
        filterStrings.forEach(System.out::println);
        System.out.println("====================================");
        // 将 students 集合中叫年龄为 18 的同学找出来
        List<Student> filterStudents = students.stream().filter(student -> 18 == student.getAge()).collect(Collectors.toList());
        filterStudents.forEach(System.out::println);
    }

打印结果如下:

在这里插入图片描述

distinct

作用:去除集合中重复的元素

    /**
     * distinct:去重
     */
    @Test
    public void distinct() {
        // 去除集合中重复的元素
        List<String> distinct = strings.stream().distinct().collect(Collectors.toList());
        distinct.forEach(System.out::println);
        System.out.println("====================================");
        List<Student> distinctStudents = students.stream().distinct().collect(Collectors.toList());
        distinctStudents.forEach( student -> System.out.println(student.toString()));
    }

打印结果如下:

在这里插入图片描述

limit

作用:获取流中前 n 元素

    /**
     * limit:获取流中前 n 元素
     */
    @Test
    public void limit() {
        List<String> limitStrings = strings.stream().limit(2).collect(Collectors.toList());
        limitStrings.forEach(System.out::println);
        System.out.println("====================================");
        Random random = new Random();
        random.ints().limit(5).forEach(System.out::println);
    }

打印结果如下:

在这里插入图片描述

skip

作用:获取流中除去前 n 个元素的其它所有元素

    /**
     * skip:获取流中除去前 n 个元素的其它所有元素
     */
    @Test
    public void skip() {
        List<String> skipNames = names.stream().skip(2).collect(Collectors.toList());
        skipNames.forEach(System.out::println);
    }

打印结果如下:

在这里插入图片描述

map

作用:用于映射每个元素到对应的结果

    /**
     * map:接受一个函数作为参数,这个函数会被应用到每一个元素上,并将其映射成一个新的元素
     *      使用映射一词,是因为它和转换类似,但其中的细微差别在于它是 创建一个新版本而不是去 修改
     *
     *      对流中所有元素做处理
     */
    @Test
    public void map() {
        // 将集合 names 中的每个元素都拼接一个 A- str -A
        // 会将箭头函数处理出来的结果作为集合中的元素返回
        List<String> mapNames = names.stream().map( str -> "A-"+ str.concat("-A")).collect(Collectors.toList());
        mapNames.forEach(System.out::println);
        System.out.println("====================================");
        // 获取 students 集合中每个同学的 名字
        List<String> mapStudents = students.stream().map(Student::getName).collect(Collectors.toList());
        mapStudents.forEach(System.out::println);
    }

打印结果如下:

在这里插入图片描述

flatMap

作用:流的扁平化处理

    /**
     * flatMap:使用 flatMap 方法的效果是:各个数组并不是分别映射成一个流,而是映射成流的内容
     *          所有使用 map(Arrays::stream) 时生成的单个流被合并起来,即扁平化为一个流
     *
     *          流的扁平化处理
     */
    @Test
    public void flatMap() {
        List<Character> flatMap = strings.stream().flatMap(TestStream::getCharacterByString)
                .collect(Collectors.toList());
        System.out.println(flatMap);

        List<Stream<Character>> map = strings.stream().map(TestStream::getCharacterByString)
                .collect(Collectors.toList());
        System.out.println(map);

        // 对比这 map 和 flatMap 两个方法
        /**
         * map 解析:
         *      1) [a,b,c] , [0] ,..., [8,8,6]
         *      2) [a,b,c,0,b,c,...,8,8,6]
         * flatMap 解析:
         *      1) [a,b,c] , [0] ,..., [8,8,6]
         *
         * map 是返回一个值,而 flatMap 返回一个流,多个值
         */
        Stream<Stream<Character>> mapStream = strings.stream().map(TestStream::getCharacterByString);
        System.out.println("==========================");
        Stream<Character> flatMapStream = strings.stream().flatMap(TestStream::getCharacterByString);
    }

    /**
     * 字符串转换为字符流
     */
    public static Stream<Character> getCharacterByString(String str) {
        List<Character> characterList = new ArrayList<>();
        for (Character character : str.toCharArray()) {
            characterList.add(character);
        }
        return characterList.stream();
    }

打印结果如下:

在这里插入图片描述
map 和 flatMap 的区别主要体现在返回对象中
map 的返回对象是 Stream<Stream<Object>>
而 flatMap 返回的对象是 Stream<Object>
如果想明白的更加深入点可参见 面试官:Java 8 map 和 flatMap 的区别?大部分人答不上来 这篇博客

mapToXxx

作用:转换类型

    /**
     * mapToXxx: 转换
     */
    @Test
    public void mapToXxx() {
        // 提取出 students 集合的 age 属性并转换成 Long 类型
        List<Long> mapToLongStudents = students.stream().mapToLong(Student::getAge).boxed().collect(Collectors.toList());
        System.out.println("mapToLongStudents = " + mapToLongStudents);
    }

打印结果如下:

在这里插入图片描述

sorted

作用:用于对流进行排序

    /**
     * sorted:返回排序后的流
     */
    @Test
    public void sorted() {
        // 默认排序
        List<String> defaultSortedStrings = strings.stream().sorted().collect(Collectors.toList());
        List<Integer> defaultSortedInts = ints.stream().sorted().collect(Collectors.toList());
        List<String> defaultSortedNames = names.stream().sorted().collect(Collectors.toList());
        System.out.println("defaultSortedStrings = " + defaultSortedStrings);
        System.out.println("defaultSortedInts = " + defaultSortedInts);
        System.out.println("defaultSortedNames = " + defaultSortedNames);

        // 使用比较器
        // 正向按中文排序
        List<String> collatorNames = names.stream().sorted(Collator.getInstance(Locale.CHINA)).collect(Collectors.toList());
        System.out.println("collatorNames = " + collatorNames);
        // 反向按中文排序
        List<String> reverseOrderNames = names.stream().sorted(Collections.reverseOrder(Collator.getInstance(Locale.CHINA))).collect(Collectors.toList());
        System.out.println("reverseOrderNames = " + reverseOrderNames);

        // 将 students 集合按照年龄进行排序,年龄相同则通过 id 进行排序
        List<Student> collectStudents = students.stream().sorted(Comparator.comparing(Student::getAge).thenComparing(Student::getId))
                .collect(Collectors.toList());
        collectStudents.forEach(System.out::println);
    }

打印结果如下:

在这里插入图片描述

anyMatch

作用:检查集合中是否至少匹配一个元素,返回 boolean

    /**
     * anyMatch:检查集合中是否至少匹配一个元素,返回 boolean
     */
    @Test
    public void anyMatch() {
        // 检查集合 strings 中是否有包含 bc 的元素
        boolean anyMatchBcStrings = strings.stream().anyMatch(s -> s.contains("bc"));
        System.out.println("anyMatchBcStrings = " + anyMatchBcStrings);

        // 检查集合 students 中是否有同学成绩不存在
        boolean anyMatchNullStudents = students.stream().anyMatch(student -> student.getScore() == null);
        System.out.println("anyMatchNullStudents = " + anyMatchNullStudents);
    }

打印结果如下:

在这里插入图片描述

allMatch

作用:检查是否匹配所有元素,返回 boolean

    /**
     * allMatch:检查是否匹配所有元素,返回 boolean
     */
    @Test
    public void allMatch() {
        // 检查集合中的元素是否都大于 0
        boolean result = ints.stream().allMatch(s -> s>0);
        System.out.println("result = " + result);
    }

打印结果如下:

在这里插入图片描述

noneMatch

作用:检查是否没有匹配所有元素,返回 boolean

    /**
     * noneMatch:检查是否没有匹配所有元素,返回 boolean
     */
    @Test
    public void noneMatch() {
        // 检查集合中的元素长度是否都不大于 5
        boolean result = strings.stream().noneMatch(s -> s.length()<5);
        System.out.println("result = " + result);
    }

打印结果如下:

在这里插入图片描述

findAny

作用:将返回当前流中的任意元素

    /**
     * findAny:将返回当前流中的任意元素
     */
    @Test
    public void findAny() {
        Optional<String> any = strings.stream().findAny();
        // 获取一下值,如果是 串行的流 默认会返回集合中的第一个(效率快)
        //if (any.isPresent()) System.out.println(any.get());
        any.ifPresent(System.out::println);

        Optional<String> any1 = strings.parallelStream().findAny();
        // 获取一下值,如果是 并行的流 集合里的元素都有可能拿得到
        any1.ifPresent(System.out::println);
    }

打印结果如下:

在这里插入图片描述

findFirst

作用:将返回集合中的第一元素

    /**
     * findFirst:将返回集合中的第一元素
     */
    @Test
    public void findFirst() {
        Optional<String> first = names.stream().findFirst();
        first.ifPresent(System.out::println);
    }

打印结果如下:

在这里插入图片描述

reduce

作用:可以将流中元素反复结合起来,得到一个值

    /**
     * reduce:可以将流中元素反复结合起来,得到一个值
     */
    @Test
    public void reduce() {
        /**
         * acc:初始化值,默认为 null
         * item:集合中的元素
         */
        Optional<String> reduce = strings.stream().reduce((acc,item) -> {return acc+item;});
        //if (reduce.isPresent()) System.out.println(reduce.get());
        reduce.ifPresent(System.out::println);

        // 获取集合 students 的总体成绩
        Optional<Integer> reduceScore = students.stream().map(student -> student.getScore() == null ? 0: student.getScore()).reduce(Integer::sum);
        reduceScore.ifPresent(System.out::println);
    }

打印结果如下:

在这里插入图片描述

count

作用:获取集合中元素的数量

    /**
     * count:获取集合中元素的数量
     */
    @Test
    public void count() {
        // 查看 strings 集合中有多少元素
        long count = strings.stream().count();
        System.out.println("count = " + count);

        // 统计成绩大于 90 的分同学的个数
        long countStudents = students.stream().filter(student -> (student.getScore() == null ? 0 : student.getScore()) > 90).count();
        System.out.println("countStudents = " + countStudents);
    }

打印结果如下:

在这里插入图片描述

peek

作用:接收一个没有返回值的λ表达式,可以做一些输出,外部处理等

    /**
     * 接收一个没有返回值的λ表达式,可以做一些输出,外部处理等
     */
    @Test
    public void peek() {
        // 将学生成绩为空的同学记上 0 分 返回
        List<Student> peekStudents = students.stream().filter(student -> student.getScore() == null).peek(student -> student.setScore(0)).collect(Collectors.toList());
        System.out.println("peekStudents = " + peekStudents);

    }

打印结果如下:

在这里插入图片描述

Stream.of

作用:初始化集合

    /**
     * 初始化集合
     */
    @Test
    public void streamOf() {
        List<String> list = Stream.of("1", "2", "3").collect(Collectors.toList());
        System.out.println("list = " + list);
    }

打印结果如下:
在这里插入图片描述


四、总结

以上大概就是我在工作中经常使用到的 stream 操作,可能不是很齐全,今有若使用到其它的操作,我也会更新上去,欢迎大家指出我的不足。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。