Stream 流
背景介绍
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
@ApiModelProperty("学生id")
public Integer id;
@ApiModelProperty("学生姓名")
public String name;
@ApiModelProperty("学生年龄")
public Integer age;
@ApiModelProperty("学生生日")
public Date birthday;
@ApiModelProperty("学生年级")
public String grade;
private static final long serialVersionUID = 1L;
}
添加数据
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// 模拟数据
ArrayList<Student> students = new ArrayList<>();
students.add(new Student(1, "张三", 20, dateFormat.parse("2020-10-01"), "高一"));
students.add(new Student(2, "萧炎", 22, dateFormat.parse("2020-10-02"), "高二"));
students.add(new Student(3, "唐三", 18, dateFormat.parse("2020-09-10"), "高一"));
students.add(new Student(4, "牧尘", 20, dateFormat.parse("2020-05-20"), "高二"));
students.add(new Student(5, "林动", 16, dateFormat.parse("2020-08-09"), "高三"));
Stream对象的创建
Stream对象分为两种,一种串行的流对象,一种并行的流对象。
// permissionList指所有权限列表
// 为集合创建串行流对象
Stream<Student> stream = students.stream();
// 为集合创建并行流对象
Stream<Student> parallelStream = students.parallelStream();
forEach
Stream 提供了新的方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
filter
对Stream中的元素进行过滤操作,当设置条件返回true时返回相应元素。
// 获取权限类型为目录的权限
List<UmsPermission> dirList = permissionList.stream()
.filter(permission -> permission.getType() == 0)
.collect(Collectors.toList());Copy to clipboardErrorCopied
map
对Stream中的元素进行转换处理后获取。比如可以将UmsPermission对象转换成Long对象。 我们经常会有这样的需求:需要把某些对象的id提取出来,然后根据这些id去查询其他对象,这时可以使用此方法。
// 获取所有权限的id组成的集合
List<Long> idList = permissionList.stream()
.map(permission -> permission.getId())
.collect(Collectors.toList());Copy to clipboardErrorCopied
List<String> collect = joinCircles.stream().filter(joinCircle -> joinCircle.getUserSignin() < 10).distinct()
.map(JoinCircle::getUserId).collect(Collectors.toList());
limit
从Stream中获取指定数量的元素。
// 获取前5个权限对象组成的集合
List<UmsPermission> firstFiveList = permissionList.stream()
.limit(5)
.collect(Collectors.toList());Copy to clipboardErrorCopied
count
仅获取Stream中元素的个数。
// count操作:获取所有目录权限的个数
long dirPermissionCount = permissionList.stream()
.filter(permission -> permission.getType() == 0)
.count();Copy to clipboardErrorCopied
sorted
对Stream中元素按指定规则进行排序。
对单个属性排序:
- 根据年龄升序排序
students.stream()
.sorted(Comparator.comparing(Student::getAge))
.collect(Collectors.toList());
- 根据年龄降序排序(先升序,后逆序)
students.stream()
.sorted(Comparator.comparing(Student::getAge).reversed())
.collect(Collectors.toList());
这个是先根据年龄升序排序,然后利用reversed()逆序;
- 根据年龄降序排序(直接逆序)
students.stream()
.sorted(Comparator.comparing(Student::getAge,Comparator.reverseOrder()))
.collect(Collectors.toList());
利用Comparator.reverseOrder()直接就是降序排序
对多个属性排序
- 根据年龄降序,生日升序
students.stream()
.sorted(Comparator.comparing(Student::getAge).reversed()
.thenComparing(Student::getBirthday))
.collect(Collectors.toList());
students.stream()
.sorted(Comparator.comparing(Student::getAge,Comparator.reverseOrder())
.thenComparing(Student::getBirthday))
.collect(Collectors.toList());
第一种是先按照年龄升序,然后逆序,第二种则是直接按照年龄降序
- 根据年龄降序,生日降序
students.stream()
.sorted(Comparator.comparing(Student::getAge)
.thenComparing(Student::getBirthday).reversed())
.collect(Collectors.toList());
这里要明白为什么只是用了一次reversed()年龄为什么也逆序了,reversed()的作用域是reversed()前面的所有的排序,也就是作用域为年龄和生日,如果想按照**年龄升序,生日降序:**则在年龄后面在加上一个reversed()逆序两次也就是升序了
students.stream()
.sorted(Comparator.comparing(Student::getAge,Comparator.reverseOrder())
.thenComparing(Student::getBirthday,Comparator.reverseOrder()))
.collect(Collectors.toList());
Comparator自定义比较器:
int compare(Object o1, Object o2);
1、比较者大于被比较者,那么返回正整数
2、比较者等于被比较者,那么返回0
3、比较者小于被比较者,那么返回负整数
定义一个类实现Comparator接口
public class StudentComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
// 这里可以自己定义复杂的排序算法
return o1.getAge() - o2.getAge();
}
}
使用该排序器
students.stream().sorted(new StudentComparator()).collect(Collectors.toList());
自定义比较复杂的排序算法,利用stream流排序(可以去详细了解stream流)分页获取数据例如:
students.stream().sorted(newStudentComparator()).skip(5).limit(10).collect(Collectors.toList());
综合日常使用:
List<Student> finStudent =
students.stream()
// 去重
.distinct()
.filter(student -> {
// 这里只是演示一下复杂的过滤怎么实现,这里可以直接一行表示,不需要大括号!
boolean filter = student.name.contains("三") && student.getAge() > 10;
return filter;
})
// 根据自定义的排序器(根据学生年龄进行升序),然后根据学生的生日进行降序 这里最好不要使用.reversed()若使用这个会导致年龄逆序的,除非前面也加上.reversed()
.sorted(new StudentComparator().thenComparing(Student::getBirthday, Comparator.reverseOrder()))
// skip跳过0个数据 limit获取10个数据 和数据库中分页 limit 0,10 一样
.skip(0).limit(10).collect(Collectors.toList());
小结
逆序存在两种写法:
- Comparator.comparing(Student::getAge).reversed()
- Comparator.comparing(Student::getAge,Comparator.reverseOrder())
- 第一种写法会逆序之前的全部排序规则,如果思路不清晰容易出错,如果要排序的全部字段都按照降序,推荐使用这个,直接在最后添加reversed()就好。
- 但是如果比较比较复杂,使用Comparator.reverseOrder()比较稳妥一些。
skip
跳过指定个数的Stream中元素,获取后面的元素。
// 跳过前5个元素,返回后面的
List<UmsPermission> skipList = permissionList.stream()
.skip(5)
.collect(Collectors.toList());Copy to clipboardErrorCopied
distinct
剔除重复的元素
Collectors
Collectors.toList()
List<Student> studentList =
students.stream().filter(student -> student.getAge() > 20).collect(Collectors.toList());
[Student(id=2, name=萧炎, age=22, birthday=Fri Oct 02 00:00:00 CST 2020)]
Collectors.joining()
- Collectors.joining()
- Collectors.joining(delimiter)
- Collectors.joining(delimiter,prefix,suffix)
String studentStr1 = students.stream().map(Student::getName).collect(Collectors.joining());
String studentStr2 = students.stream().map(Student::getName).collect(Collectors.joining(","));
String studentStr3 = students.stream().map(Student::getName).collect(Collectors.joining(",", "武动乾坤->", "<-斗破苍穹"));
张三萧炎唐三牧尘林动
张三,萧炎,唐三,牧尘,林动
武动乾坤->张三,萧炎,唐三,牧尘,林动<-斗破苍穹
Collectors.toSet()
Set<Integer> ageSet = students.stream().map(Student::getAge).collect(Collectors.toSet());
[16, 18, 20, 22]
Collectors.toMap()/Collectors.toConcurrentMap()
- Collectors.toMap(p1,p2);
- Collectors.toMap(p1,p2,p3);
- Collectors.toMap(p1,p2,p3,p4);
p1: 要转换为的map的键
p2:要转换为的map的值,如果要转换为本对象则可设置为Function.identity()
p3:用于解决键的冲突,(o1,o2)-> o1 如果冲突选择前面的那个值,如果不设置冲突会造成异常
p4:设置要转换为的Map类型,如果不设置就为Map/ConcurrentMap
Map<Integer, Student> studentMap = students.stream().collect(Collectors.toMap(Student::getId, Function.identity()));
Map<Integer, Student> outStudentMap = students.stream().collect(Collectors.toMap(Student::getId, Function.identity(), (o1, o2) -> o2));
ConcurrentHashMap<Integer, Student> studentConcurrentHashMap = students.stream().collect(Collectors.toMap(Student::getId, Function.identity(), (o1, o2) -> o2, ConcurrentHashMap::new));
略,可以尝试键相同的情况下,不设置param3所产生异常!
Collectors.toConcurrentMap()的所有都和Collectors.toMap()相同!!除了返回的Map类型
Collectors.groupingBy()/Collectors.groupingByConcurrent()
- Collectors.groupingBy(p1)
- Collectors.groupingBy(p1,p2)
- Collectors.groupingBy(p1,p2,p3)
p1:按照什么来进行分组
p2:分组完成后用什么容器装载数据 默认Map
p3:分类后,对应的分类结果用什么容器装载 默认List
ConcurrentHashMap<String, List<Student>> groupStudentByGrade = students.stream().collect(Collectors.groupingBy(Student::getGrade,ConcurrentHashMap::new, Collectors.toList()));
Map<String, Long> groupCountingByGrade = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
Map<String, List<Student>> groupStudentByGradeAndAge = students.stream().collect(Collectors.groupingBy(student -> student.getGrade() + "," + student.getAge()));
{高三=[Student(id=5, name=林动, age=16, birthday=Sun Aug 09 00:00:00 CST 2020, grade=高三)],
高二=[Student(id=2, name=萧炎, age=22, birthday=Fri Oct 02 00:00:00 CST 2020, grade=高二), Student(id=4, name=牧尘, age=20, birthday=Wed May 20 00:00:00 CST 2020, grade=高二)],
高一=[Student(id=1, name=张三, age=20, birthday=Thu Oct 01 00:00:00 CST 2020, grade=高一), Student(id=3, name=唐三, age=18, birthday=Thu Sep 10 00:00:00 CST 2020, grade=高一)]}
{高三=1, 高二=2, 高一=2}
结果三:略 根据年级和年龄分类无意义,只是为了展示多个条件分组
这个分组远远不止这点东西,其他的可以自行了解这个
Collectors.partitioningBy
分割列表 一个为false 一个为true
Map<Boolean, List<Student>> partitioningStudent = students.stream().collect(Collectors.partitioningBy(student -> student.getAge() > 20));
{false=[Student(id=1, name=张三, age=20, birthday=Thu Oct 01 00:00:00 CST 2020, grade=高一), Student(id=3, name=唐三, age=18, birthday=Thu Sep 10 00:00:00 CST 2020, grade=高一), Student(id=4, name=牧尘, age=20, birthday=Wed May 20 00:00:00 CST 2020, grade=高二), Student(id=5, name=林动, age=16, birthday=Sun Aug 09 00:00:00 CST 2020, grade=高三)],
true=[Student(id=2, name=萧炎, age=22, birthday=Fri Oct 02 00:00:00 CST 2020, grade=高二)]}
统计
另外,一些产生统计结果的收集器也非常有用。它们主要用于int、double、long等基本类型上,它们可以用来产生类似如下的统计结果。
List<Integer> agesList = students.stream().map(Student::getAge).collect(Collectors.toList());
IntSummaryStatistics intSummaryStatistics =
agesList.stream().mapToInt(x -> x).summaryStatistics();
System.out.println("获取最大的年龄:" + intSummaryStatistics.getMax());
System.out.println("获取最小的年龄:" + intSummaryStatistics.getMin());
System.out.println("所有年龄之和:" + intSummaryStatistics.getSum());
System.out.println("获取年龄的平均是:" + intSummaryStatistics.getAverage());
System.out.println("获取年龄个数:" + intSummaryStatistics.getCount());
stream 不仅仅是这些,这只是基本的使用
全部代码
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
// 模拟数据
ArrayList<Student> students = new ArrayList<>();
students.add(new Student(1, "张三", 20, dateFormat.parse("2020-10-01"), "高一"));
students.add(new Student(2, "萧炎", 22, dateFormat.parse("2020-10-02"), "高二"));
students.add(new Student(3, "唐三", 18, dateFormat.parse("2020-09-10"), "高一"));
students.add(new Student(4, "牧尘", 20, dateFormat.parse("2020-05-20"), "高二"));
students.add(new Student(5, "林动", 16, dateFormat.parse("2020-08-09"), "高三"));
// Collectors.toList()
List<Student> studentList = students.stream().filter(student -> student.getAge() > 20).collect(Collectors.toList());
// Collectors.joining()
String studentStr1 = students.stream().map(Student::getName).collect(Collectors.joining());
String studentStr2 = students.stream().map(Student::getName).collect(Collectors.joining(","));
String studentStr3 = students.stream().map(Student::getName).collect(Collectors.joining(",", "武动乾坤->", "<-斗破苍穹"));
Set<Integer> ageSet = students.stream().map(Student::getAge).collect(Collectors.toSet());
// Collectors.toMap()/Collectors.toConcurrentMap()
Map<Integer, Student> studentMap = students.stream().collect(Collectors.toMap(Student::getId, Function.identity()));
Map<Integer, Student> outStudentMap = students.stream().collect(Collectors.toMap(Student::getId, Function.identity(), (o1, o2) -> o2));
ConcurrentHashMap<Integer, Student> studentConcurrentHashMap = students.stream().collect(Collectors.toMap(Student::getId, Function.identity(), (o1, o2) -> o2, ConcurrentHashMap::new));
//Collectors.groupingBy()/Collectors.groupingByConcurrent()
ConcurrentHashMap<String, List<Student>> groupStudentByGrade = students.stream().collect(Collectors.groupingBy(Student::getGrade, ConcurrentHashMap::new, Collectors.toList()));
Map<String, List<Student>> groupStudentByGradeAndAge = students.stream().collect(Collectors.groupingBy(student -> student.getGrade() + "," + student.getAge()));
Map<String, Long> groupCountingByGrade = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
//Collectors.partitioningBy() 分割列表 一个为false 一个为true
Map<Boolean, List<Student>> partitioningStudent = students.stream().collect(Collectors.partitioningBy(student -> student.getAge() > 20));
System.out.println("Collectors.toList()-->" + studentList);
System.out.println("Collectors.joining()-->" + studentStr1);
System.out.println("Collectors.joining(delimiter)-->" + studentStr2);
System.out.println("Collectors.joining(delimiter,prefix,suffix)-->" + studentStr3);
System.out.println("Collectors.toSet()-->" + ageSet.toString());
//这里为了程序正常执行,并没有创建相同的id
System.out.println("Collectors.toMap(p1, p2)-->" + studentMap.toString());
System.out.println("Collectors.toMap(p1, p2, p3)-->" + outStudentMap.toString());
System.out.println("Collectors.toMap(p1, p2, p3, p4)" + studentConcurrentHashMap.toString());
System.out.println("Collectors.groupingBy()单条件分组-->" + groupStudentByGrade);
System.out.println("Collectors.groupingBy()多条件分组-->" + groupStudentByGradeAndAge);
System.out.println("Collectors.groupingBy()计数-->" + groupCountingByGrade);
System.out.println("Collectors.partitioningBy()" + partitioningStudent);