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()
- Map体系的集合间接的生成流
- 数组可以通过Stream接口的静态方法of(T...values)生成流
代码实例
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}
结果是对的,大家可以自己验证。