文章目录
StreamApi简述
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。
流的执行过程
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
样例
对于一个学生的列表,先过滤体育不及格的学生,再对其三门成绩进行权重相加得到总分,根据总分进行排序,并取到前三名成绩的信息。
用原有for循环的方法写,可能需要写很长的一段代码。
stream方式:
List<Student> studentList1 = studentList.stream()
.filter(t -> t.getSport() < 60)
.peek(t -> t.setTotalScore(t.getMath()*1.2+t.getChinese()+t.getEnglish()*0.8))
.sorted(Comparator.comparing(Student::getTotalScore).reversed())
.limit(10)
.collect(Collectors.toList());
流的来源
集合,数组,I/O channel, 产生器generator 等
流的操作类型分为两种:
- Intermediate(中间操作):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
- Terminal(终止操作):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历
常用中间操作
map
转换操作,将A类型转换为B类型。
1对1映射,一个元素转为另一个元素
如:根据学生信息列表获取所有学生id
List<Integer> idList = studentList.stream().map(Student::getId).collect(Collectors.toList());
flatmap
拍平操作 把 Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起
一对多映射 一个元素转为多个元素
// 对数组中所有字母去重。
String[] words = new String[]{"Hello","World"};
List<String> wordsSplit = Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
结果为: [“H”,“e”,“l”,“o”,“W”,“r”,“d”]
limit
limit 方法用于获取指定数量的流
见样例
sorted
sorted 方法用于对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。
见样例
filter
过滤,用于通过设置的条件过滤出元素
见样例
distinct
去除重复的元素,底层使用了equals方法。
如对id去重
List<Integer> idListDistinct = idList.stream().distinct().collect(Collectors.toList());
peek
对选择的元素执行操作,如:读取、编辑修改等。并返回新的stream
见样例
skip
跳过前面的几个元素
List<Integer> skipIdList = idList.stream().skip(10).collect(toList());
常用终止操作
collect
收集操作,将所有数据收集起来。官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors
如:
其他样例最基本的 转为list
根据字段分组
//根据所在城市分组
Map<String,List<Student>> cityMap = studentList.stream().collect(groupingBy(Student :: getCity));
根据是否满足某一条件分组
//根据语文是否>90分组
Map<Boolean,List<Student>> chineseGood = studentList.stream().collect(partitioningBy(t -> t.getChinese() > 90));
System.out.println("语文分数>90的人数为: " + chineseGood.get(true).size());
System.out.println("语文分数<90的人数为: " + chineseGood.get(false).size());
从对象中取出特点属性组成map
//构建成 学号->总分map,排除重复值
Map<Integer,Double> studentMap = topStudentList.stream().collect(Collectors.toMap(Student :: getId,Student :: getTotalScore,(key1,key2) -> key2));
将list转化为string,并可指定分隔符,前缀后缀
//取学生名字拼接为字符串,以,分割,前缀为[,后缀为]
String nameStr = studentList.stream().map(Student::getName).collect(Collectors.joining(",","[","]"));
转化为其他collection类型:
转为为 stack及set:
//id转为栈
Stack idStack = idList.stream().collect(Collectors.toCollection(Stack :: new));
//id转为set
Set idSet = idList.stream().collect(Collectors.toSet());
其他~~
count
统计操作,统计最终的数据个数
//体育不及格的人数
Long count = studentList.stream().filter(t -> t.getSport() < 60).count();
findFirst、findAny
查找操作,查找第一个、查找任何一个 返回的类型为Optional
如果是数据较少,串行地情况下,一般会返回第一个结果,如果是并行的情况,那就不能确保是第一个
//随机获取一个学生元素
Optional<Student> studentOptionalRandom = studentList.stream().findAny();
//获取第一个学生元素
Optional<Student> studentOptionalFirst = studentList.stream().findFirst();
noneMatch、allMatch、anyMatch
匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。
//是否有学生语文>95分
Boolean anyChineseBest = studentList.stream().anyMatch(t -> t.getChinese() > 95);
//是否所有学生体育都及格
Boolean allSportPass = studentList.stream().allMatch(t -> t.getSport() >= 60);
//是否没有学生体育不及格
Boolean noneSportFail = studentList.stream().noneMatch(t -> t.getSport() < 60);
min、max
最值操作,需要自定义比较器,返回数据流中最大最小的值
//体育最好的学生
Optional<Student> maxSportStudent = studentList.stream().max(Comparator.comparing(Student::getSport));
//体育最差的学生
Optional<Student> minSportStudent = studentList.stream().min(Comparator.comparing(Student::getSport));
reduce
规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。
//获取前十名的总分之和
Double sumTotalCounnt = topStudentList.stream().map(Student::getTotalScore).reduce(0.0,Double :: sum);
forEach、forEachOrdered
遍历操作
//打印每个学生的姓名及城市
studentList.forEach(t -> System.out.println("name:" + t.getName() + " city:" + t.getCity()));
并行parallel
parallelStream 是流并行处理程序的代替方法。在生成流的时候使用 parallelStream()进行流的获取
底层使用fork/join 框架进行处理
数据量较少时,for循环,串行流,并行流性能差异不大。
数据量特别多时,并行流可能发生阻塞,for循环的效率更好。
如何选用请跟进业务实际情况判断。
调试插件 Java Stream Debugger
使用idea插件可查看中间操作的结果
在stream中打个断点,使用debug模式调试
单步模式:
全局概览模式:
测试源码
Student类
import lombok.Getter;
import lombok.Setter;
/**
* @date: 2020-05-13 14:39
* @author: bufang
* @description:
*/
@Setter
@Getter
public class Student {
private Integer id;
private String name;
private Double totalScore;
private Double math;
private Double chinese;
private Double sport;
private Double english;
private String city;
}
StreamApiTest
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.partitioningBy;
import static java.util.stream.Collectors.toList;
/**
* @date: 2020-05-13 11:22
* @author: bufang
* @description:java8 streamApi 测试类
*/
public class StreamApiTest {
private static List<Student> studentList = Lists.newArrayList();
/**
* 测试列表初始化
* 成绩区间 50-100
* 来源城市4个
*/
private static void initList(){
List<String> cities = Lists.newArrayList();
cities.add("杭州");
cities.add("湖州");
cities.add("其他");
cities.add("宁波");
for (int i =0 ; i<50 ; i++){
Student student = new Student();
student.setId(i);
student.setName("小" + i);
student.setSport((Math.random( )*50+50));
student.setCity(cities.get(i%4));
student.setMath((Math.random( )*50+50));
student.setEnglish((Math.random( )*50+50));
student.setChinese((Math.random( )*50+50));
studentList.add(student);
studentList.add(student);
}
}
public static void main(String[] args) {
initList();
//对于一个学生的列表,先过滤体育不及格的学生,再对其三门成绩进行权重相加得到总分,根据总分进行排序,并取到前十名成绩的信息。
//System.out.println("开始计时 当前时间为 " + DateUtil.getFormantDate(new Date(),DATA_FORMANT15));
List<Student> topStudentList = studentList.stream()
.filter(t -> t.getSport() < 60)
.peek(t -> t.setTotalScore(t.getMath()*1.2+t.getChinese()+t.getEnglish()*0.8))
.sorted(Comparator.comparing(Student::getTotalScore).reversed())
.limit(10)
.collect(toList());
//List<Student> topStudentList = studentList.parallelStream()
// .filter(t -> t.getSport() < 60)
// .peek(t -> t.setTotalScore(t.getMath()*1.2+t.getChinese()+t.getEnglish()*0.8))
// .sorted(Comparator.comparing(Student::getTotalScore).reversed())
// .limit(10)
// .collect(Collectors.toList());
//System.out.println("结束计时 当前时间为 " + DateUtil.getFormantDate(new Date(),DATA_FORMANT15));
//解析json不会进行引用显示
//System.out.println("前十名学生对象为: " + JSON.toJSONString(topStudentList, SerializerFeature.DisableCircularReferenceDetect));
System.out.println("前十名学生对象为: " + JSON.toJSONString(topStudentList));
//获取前十名的总分之和
Double sumTotalCounnt = topStudentList.stream().map(Student::getTotalScore).reduce(0.0,Double :: sum);
System.out.println("\n前十名学生总分之和为: " + sumTotalCounnt);
//构建成 学号->总分map
Map<Integer,Double> studentMap = topStudentList.stream().collect(Collectors.toMap(Student :: getId,Student :: getTotalScore,(key1,key2) -> key2));
System.out.println("\n前十名id及总分为: " + JSON.toJSONString(studentMap));
//根据所在城市分组
Map<String,List<Student>> cityMap = studentList.stream().collect(groupingBy(Student :: getCity));
System.out.println("\n根据城市分组后map为: " + JSON.toJSONString(cityMap));
//根据语文是否>90分组
Map<Boolean,List<Student>> chineseGood = studentList.stream().collect(partitioningBy(t -> t.getChinese() > 90));
System.out.println("\n语文分数>90的人数为: " + chineseGood.get(true).size());
System.out.println("\n语文分数<90的人数为: " + chineseGood.get(false).size());
//获取所有学生id
List<Integer> idList = studentList.stream().map(Student::getId).collect(toList());
System.out.println("\n所有学生id为: " + JSON.toJSONString(idList));
//id去重
List<Integer> idListDistinct = idList.stream().distinct().collect(toList());
System.out.println("\n去重后学生id为: " + JSON.toJSONString(idListDistinct));
//跳过前10个学生id
List<Integer> skipIdList = idList.stream().skip(10).collect(toList());
System.out.println("\n第10个之后的id为: " + JSON.toJSONString(skipIdList));
//flatmap
String[] words = new String[]{"Hello","World"};
List<String> wordsSplit = Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
System.out.println("\nflatMap 后" + JSON.toJSONString(wordsSplit));
//取学生名字拼接为字符串,以,分割,前缀为[,后缀为]
String nameStr = studentList.stream().map(Student::getName).collect(Collectors.joining(",","[","]"));
System.out.println("\n所有学生姓名为 " + nameStr);
//id转为栈
Stack idStack = idList.stream().collect(Collectors.toCollection(Stack :: new));
System.out.println("\nidStack " + JSON.toJSONString(idStack));
//id转为set
Set idSet = idList.stream().collect(Collectors.toSet());
System.out.println("\nidSet " + JSON.toJSONString(idSet));
//体育不及格的人数
Long count = studentList.stream().filter(t -> t.getSport() < 60).count();
System.out.println("\n体育不及格人数为: " + count);
//随机获取一个学生元素
Optional<Student> studentOptionalRandom = studentList.parallelStream().findAny();
System.out.println("\n随机获取到的学生姓名为 : " + studentOptionalRandom.get().getName());
//获取第一个学生元素
Optional<Student> studentOptionalFirst = studentList.stream().findFirst();
System.out.println("\n第一个学生姓名为 : " + studentOptionalFirst.get().getName());
//是否有学生语文>95分
Boolean anyChineseBest = studentList.stream().anyMatch(t -> t.getChinese() > 95);
//是否所有学生体育都及格
Boolean allSportPass = studentList.stream().allMatch(t -> t.getSport() >= 60);
//是否没有学生体育不及格
Boolean noneSportFail = studentList.stream().noneMatch(t -> t.getSport() < 60);
System.out.println("\n是否有学生语文>95 : " + anyChineseBest);
System.out.println("\n是否所有学生体育都及格 : " + allSportPass);
System.out.println("\n是否没有学生体育不及格 : " + noneSportFail);
//体育最好的学生
Optional<Student> maxSportStudent = studentList.stream().max(Comparator.comparing(Student::getSport));
//体育最差的学生
Optional<Student> minSportStudent = studentList.stream().min(Comparator.comparing(Student::getSport));
System.out.println("\n体育最好的学生为 : " + JSON.toJSONString(maxSportStudent));
System.out.println("\n体育最差的学生为 : " + JSON.toJSONString(minSportStudent));
//打印每个学生的姓名及城市
studentList.forEach(t -> System.out.println("name:" + t.getName() + " city:" + t.getCity()));
}
}