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)_Lambda_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



mapToInt​和​mapToLong​也是和​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}



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