Stream流是Java8提供的一个新特性,这个有什么新大陆发现呢,我们先看一个例子

以下内容要先有Lambda表达式基础,不清楚Lambda表达式的可以看这个

我们以下的例子都是基于这个学生类Student来操作,下面是学生类Student的代码

学生属性有:编号,名字,年龄,数学成绩,语文成绩,重写toString方法,重写equals和hashCode方法,编号一样就是同一个人

package com.TestStream;

/**
 * @author 林高禄
 * @create 2020-06-04-16:47
 */
public class Student {
    private Integer no;
    private String name;
    private Integer age;
    private Double mathScore;
    private Double chineseScore;

    public Student(Integer no, String name, Integer age, Double mathScore, Double chineseScore) {
        this.no = no;
        this.name = name;
        this.age = age;
        this.mathScore = mathScore;
        this.chineseScore = chineseScore;
    }

    public Integer getNo() {
        return no;
    }

    public void setNo(Integer no) {
        this.no = no;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public Double getMathScore() {
        return mathScore;
    }

    public void setMathScore(Double mathScore) {
        this.mathScore = mathScore;
    }

    public Double getChineseScore() {
        return chineseScore;
    }

    public void setChineseScore(Double chineseScore) {
        this.chineseScore = chineseScore;
    }

    @Override
    public String toString() {
        return "Student{" +
                "no=" + no +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", mathScore=" + mathScore +
                ", chineseScore=" + chineseScore +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        return no != null ? no.equals(student.no) : student.no == null;
    }

    @Override
    public int hashCode() {
        return no != null ? no.hashCode() : 0;
    }
}

为了方便代码的复用,就弄了一个学生的工具类StudentUtil,来生成学生的列表,代码为

 

package com.TestStream;

import java.util.ArrayList;
import java.util.List;

/**
 * @author 林高禄
 * @create 2020-06-04-17:18
 */
public class StudentUtil {
    /**
     * 生成指定的学生类的列表,用于测试
     *
     * @return
     */
    public static List<Student> createStudentList() {
        List<Student> studentList = new ArrayList<>();
        studentList.add(new Student(3, "林高禄", 18, 95.5, 68.0));
        studentList.add(new Student(2, "徐辉强", 19, 99.0, 98.0));
        studentList.add(new Student(1, "吴忠威", 23, 86.0, 78.5));
        studentList.add(new Student(17, "东方不败", 16, 54.0, 47.0));
        studentList.add(new Student(9, "西方失败", 45, 94.5, 92.0));
        studentList.add(new Student(5, "任我行", 29, 75.0, 97.0));
        studentList.add(new Student(9, "独孤求败", 45, 98.5, 86.0));
        return studentList;
    }

    /**
     * 打印学生列表里的学生信息
     *
     * @param studentList
     */
    public static void printList(List<Student> studentList) {
        for (Student s : studentList) {
            System.out.println(s);
        }
    }
}

筛选出数学成绩为90分以上(含90分)的学生,并且分数按从高的到低排序打印

按照以前的做法,我们的代码如下

package com.TestStream;

import java.util.*;

/**
 * @author 林高禄
 * @create 2020-06-04-16:51
 */
public class BeforeDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        // 筛选出数学成绩为90分以上(含90分)的学生,并且分数按从高的到低排序打印
        System.out.println("-------原数据------");
        StudentUtil.printList(studentList);
        // 1、筛选出数学成绩为90分以上(含90分)的学生
        List<Student> newList = new ArrayList<>();
        for(Student s:studentList){
            if(s.getMathScore()>=90){
                newList.add(s);
            }
        }
        System.out.println("-------成绩为90分以上(含90分)的数据------");
        StudentUtil.printList(newList);
        //  2、分数按从高的到低排序
        newList.sort(new Comparator<Student>() {
            @Override
            public int compare(Student s1, Student s2) {
                return s2.getMathScore().compareTo(s1.getMathScore());
            }
        });
        // 3、打印
        System.out.println("-------分数按从高的到低的数据------");
        StudentUtil.printList(newList);
    }

}

输出:

-------原数据------
Student{no=3, name='林高禄', age=18, mathScore=95.5, chineseScore=68.0}
Student{no=2, name='徐辉强', age=19, mathScore=99.0, chineseScore=98.0}
Student{no=1, name='吴忠威', age=23, mathScore=86.0, chineseScore=78.5}
Student{no=17, name='东方不败', age=16, mathScore=54.0, chineseScore=47.0}
Student{no=9, name='西方失败', age=45, mathScore=94.5, chineseScore=92.0}
Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}
Student{no=9, name='独孤求败', age=45, mathScore=98.5, chineseScore=86.0}
-------成绩为90分以上(含90分)的数据------
Student{no=3, name='林高禄', age=18, mathScore=95.5, chineseScore=68.0}
Student{no=2, name='徐辉强', age=19, mathScore=99.0, chineseScore=98.0}
Student{no=9, name='西方失败', age=45, mathScore=94.5, chineseScore=92.0}
Student{no=9, name='独孤求败', age=45, mathScore=98.5, chineseScore=86.0}
-------分数按从高的到低的数据------
Student{no=2, name='徐辉强', age=19, mathScore=99.0, chineseScore=98.0}
Student{no=9, name='独孤求败', age=45, mathScore=98.5, chineseScore=86.0}
Student{no=3, name='林高禄', age=18, mathScore=95.5, chineseScore=68.0}
Student{no=9, name='西方失败', age=45, mathScore=94.5, chineseScore=92.0}

从这个例子,我们可以看出,这只是一个小小的需求,我们就要做很多操作,写很多代码,要遍历赛选,还要排序,还要遍历打印等等

那么我们就先体验一下Stream的写法(这个看不懂没关系,这只是体验,下面会讲到如何使用)

package com.TestStream;

import java.util.*;

/**
 * @author 林高禄
 * @create 2020-06-04-16:51
 */
public class StreamDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().filter(s -> s.getMathScore() >= 90)
                .sorted((s1, s2) -> s2.getMathScore().compareTo(s1.getMathScore()))
                .forEach(System.out::println);
    }
}

运行输出:

Student{no=2, name='徐辉强', age=19, mathScore=99.0, chineseScore=98.0}
Student{no=9, name='独孤求败', age=45, mathScore=98.5, chineseScore=86.0}
Student{no=3, name='林高禄', age=18, mathScore=95.5, chineseScore=68.0}
Student{no=9, name='西方失败', age=45, mathScore=94.5, chineseScore=92.0}

有没有发现我们的代码简洁了很多,并且也很方便,那么我们下面就讲讲Stream如何使用

Stream流的使用

  • 生成流:通过数据源(集合,数组等)生成流,比如:list.stream()

  • 中间操作:一个流后面可以跟随零个或多个中间操作,其目的主要是打开流,做出某种程度的数据过滤、映射,然后返回一个新的流,交给下一个操作使用,比如:filter()

  • 终结操作:一个流只能有一个终结操作,当这个操作哦执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作,比如:forEach()

生成流:

  • Collection体系的集合可以使用默认方法stream()生成流,default Stream<E> stream()

Stream流(Stream,Lambda)_集合操作

  • Map体系的集合间接的生成流
  • 数组可以通过Stream接口的静态方法of(T...values)生成流

Stream流(Stream,Lambda)_Stream_02

 

代码实例

package com.TestStream;

import java.util.*;
import java.util.stream.Stream;

/**
 * @author 林高禄
 * @create 2020-06-05-8:00
 */
public class CreateStreamDemo {
    public static void main(String[] args) {
        //Collection体系的集合可以使用默认方法strean()生成流
        List<Student> studentList = new ArrayList<>();
        Stream<Student> listStream = studentList.stream();

        Set<Student> studentSet = new HashSet<>();
        Stream<Student> seStream = studentSet.stream();

        // Map体系的集合间接的生成流
        Map<String, Student> studentMap = new HashMap<>();
        Stream<String> keyStream = studentMap.keySet().stream();   //.keySet()返回的是一个set集合
        Stream<Student> valuesStream = studentMap.values().stream();    //.values()返回的是一个Collection集合
        Stream<Map.Entry<String, Student>> entryStream = studentMap.entrySet().stream();    //.entrySet()返回的是一个set集合

        // 数组可以通过Stream接口的静态方法of(T...values)生成流
        Student[] stuArray = {
                new Student(3, "林高禄", 18, 95.5, 68.0),
                new Student(2, "徐辉强", 19, 99.0, 98.0),
                new Student(1, "吴忠威", 23, 86.0, 78.5)
        };
        Stream<Student> stuArrayStream = Stream.of(stuArray);

        // 当然Collection体系也能通过这种方式生成流
        Stream<List<Student>> studentList1 = Stream.of(studentList);
        Stream<Set<Student>> studentSet1 = Stream.of(studentSet);
        // 但是注意,这样生成的流是List<Student>的,而不是Student的
        studentList1.filter(s -> s.size() > 5);     // 这里的s指代的就是list,而不是student
    }
}

Stream流的常见中间操作方法

这里涉及函数式接口

  • Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤

        Predicate接口中的方法:boolean test(T t),对给定的参数进行判断,返回一个布尔值

  • Stream<T> limit(long maxSize):返回此流中元素组成的流,截取前指定参数个数的数据组成的流
  • Stream<T> skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
  • static<T> Stream<T> concat(Stream a,Stream b):合并a和b两个流为一个流
  • Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object)和hashCode())组成的流,去重
  • Stream<T> soeted()返回由此流的元素组成的流,根据自然顺序排序
  • Stream<T> soeted(Comparator comparator):返回返回由此流的元素组成的流,根据提供的Comparator进行排序
  • <R> Stream<R> map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流

         Function接口中的方法:R apply(T t)

  • DoubleStream mapToDouble(ToDoubleFunction mapper):返回一个DoubleStream 其中包含将给定函数应用此流的元素结果

         DoubleStream :表示原始double流,ToDoubleFunction 接口中的方法:int applyAsDouble(T value)

  • Stream<T> filter(Predicate predicate):用于对流中的数据进行过滤

        Predicate接口中的方法:boolean test(T t),对给定的参数进行判断,返回一个布尔值

例子:赛选出语文成绩大于等于90分的学生,并且打印出来

package com.TestStream;

import java.util.List;

/**
 * @author 林高禄
 * @create 2020-06-05-9:50
 */
public class FilterDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().filter(s -> s.getChineseScore() >= 90).forEach(System.out::println);
    }
}

运行输出:

Student{no=2, name='徐辉强', age=19, mathScore=99.0, chineseScore=98.0}
Student{no=9, name='西方失败', age=45, mathScore=94.5, chineseScore=92.0}
Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}

Stream<T> limit(long maxSize):返回此流中元素组成的流,截取前指定参数个数的数据组成的流

例子:输出列表中前2个学生信息

package com.TestStream;

import java.util.List;

/**
 * @author 林高禄
 * @create 2020-06-05-10:05
 */
public class LimitDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().limit(2).forEach(System.out::println);
    }
}

运行输出:

Student{no=3, name='林高禄', age=18, mathScore=95.5, chineseScore=68.0}
Student{no=2, name='徐辉强', age=19, mathScore=99.0, chineseScore=98.0}

Stream<T> skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流

例子:输出列表中最后2个学生信息

package com.TestStream;

import java.util.List;

/**
 * @author 林高禄
 * @create 2020-06-05-10:05
 */
public class SkipDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().skip(studentList.size() - 2).forEach(System.out::println);
    }
}

运行输出:

Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}
Student{no=9, name='独孤求败', age=45, mathScore=98.5, chineseScore=86.0}

static<T> Stream<T> concat(Stream a,Stream b):合并a和b两个流为一个流

例子:将学生列表中的前4名的信息记录,再把后5名的信息记录,打印这些全部记录信息(我们的列表只有7个学生)

package com.TestStream;

import java.util.List;
import java.util.stream.Stream;

/**
 * @author 林高禄
 * @create 2020-06-05-10:18
 */
public class ConcatDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        Stream<Student> limitStream = studentList.stream().limit(4);
        Stream<Student> skipStream = studentList.stream().skip(studentList.size() - 5);
        Stream.concat(limitStream, skipStream).forEach(System.out::println);
    }
}

运行输出:

Student{no=3, name='林高禄', age=18, mathScore=95.5, chineseScore=68.0}
Student{no=2, name='徐辉强', age=19, mathScore=99.0, chineseScore=98.0}
Student{no=1, name='吴忠威', age=23, mathScore=86.0, chineseScore=78.5}
Student{no=17, name='东方不败', age=16, mathScore=54.0, chineseScore=47.0}
Student{no=1, name='吴忠威', age=23, mathScore=86.0, chineseScore=78.5}
Student{no=17, name='东方不败', age=16, mathScore=54.0, chineseScore=47.0}
Student{no=9, name='西方失败', age=45, mathScore=94.5, chineseScore=92.0}
Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}
Student{no=9, name='独孤求败', age=45, mathScore=98.5, chineseScore=86.0}

发现没有,吴忠威和东方不败这2个学生是重复的信息,concat合并流的时候并没有去重,那么我们下面就来看一个去重的方法

Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object))组成的流,去重

例子:将学生列表中的前4名的调去新班,再把后5名也调去新班,打印新班的学生信心(我们的列表只有7个学生)

package com.TestStream;

import java.util.List;
import java.util.stream.Stream;

/**
 * @author 林高禄
 * @create 2020-06-05-10:18
 */
public class DistinctDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        Stream<Student> limitStream = studentList.stream().limit(4);
        Stream<Student> skipStream = studentList.stream().skip(studentList.size() - 5);
        Stream.concat(limitStream, skipStream).distinct().forEach(System.out::println);
    }
}

运行输出:

Student{no=3, name='林高禄', age=18, mathScore=95.5, chineseScore=68.0}
Student{no=2, name='徐辉强', age=19, mathScore=99.0, chineseScore=98.0}
Student{no=1, name='吴忠威', age=23, mathScore=86.0, chineseScore=78.5}
Student{no=17, name='东方不败', age=16, mathScore=54.0, chineseScore=47.0}
Student{no=9, name='西方失败', age=45, mathScore=94.5, chineseScore=92.0}
Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}

去重之后,合并的9条数据中,吴忠威和东方不败这2个学生是重复的信息去掉了,但是我们发现独孤求败这个学生的信息也不见了,这是因为我们的学生类重写了equals和hashCode的方法,编号一样的学生就为同一个对象,而独孤求败这个学生的编号我9,编号9已被学生西方失败使用了,所以判定独孤求败这个学生重复了,所以去掉了。

Stream<T> soeted()返回由此流的元素组成的流,根据自然顺序排序

例子:将学生按自然顺序排序

package com.TestStream;

import java.util.List;

/**
 * @author 林高禄
 * @create 2020-06-05-10:18
 */
public class SortDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().sorted().forEach(System.out::println);
    }
}

运行输出:

Exception in thread "main" java.lang.ClassCastException: com.TestStream.Student cannot be cast to java.lang.Comparable
    at java.util.Comparators$NaturalOrderComparator.compare(Comparators.java:47)
    at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)
    at java.util.TimSort.sort(TimSort.java:220)
    at java.util.Arrays.sort(Arrays.java:1512)
    at java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:348)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
    at com.TestStream.SortDemo.main(SortDemo.java:12)

报错了,为何,按自然排序到底是按什么排序啊?因为我们的学生类没有实现Comparable接口,没有重写排序方法,所以报错,所以我们就用另一个排序的方法,指定的Comparator进行排序,往下看

Stream<T> soeted(Comparator comparator):返回返回由此流的元素组成的流,根据提供的Comparator进行排序

例子:将学生按编号排序输出

package com.TestStream;

import java.util.Comparator;
import java.util.List;

/**
 * @author 林高禄
 * @create 2020-06-05-10:18
 */
public class SortDemo2 {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().sorted(Comparator.comparing(Student::getNo)).forEach(System.out::println);
    }
}

运行输出:

Student{no=1, name='吴忠威', age=23, mathScore=86.0, chineseScore=78.5}
Student{no=2, name='徐辉强', age=19, mathScore=99.0, chineseScore=98.0}
Student{no=3, name='林高禄', age=18, mathScore=95.5, chineseScore=68.0}
Student{no=5, name='任我行', age=29, mathScore=75.0, chineseScore=97.0}
Student{no=9, name='西方失败', age=45, mathScore=94.5, chineseScore=92.0}
Student{no=9, name='独孤求败', age=45, mathScore=98.5, chineseScore=86.0}
Student{no=17, name='东方不败', age=16, mathScore=54.0, chineseScore=47.0}

  • <R> Stream<R> map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流

         Function接口中的方法:R apply(T t)

例子1:将学生的姓名和数学分数输出:

package com.TestStream;

import java.util.List;

/**
 * @author 林高禄
 * @create 2020-06-05-10:18
 */
public class MapDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        studentList.stream().map(s -> s.getName() + s.getMathScore()).forEach(System.out::println);
    }
}

运行输出:

林高禄95.5
徐辉强99.0
吴忠威86.0
东方不败54.0
西方失败94.5
任我行75.0
独孤求败98.5

  • DoubleStream mapToDouble(ToDoubleFunction mapper):返回一个DoubleStream 其中包含将给定函数应用此流的元素结果

         DoubleStream :表示原始double流,ToDoubleFunction 接口中的方法:int applyAsDouble(T value)

 例子:求出所有人的数学成绩的总和

package com.TestStream;

import java.util.List;

/**
 * @author 林高禄
 * @create 2020-06-05-10:18
 */
public class MapToDoubleDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        double sum = studentList.stream().mapToDouble(Student::getMathScore).sum();
        System.out.println(sum);

    }
}

运行输出:

602.5

mapToIntmapToLong也是和mapToDouble一样的运用。

Stream流的终结操作

  • void forEach(Consumer action):对此流的每个元素执行操作

        Consumer接口中的方法:void accept(T t),对给定的参数执行此操作

  • <R, A> R collect(Collector<? super T, A, R> collector),收集流为指定的集合类型
  • long count():返回此流中的元素个数
  • void forEach(Consumer action):对此流的每个元素执行操作

        Consumer接口中的方法:void accept(T t),对给定的参数执行此操作

这个我们上面的例子一直再使用。用于输出学生的信息,就不再举例

<R, A> R collect(Collector<? super T, A, R> collector),收集流为指定的集合类型

 例子:取出前3名学生为一个新的集合,并且打印

package com.TestStream;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @author 林高禄
 * @create 2020-06-05-10:18
 */
public class CollectDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        List<Student> collect = studentList.stream().limit(3).collect(Collectors.toList());
        StudentUtil.printList(collect);
    }
}

运行输出:

Student{no=3, name='林高禄', age=18, mathScore=95.5, chineseScore=68.0}
Student{no=2, name='徐辉强', age=19, mathScore=99.0, chineseScore=98.0}
Student{no=1, name='吴忠威', age=23, mathScore=86.0, chineseScore=78.5}

long count():返回此流中的元素个数

例子:统计语文分数大于等于90分的学生的个数

package com.TestStream;

import java.util.List;

/**
 * @author 林高禄
 * @create 2020-06-05-10:18
 */
public class CountDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        long count = studentList.stream().filter(s -> s.getChineseScore() >= 90).count();
        System.out.println(count);
    }
}

运行输出:

3

到这里,Stream基本上就介绍完了,我们做一个综合的例子回顾一下

例子:打印出总分排名第三的学生信息

思路有很多,我的思路:

A:1、进行总分倒序排名;2、去掉前面2个;3、取前2步得出的第一个;4、打印

B:1、进行总分倒序排名;2、取前3个;3、取前2步得出的最后一个;4、打印

package com.TestStream;

import java.util.List;

/**
 * @author 林高禄
 * @create 2020-06-05-10:18
 */
public class AllDemo {
    public static void main(String[] args) {
        List<Student> studentList = StudentUtil.createStudentList();
        System.out.println("------思路A------");
        studentList.stream().sorted((s1,s2)->new Double(s2.getMathScore()+s2.getChineseScore()).compareTo(new Double(s1.getMathScore()+s1.getChineseScore())))
                .skip((3-1)).limit(1).forEach(System.out::println);
        System.out.println("------分割线------");
        System.out.println("------思路B------");
        studentList.stream().sorted((s1,s2)->new Double(s2.getMathScore()+s2.getChineseScore()).compareTo(new Double(s1.getMathScore()+s1.getChineseScore())))
               .limit(3).skip((3-1)).forEach(System.out::println);
    }
}

运行输出:

------思路A------
Student{no=9, name='独孤求败', age=45, mathScore=98.5, chineseScore=86.0}
------分割线------
------思路B------
Student{no=9, name='独孤求败', age=45, mathScore=98.5, chineseScore=86.0}

结果是对的,大家可以自己验证。