[Java][华为云Java编程创造营][学习笔记][第三阶段][06_Java集合][05_Stream]

举报
John2021 发表于 2021/12/26 10:35:32 2021/12/26
【摘要】 5,Java Stream 5.1,Java8的新特性Stream概述Stream的概述流Stream是数据渠道,用于操作数据源集合、数组等所生成的元素序列。集合讲的是数据,流讲的是计算。Stream不是数据结构,不会保存数据。Stream不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中。Stream是惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到...

5,Java Stream

5.1,Java8的新特性Stream概述

  • Stream的概述

    • 流Stream是数据渠道,用于操作数据源集合、数组等所生成的元素序列。
    • 集合讲的是数据,流讲的是计算。
  • Stream不是数据结构,不会保存数据。

  • Stream不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中。

  • Stream是惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。

Stream流

  • Stream就好像一个高级的迭代器,但只能遍历一次,就好像一江春水向东流;在流的过程中,对流中的元素执行一些操作,比如"过滤掉长度大于10的字符串"、"获取每个字符串的首字母"等。
  • 假设我们现在要统计一个List<Person>里面的男性个数,那么代码我们通常是这样写的。
import java.util.Iterator;

public class Test
{
    public int countMan()
    {
        int count = 0;
        Iterator<Person> iterator = personList.iterator();
        while (iterator.hasNext())
        {
            Person person = iterator.next();
            if (person.getGender().equal("男"))
            {
                count++;
            }
        }
        return count;
    }
}

循环迭代遍历的弊端

  • Java8让我们可以更加专注于做什么(What),而不是怎么做(How)。现在,我们仔细体会一下上例代码。可以发现:

    • 循环的语法就是"怎么做"
    • 循环的循环体才是"做什么"
  • 为什么使用循环?因为要进行遍历,但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

使用Stream流解决问题

  • 我们要做的事情是,获取到Stream,告诉Stream我们要做什么,然后问Stream要结果。
  • 实际上,Stream并没有new一个新的List。
  • Steam在这里主要做了两件事情。
  • 第一,把所有人获取到
  • 第二,把"男性"过滤出来。
  • 优化后的代码
public class Test
{
    public int countMan()
    {
        return personList.stream().filter(person -> person.getGender().equal("男")).count();
    }
}

5.2,Stream的操作流程和创建

  • Stream操作的三个步骤:

    1. 创建Stream
      • 一个数据源(集合、数组),获取一个流。
    2. 中间操作
      • 一个中间操作链,对数据源的数据进行处理。
    3. 终止操作
      • 一个终止操作,执行中间操作链。并产生结果。
Stream操作分类
中间操作 无状态 Filter() map() peek() flatMap()
中间操作 有状态 distinct() sorted() limit() skip()
结束操作 非短路操作 forEach() toArray() reduce() max() min() count() collect()
结束操作 短路操作 findFirst() findAny() allMatch() anyMatch() noneMatch()
  • 无状态:指元素的处理不受之前元素的影响。
  • 有状态:指该操作只有拿到所有元素之后才能继续下去。
  • 非短路操作:指必须处理所有元素才能得到最终结果。
  • 短路操作:指遇到某些符合条件的元素就可以得到最终结果,如A||B,只要A为true,则无需判断B的结果。

5.3,Stream的中间操作

  • 中间操作:中间操作会返回一个新的流,一个流可以后面跟随零个或多个intermediate操作。
  • 中间操作目的主要是打开流,做出某种程度的数据映射/过滤,然后会返回一个新的流,交给下一个操作使用。这类操作都是惰性的,就是说,仅仅调用到这类方法,并没有真正开始流的遍历。而是在终端操作开始的时候才真正开始执行。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Test
{
    public static void main(String[] args)
    {
        List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
        //filter():返回结果生成新的流中只包含满足筛选条件的数据
        List<Integer> result = nums.stream().filter(n -> n > 3).collect(Collectors.toList());
        System.out.println(result);//[4, 5]
    }
}

5.4,Stream的串行流和并行流

  • Stream的串行流

    1. 所有的Collection集合都可以通过stream默认方法获取流:list.stream();
    2. Stream接口的静态方法of可以获取数组对应的流:Steam.of(6,1,5,4,3);
  • Stream的并行流

    1. 并行流是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
    2. 获取并行流的两种方式:
      • 直接获取并行的流:
        • ArrayList<Integer> list = new ArrayList<>();
        • Stream<Integer> stream = list.parallelStream();
      • 将串行流转成并行流:
        • ArrayList<Integer> list2 = new ArrayList<>();
        • Stream<Integer> stream1 = list2.stream().parallel();
  • stream和parallelStream的简单区分:stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处:

  • 如果流中的数据量足够大,并行流可以加快处理速度。除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流。

import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Test
{
    public static void main(String[] args)
    {
        //初始化1千万条随机数据
        System.out.println("初始化测试数据开始...");
        List<UUID> datas = Stream.iterate(UUID.randomUUID(),
                t -> UUID.randomUUID()).limit(10000000).collect(Collectors.toList());
        System.out.println("初始化测试数据结束...");

        //开始测试串行流
        System.out.println("测试串行流开始");
        long streamStartTime = System.nanoTime();
        datas.stream().sorted().count();
        long streamEndTime = System.nanoTime();
        System.out.println("测试串行流结束");

        //开始测试并行流
        System.out.println("测试并行流开始");
        long parallelStreamStartTime = System.nanoTime();
        datas.parallelStream().sorted().count();
        long parallelStreamEndTime = System.nanoTime();
        System.out.println("测试并行流结束");

        System.out.println("测试完毕,计算结果...");

        BiFunction<Long, Long, Long> workMillionTime = (start, end) -> TimeUnit.NANOSECONDS.toMillis(end - start);
        long streamTime = workMillionTime.apply(streamStartTime, streamEndTime);//计算串行耗时
        long paramStreamTime = workMillionTime.apply(parallelStreamStartTime, parallelStreamEndTime);//计算并行耗时

        //串行并行耗时测试结果
        System.out.println(String.format("串行流耗时:%d,并行流耗时:%d", streamTime, paramStreamTime));
        /*
        * 输出结果
        *   初始化测试数据开始...
            初始化测试数据结束...
            测试串行流开始
            测试串行流结束
            测试并行流开始
            测试并行流结束
            测试完毕,计算结果...
            串行流耗时:4295,并行流耗时:1437
        * */
    }
}

5.5,Stream的filter过滤操作

  • Stream流中的常用方法filter:用于对Stream流中的数据进行过滤,filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤。
import java.util.stream.Stream;

public class Test
{
    public static void main(String[] args)
    {
        //创建Stream流
        Stream<String> stream = Stream.of("aa", "bb", "cc", "dd", "apple");
        stream.filter(name -> name.startsWith("a")).forEach(name -> System.out.println(name));
    }
    /*
    * 输出结果
    *   aa
        apple
    * */
}

5.6,Stream的sorted排序

  • Stream的sorted()排序,能够以自然顺序或用Compactor接口定义的排序规则来排序一个流,Comparator能用Lambda表达式来初始化,还能够逆序一个已经排序的流。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class Test
{
    public static void main(String[] args)
    {
        List lists = new ArrayList();

        lists.add(10);
        lists.add(9);
        lists.add(1);
        lists.add(0);
        lists.add(3);
        lists.add(6);

        lists.stream().sorted().forEach(i -> System.out.print(i + ","));//0,1,3,6,9,10,
        System.out.println();
        lists.stream().sorted(Comparator.reverseOrder()).forEach(i -> System.out.print(i + ","));//10,9,6,3,1,0,
    }
}
  • Stream的sorted()自定义排序:用Comparable接口定义的排序规则来排序一个流:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

class Student implements Comparable<Student>
{
    private int id;
    private String name;
    private int age;

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

    @Override
    public boolean equals(Object obj)
    {
        if (obj == null)
        {
            return false;
        }
        Student student = (Student) obj;
        if (this == student)
        {
            return true;
        }
        else
        {
            return (this.name.equals(student.name) && (this.age == student.age));
        }
    }

    @Override
    public int compareTo(Student o)
    {
        return name.compareTo(o.getName());
    }

    @Override
    public int hashCode()
    {
        int hashno = 7;
        hashno = 13 * hashno + (name == null ? 0 : name.hashCode());
        return hashno;
    }


    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public int getAge()
    {
        return age;
    }

    public void setAge(int age)
    {
        this.age = age;
    }
}

public class Test
{
    public static void main(String[] args)
    {
        List<Student> list = new ArrayList<>();
        list.add(new Student(1, "Mahesh", 12));
        list.add(new Student(2, "Suresh", 15));
        list.add(new Student(3, "Nilesh", 10));

        System.out.println("---Natural Sorting by Name---");
        List<Student> slist = list.stream().sorted().collect(Collectors.toList());
        slist.forEach(e -> System.out.println("Id: " + e.getId() + ",Name: " + e.getName() + ",Age: " + e.getAge()));
        /*
        * 输出结果
        *   ---Natural Sorting by Name---
            Id: 1,Name: Mahesh,
            Id: 3,Name: Nilesh,
            Id: 2,Name: Suresh,
        * */

        System.out.println("---Natural Sorting by Name in reverse order---");
        slist = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
        slist.forEach(e -> System.out.println("Id: " + e.getId() + ",Name: " + e.getName() + ",Age: " + e.getAge()));
        /*
        * 输出结果
        *   ---Natural Sorting by Name in reverse order---
            Id: 2,Name: Suresh,Age: 15
            Id: 3,Name: Nilesh,Age: 10
            Id: 1,Name: Mahesh,Age: 12
        * */

        System.out.println("---Sorting using Comparator by Age---");
        slist = list.stream().sorted(Comparator.comparing(Student::getAge)).collect(Collectors.toList());
        slist.forEach(e -> System.out.println("Id: " + e.getId() + ",Name: " + e.getName() + ",Age: " + e.getAge()));
        /*
        * 输出结果
        *   ---Sorting using Comparator by Age---
            Id: 3,Name: Nilesh,Age: 10
            Id: 1,Name: Mahesh,Age: 12
            Id: 2,Name: Suresh,Age: 15
        * */

        System.out.println("---Sorting user Comparator by Age with reverse order---");
        slist = list.stream().sorted(Comparator.comparing(Student::getAge).reversed()).collect(Collectors.toList());
        slist.forEach(e -> System.out.println("Id: " + e.getId() + ",Name: " + e.getName() + ",Age: " + e.getAge()));
        /*
        * 输出结果
        *   ---Sorting user Comparator by Age with reverse order---
            Id: 2,Name: Suresh,Age: 15
            Id: 1,Name: Mahesh,Age: 12
            Id: 3,Name: Nilesh,Age: 10
        * */
    }
}

5.7,Stream的limit限制条目

  • Stream流中的常用方法limit:用于截取流中的元素,limit方法可以对流进行截取,只取用前n个。
import java.util.stream.Stream;

public class Test
{
    public static void main(String[] args)
    {
        //获取Stream流
        String[] arr = {"aa", "bb", "cc", "dd", "ee"};
        Stream<String> stream = Stream.of(arr);
        //使用limit对Stream流中的元素进行截取
        stream.limit(3).forEach(name -> System.out.println(name));
        /*
        *   输出结果
        *   aa
            bb
            cc
        * */
    }
}

5.8,Steam的distinct去重

  • Stream流的distinct方法对自定义对象去重,定义的对象需要实现hashcode()和equals()方法:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Test
{
    public static void main(String[] args)
    {
        List<String> list = Arrays.asList("aa", "bb", "cc", "aa");
        long l = list.stream().distinct().count();
        System.out.println("No. of distinct elements: " + l);
        String output = list.stream().distinct().collect(Collectors.joining(","));
        System.out.println(output);
    }
    /*
    * 输出结果
    *   No. of distinct elements: 3
        aa,bb,cc
    * */
}
import java.util.ArrayList;
import java.util.List;

class Book
{
    private String name;
    private int price;

    public Book(String name, int price)
    {
        this.name = name;
        this.price = price;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj == null)
        {
            return false;
        }
        Book book = (Book) obj;
        if (this == book)
        {
            return true;
        }
        else
        {
            return (this.name.equals(book.name) && (this.price == book.price));
        }
    }

    @Override
    public int hashCode()
    {
        int hashno = 7;
        hashno = 13 * hashno + (name == null ? 0 : name.hashCode());
        return hashno;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public int getPrice()
    {
        return price;
    }

    public void setPrice(int price)
    {
        this.price = price;
    }
}

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

        list.add(new Book("CoreJava", 200));
        list.add(new Book("CoreJava", 200));
        list.add(new Book("SpringMVC", 200));

        long l = list.stream().distinct().count();
        System.out.println("No. of distinct books:" + l);
        list.stream().distinct().forEach(b -> System.out.println(b.getName() + "," + b.getPrice()));
    }
    /*
    * 输出结果
    *   No. of distinct books:2
        CoreJava,200
        SpringMVC,200
    * */
}

5.9,Stream的skip跳过元素

  • Stream流中的常用方法skip:用于跳过元素,如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流。
import java.util.stream.Stream;

public class Test
{
    public static void main(String[] args)
    {
        //获取一个Stream流
        String[] arr = {"aa", "bb", "cc", "dd", "ee"};
        Stream<String> stream = Stream.of(arr);
        //使用skip方法跳过前四个元素
        stream.skip(4).forEach(name -> System.out.println(name));
    }
    /*
     * 输出结果
     * ee
     * */
}

5.10,Stream的mapTo统计

  • Stream的mapTo返回一个Stream,其中包括将给定函数应用于此流的元素的结果。
import java.util.Arrays;
import java.util.List;

public class Test
{
    public static void main(String[] args)
    {
        List<String> list = Arrays.asList("1", "2", "3", "4");
        list.stream().mapToInt(num -> Integer.parseInt(num)).filter(num -> num % 3 == 0)
                .forEach(i -> System.out.print(i + ","));
        /*
         * 输出结果
         * 3,
         * */
    }
}

映射(map)

  • 映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。
  • map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

5.11,Stream的collect规约操作

  • Stream的collect是一个把stream规约成一个value的规约操作。
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Test
{
    public static void main(String[] args)
    {
        List<Integer> skipNum = IntStream.range(1, 100).skip(85).boxed()
                .collect(Collectors.toList());
        System.out.println(skipNum);
    }
    /*
     * 输出结果
     * [86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
     * */
}

收集(collect)

  • collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。
  • collect主要依赖java.util.stream.Collectors类内置的静态方法。

归集(toList/toSet/toMap)

  • 因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toListtoSettoMap比较常用,另外还有toCollectiontoConcurrentMap等复杂一些的用法。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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