JDK8--06:Stream流

一、描述

Stream流提供了筛选与切片、映射、排序、匹配与查找、归约、收集等功能

筛选与切片:

  filter:接收lambda,从流中排除某些元素

  limit(n):截断流,使其元素不超过n

  skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补

  distinct:筛选,通过流所生成的元素的hashCode和equals去重

映射:

  map:接收一个lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素

  flatMap:接收一个函数作为参数,将其中的每个值都替换成另一个流,然后把所有流连接成一个流

排序:

  sorted()自然排序:comparable

  sorted(Comparator com)定制排序:comparator

查找与匹配:

  allMatch:检查是否匹配所有元素

  anyMatch:检查是否至少匹配一个元素

  noneMatch:检查是否所有都不匹配

  findFirst:返回第一个元素

  findAny:返回当前流中任意元素

  count:返回当前流中元素个数

  max:返回当前流中最大值

  min:返回流中最小值

归约:

  reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中元素反复结合起来,得到一个流

收集:

  collect:将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法

二、Strem语法

  Stream分为创建流、中间操作和终止操作三个部分

  (1)创建流有三种方式:

  第一种是使用通过Collection系列集合提供Strem()(串行流)和parellelStream()(并行流)得到一个流对象

        //1、通过Collection系列集合提供Strem()(串行流)和parellelStream()(并行流)得到一个流对象
        List list = new ArrayList();
        Stream stream = list.stream();
        Stream stream1 = list.parallelStream();

  第二种是通过数组中的静态方法stream()获取数组流

        //2、通过数组中的静态方法stream()获取数组流
        String[] arr = new String[0];
        Stream stream2 = Arrays.stream(arr);

  第三种是通过Stream中的静态方法of()创建

        List list = new ArrayList();//3、通过Stream中的静态方法of()创建
        Stream stream3 = Stream.of(list);

  除了上述三种,还有一种无限流的创建

  下面的迭代表示从0开始,每次加2,无限循环;生成就会无限生成随机数

        //无限流------迭代
        Stream stream4 = Stream.iterate(0, x->x+2);
        //无限流------生成
        Stream stream6 = Stream.generate(()->Math.random());

  (2)中间操作

  中间操作就是筛选切片、映射等都属于中间操作,操作后仍然返回一个流,以上面的无限流--迭代为例,可以使用截断流limit不让其无限循环,只要前10个

        Stream stream5 = stream4.limit(10);

  (3)终止操作,将流对象转换成数据对象的操作,以上述中间操作为例,加循环输出

        //加中间操作的终止操作
        stream4.limit(3).forEach(System.out::println);

  输出结果:0 2 4

三、中间操作详解

  Stream的操作主要就是中间操作如何处理,接下来详细说明中间操作   

1、筛选与切片

为了代码演示需要,先创建一个Student类,和一个状态的枚举类

    public enum Status{
        FREE,
        BUSY,
        VOCATION
    }




    class Student{
        private String name;
        private Integer age;
        private Status status;

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

        public Student(String name,Integer age, Status status){
            this.name = name;
            this.age = age;
            this.status = status;
        }

        public String getName() {
            return name;
        }

        public Integer getAge() {
            return age;
        }

        public Status getStatus(){
            return status;
        }

        public String toString(){
            return JSON.toJSONString(this);
        }

        /*public int hashCode(){
            int result = 1;
            result = this.getAge().hashCode() + this.getName().hashCode();
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if(obj instanceof Student){
                if(this.name.equals(((Student) obj).getName()) && this.age == ((Student) obj).getAge()){
                    return true;
                }
            }else {
                return super.equals(obj);
            }
            return false;
        }*/
    }

创建一个Student的集合

List<Student> students1 = Arrays.asList(
            new Student("lcl1",18, Status.BUSY)
            ,new Student("lcl2",30, Status.VOCATION)
            ,new Student("lcl3",50, Status.FREE)
            ,new Student("lcl4",20, Status.FREE)
            ,new Student("lcl5",25, Status.BUSY)
            ,new Student("lcl6",8, Status.FREE)
            ,new Student("lcl2",30, Status.VOCATION));

筛选与切片:

  filter:接收lambda,从流中排除某些元素

  limit(n):截断流,使其元素不超过n

  skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补

  distinct:筛选,通过流所生成的元素的hashCode和equals去重

下面四个例子,分别对应四个中间操作的处理,操作内容分别为:

  只获取年龄小于10的Student后,将集合中Student对象输出;期望输出对象为:lcl5

  返回年龄大于10的前三个Student对象并输出:期望输出对象为:lcl1,lcl2,lcl3

  跳过两个Student对象后取前三个:期望输出对象为:lcl3,lcl4,lcl5

  输出集合中去重后的list对象:期望输出对象为集合中所有,包括两个lcl2,因为这里的去重操作是根据对象的hashCode和equals去判断是否重复的,由于两个lcl2都是new出来的,因此自然非同一个对象,但是如果我们重新hashCode和equals方法,按照名字和年龄一样,就会返回一样的hash码,且equals返回true(上述Student类中注释了重新的hashCode和equals方法,放开即可),则distinct会输出一个lcl2,下面演示输出按照重写hashCode和equals方法验证

    /**
     * 筛选与切片
     * filter:接收lambda,从流中排除某些元素
     * limit(n):截断流,使其元素不超过n
     * skip(n):跳过元素,返回一个扔掉了n个元素的流,如果流中元素数不超过n,则返回一个空流,与limit(n)互补
     * distinct:筛选,通过流所生成的元素的hashCode和equals去重
     */
    @Test
    public void tst2(){
        log.info("filter测试==============================");
        students1.stream().filter((x)->x.getAge() < 10).forEach(System.out::println);

        //惰性求值:所有中间操作不会做任何处理,只有在终止操作时,才会处理
        //内部迭代,StreamAPI自己处理的迭代处理
        //短路,只要找到满足的数据后,后面的流程就不再处理
        log.info("limit测试==============================");
        students1.stream().filter((x)->{log.info("filter");return x.getAge() > 10;}).limit(3).forEach(System.out::println);

        log.info("skip测试==============================");
        students1.stream().skip(2).limit(3).forEach(System.out::println);

        log.info("distinct测试==============================");
        students1.stream().distinct().forEach(System.out::println);
    }

测试结果:

2020-05-29 10:59:57.835  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter测试==============================
{"age":8,"name":"lcl6","status":"FREE"}
2020-05-29 10:59:58.003  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : limit测试==============================
2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter
{"age":18,"name":"lcl1","status":"BUSY"}
2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter
{"age":30,"name":"lcl2","status":"VOCATION"}
2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : filter
{"age":50,"name":"lcl3","status":"FREE"}
2020-05-29 10:59:58.005  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : skip测试==============================
{"age":50,"name":"lcl3","status":"FREE"}
{"age":20,"name":"lcl4","status":"FREE"}
{"age":25,"name":"lcl5","status":"BUSY"}
2020-05-29 10:59:58.006  INFO 18008 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : distinct测试==============================
{"age":18,"name":"lcl1","status":"BUSY"}
{"age":30,"name":"lcl2","status":"VOCATION"}
{"age":50,"name":"lcl3","status":"FREE"}
{"age":20,"name":"lcl4","status":"FREE"}
{"age":25,"name":"lcl5","status":"BUSY"}
{"age":8,"name":"lcl6","status":"FREE"}

 可以发现,所有的输出都与我们期望的一致,根据测试结果,有两个概念需要说下,就是惰性求值和短路,其实在代码的注释中已经有了,这里结合测试结果再详细说明一下

  惰性求值:测试limit的时候,在中间操作中加了输出filter的操作,但是看测试结果,filter的输出和最终的结果输出一样,也是循环一次输出一次,而非通常想的会把所有的filter输出完之后,再循环输出测试结果,这就是惰性求值,即等输出结果时,在求值,而非过程中就求值

  短路:还是以limit测试为例,在使用filter过滤后,应该还有6个元素符合条件,但是在limit(3)之后,只输出了3个对象,最重要的是,连filter字符串都是只输出了三个,而非6个,这就是因为Stream中间操作的短路操作,即找到满足自己的数据后,就不再处理,这样其实也可以提升效率。

2、映射:

  map:接收一个lambda,将元素转换成其他形式或提取信息,接收一个函数作为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素

  flatMap:接收一个函数作为参数,将其中的每个值都替换成另一个流,然后把所有流连接成一个流

为了做测试,先创建一个方法,传入一个字符串,返回一个单个字符的Stream流

    public static Stream<Character> filterChaacter(String str){
        List<Character> list = new ArrayList<>();
        for (Character c : str.toCharArray()) {
            list.add(c);
        }
        return list.stream();
    }

demo需求:

  将数据组中的字符串变为大写

  输出所有学生的名字

  将数组中所有的字符串输出为单个字符

    @Test
    public void test3(){

        log.info("模拟map操作");
        List<String> list = Arrays.asList("aaa","bbb","ccc","ddd");
        list.stream().map((str)->str.toUpperCase()).forEach(System.out::println);
        students1.stream().map(Student::getName).forEach(System.out::println);


        log.info("模拟多重流操作");
        Stream<Stream<Character>> stream = list.stream().map(Jdk8StreamDemoTest::filterChaacter);
        stream.forEach(sm -> sm.forEach(System.out::println));

        log.info("模拟flatMap操作");
        list.stream().flatMap(Jdk8StreamDemoTest::filterChaacter).forEach(System.out::println);
    }

  上述代码中,模拟map操作块,即是对前两个demo需求的测试,没什么特别可说的,就是使用map从原有对象中获取我们所需要的对象;这里主要说明一下第三个demo需求,由于新增的filterChaacter方法返回的是一个Stream流,那么使用map的话就会得到一个Stream<Stream<Character>>的多重流,所以输出是,就需要双重循环(如模拟多重流代码块),非常的麻烦,因此就有了flatMap,他直接将返回的Stream流拼接成一个Stream流,这样输出的时候就只需要循环一次即可(如模拟flatMap操作)

  测试结果

AAA
BBB
CCC
DDD
lcl1
lcl2
lcl3
lcl4
lcl5
lcl6
lcl2
2020-05-29 10:58:18.557  INFO 9444 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 模拟多重流操作
a
a
a
b
b
b
c
c
c
d
d
d
2020-05-29 10:58:18.557  INFO 9444 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 模拟flatMap操作
a
a
a
b
b
b
c
c
c
d
d
d

3、排序:

  sorted()自然排序:comparable

  sorted(Comparator com)定制排序:comparator

    @Test
    public void test4(){
        //自然排序
        Arrays.asList("uio","fghf","dsf","ewre","gffjf","poiklk").stream().sorted().forEach(System.out::println);
        //定制排序
        students1.stream().sorted((s1,s2)->s1.getAge().compareTo(s2.getAge())).forEach(System.out::println);
        //倒序排序
        students1.stream().sorted((s1,s2)->-s1.getAge().compareTo(s2.getAge())).forEach(System.out::println);
    }

测试结果:

dsf
ewre
fghf
gffjf
poiklk
uio
{"age":8,"name":"lcl6","status":"FREE"}
{"age":18,"name":"lcl1","status":"BUSY"}
{"age":20,"name":"lcl4","status":"FREE"}
{"age":25,"name":"lcl5","status":"BUSY"}
{"age":30,"name":"lcl2","status":"VOCATION"}
{"age":30,"name":"lcl2","status":"FREE"}
{"age":50,"name":"lcl3","status":"FREE"}
{"age":50,"name":"lcl3","status":"FREE"}
{"age":30,"name":"lcl2","status":"VOCATION"}
{"age":30,"name":"lcl2","status":"FREE"}
{"age":25,"name":"lcl5","status":"BUSY"}
{"age":20,"name":"lcl4","status":"FREE"}
{"age":18,"name":"lcl1","status":"BUSY"}
{"age":8,"name":"lcl6","status":"FREE"}

  这个没什么特别可说的,就是一个排序,空参的方法使用自然排序,传入排序方法的,按照穿的比较器进行排序

4、查找与匹配:

  allMatch:检查是否匹配所有元素

  anyMatch:检查是否至少匹配一个元素

  noneMatch:检查是否所有都不匹配

  findFirst:返回第一个元素

  findAny:返回当前流中任意元素

  count:返回当前流中元素个数

  max:返回当前流中最大值

  min:返回流中最小值

demo需求:

  判断集合中是否状态全为BUSY

  判断集合中是否存在状态为BUSY的数据

  判断集合中是否没有一个状态为BUSY的数据

  按照年龄排序后取第一个

  取任意一个元素

  获取集合中数据总数

  获取年龄最大的Student

  获取年龄最小的Student

    @Test
    public void test5(){
        log.info("allMatch结果{}",students1.stream().allMatch((e)-> e.getStatus() == Status.BUSY));
        log.info("anyMatch结果{}",students1.stream().anyMatch((e)-> e.getStatus() == Status.BUSY));
        log.info("noneMatch结果{}",students1.stream().noneMatch((e)-> e.getStatus() == Status.BUSY));

        Optional<Student> optionalStudent = students1.stream().sorted((e1, e2)->e1.getAge().compareTo(e2.getAge())).findFirst();
        if(optionalStudent.isPresent()) log.info("findFirst结果{}",optionalStudent.get());

        Optional<Student> optionalStudent1 = students1.stream().filter((e)->e.getStatus()==Status.FREE).findAny();
        if(optionalStudent1.isPresent()) log.info("findAny结果{}",optionalStudent1.get());

        log.info("count结果{}",students1.stream().count());

        Optional<Student> optionalStudent2 = students1.stream().max((e1,e2)->e1.getAge().compareTo(e2.getAge()));
        if(optionalStudent2.isPresent()) log.info("max结果{}",optionalStudent2.get());

        Optional<Student> optionalStudent3 = students1.stream().min((e1,e2)->e1.getAge().compareTo(e2.getAge()));
        if(optionalStudent3.isPresent()) log.info("min结果{}",optionalStudent3.get());
    }

  输出结果:

2020-05-29 10:56:39.830  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : allMatch结果false
2020-05-29 10:56:39.832  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : anyMatch结果true
2020-05-29 10:56:39.832  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : noneMatch结果false
2020-05-29 10:56:39.833  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : findFirst结果{"age":8,"name":"lcl6","status":"FREE"}
2020-05-29 10:56:39.936  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : findAny结果{"age":50,"name":"lcl3","status":"FREE"}
2020-05-29 10:56:40.025  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : count结果7
2020-05-29 10:56:40.027  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : max结果{"age":50,"name":"lcl3","status":"FREE"}
2020-05-29 10:56:40.028  INFO 18436 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : min结果{"age":8,"name":"lcl6","status":"FREE"}

   上述代码示例中,有的地方的返回是个Optional,这个下一篇会说明

5、归约:

  reduce(T identity, BinaryOperator) / reduce(BinaryOperator) 可以将流中元素反复结合起来,得到一个流

demo需求:

  将一个集合中的数据,依次相加求和后再与100求和

  将集合中学生的年龄求和后再加10

    @Test
    public void test6(){
        List<Integer> list = Arrays.asList(1,2,3,4,5);
        log.info("reduce结果{}",list.stream().reduce(100, (x,y)->x+y));
        log.info("reduce计算年龄总和结果{}",students1.stream().map((x)->x.getAge()).reduce(10,(x,y)->x+y));
    }

  测试结果:

2020-05-29 10:56:08.448  INFO 17680 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : reduce结果115
2020-05-29 10:56:08.450  INFO 17680 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : reduce计算年龄总和结果191

6、收集:

  collect:将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法,可以计算总和,最大值、最小值、平均值、分组、多重分组、分区和字符串操作等

  由于collect的内容比较多,所以分开一个一个说明

  (1)数据收集转换

  demo:

    将所有的名字获取,并添加到集合中

    将所有的名字获取,并添加到特殊集合中,例如HashSet

        List list = students1.stream().map(Student::getName).collect(Collectors.toList());
        log.info("收集:collect测试结果{}",JSON.toJSONString(list));

        Set set = students1.stream().map(Student::getName).collect(Collectors.toCollection(HashSet::new));
        log.info("收集:特殊集合collect测试结果{}",JSON.toJSONString(set));

  测试结果:

2020-05-29 10:44:37.478  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect测试结果["lcl1","lcl2","lcl3","lcl4","lcl5","lcl6","lcl2"]
2020-05-29 10:44:37.481  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:特殊集合collect测试结果["lcl5","lcl6","lcl3","lcl4","lcl1","lcl2"]

   (2)计算总数、总和、最大值、最小值、平均值等数据收集操作

        log.info("收集:collect总数测试结果{}",students1.stream().collect(Collectors.counting()));

        log.info("收集:collect计算年龄平均值测试结果{}",students1.stream().collect(Collectors.averagingInt(Student::getAge)));

        log.info("收集:collect计算年龄总和测试结果{}",students1.stream().collect(Collectors.summarizingInt(Student::getAge)));

        log.info("收集:collect获取年龄最大测试结果{}",students1.stream().collect(Collectors.maxBy((s1,s2)->s1.getAge().compareTo(s2.getAge()))).get());

        log.info("收集:collect获取最小的年龄值测试结果{}",students1.stream().map(Student::getAge).collect(Collectors.minBy((s1,s2)->s1.compareTo(s2))).get());

测试结果:

2020-05-29 10:44:37.483  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect总数测试结果7
2020-05-29 10:44:37.484  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect计算年龄平均值测试结果25.857142857142858
2020-05-29 10:44:37.485  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect计算年龄总和测试结果IntSummaryStatistics{count=7, sum=181, min=8, average=25.857143, max=50}
2020-05-29 10:44:37.488  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect获取年龄最大测试结果{"age":50,"name":"lcl3","status":"FREE"}
2020-05-29 10:44:37.589  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect获取最小的年龄值测试结果8

 (3)分组

  需求:

    单一分组:按照状态分组

    多重分组:先按照状态分组,再按照年龄分组(以26为界,大于26为中老年,小于26为青少年)

        Map map3 = students1.stream().collect(Collectors.groupingBy(Student::getStatus));
        log.info("收集:collect按照Status分组测试结果{}", JSON.toJSONString(map3));


        Map<Status,Map<String,List<Student>>> map = students1.stream().collect(Collectors.groupingBy(Student::getStatus
                ,Collectors.groupingBy((x)->{
                    if(((Student)x).getAge() > 26){
                        return "中老年";
                    }else{
                        return "青少年";
                    }
                })
        ));
        log.info("收集:collect按照Status分组后按照年龄二次分组测试结果{}", JSON.toJSONString(map));

  测试结果:

2020-05-29 10:44:37.591  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect按照Status分组测试结果{"VOCATION":[{"age":30,"name":"lcl2","status":"VOCATION"}],"FREE":[{"age":50,"name":"lcl3","status":"FREE"},{"age":20,"name":"lcl4","status":"FREE"},{"age":8,"name":"lcl6","status":"FREE"},{"age":30,"name":"lcl2","status":"FREE"}],"BUSY":[{"age":18,"name":"lcl1","status":"BUSY"},{"age":25,"name":"lcl5","status":"BUSY"}]}
2020-05-29 10:44:37.592  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect按照Status分组后按照年龄二次分组测试结果{"VOCATION":{"中老年":[{"age":30,"name":"lcl2","status":"VOCATION"}]},"FREE":{"中老年":[{"age":50,"name":"lcl3","status":"FREE"},{"age":30,"name":"lcl2","status":"FREE"}],"青少年":[{"age":20,"name":"lcl4","status":"FREE"},{"age":8,"name":"lcl6","status":"FREE"}]},"BUSY":{"青少年":[{"age":18,"name":"lcl1","status":"BUSY"},{"age":25,"name":"lcl5","status":"BUSY"}]}}

  (4)使用IntSummaryStatistics计算总数、总和、最大值、最小值、平均值等操作

        IntSummaryStatistics intSummaryStatistics = students1.stream().collect(Collectors.summarizingInt(Student::getAge));
        log.info("收集:collect按照年龄平均值{},总数{},最大值{},最小值{},总和{}", intSummaryStatistics.getAverage(),intSummaryStatistics.getCount(),intSummaryStatistics.getMax(),intSummaryStatistics.getMin(),intSummaryStatistics.getSum());

  测试结果:

2020-05-29 10:44:37.595  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect按照年龄平均值25.857142857142858,总数7,最大值50,最小值8,总和181

  (5)字符串操作

        String s = students1.stream().map(Student::getName).collect(Collectors.joining("=="));
        log.info("收集:collect连接字符串测试结果{}", s);

  测试结果:

2020-05-29 10:44:37.595  INFO 19600 --- [           main] com.example.jdk8demo.Jdk8StreamDemoTest  : 收集:collect连接字符串测试结果lcl1==lcl2==lcl3==lcl4==lcl5==lcl6==lcl2
原文地址:https://www.cnblogs.com/liconglong/p/12986255.html