Stream 流

背景介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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;
private static final long serialVersionUID = 1L;
}

添加数据

1
2
3
4
5
6
7
8
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对象分为两种,一种串行的流对象,一种并行的流对象。

1
2
3
4
5
// permissionList指所有权限列表
// 为集合创建串行流对象
Stream<Student> stream = students.stream();
// 为集合创建并行流对象
Stream<Student> parallelStream = students.parallelStream();

forEach

Stream 提供了新的方法 ‘forEach’ 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:

1
2
Random random = new Random(); 
random.ints().limit(10).forEach(System.out::println);

filter

对Stream中的元素进行过滤操作,当设置条件返回true时返回相应元素。

1
2
3
4
// 获取权限类型为目录的权限
List<UmsPermission> dirList = permissionList.stream()
.filter(permission -> permission.getType() == 0)
.collect(Collectors.toList());Copy to clipboardErrorCopied

map

对Stream中的元素进行转换处理后获取。比如可以将UmsPermission对象转换成Long对象。 我们经常会有这样的需求:需要把某些对象的id提取出来,然后根据这些id去查询其他对象,这时可以使用此方法。

1
2
3
4
5
6
7
// 获取所有权限的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中获取指定数量的元素。

1
2
3
4
// 获取前5个权限对象组成的集合
List<UmsPermission> firstFiveList = permissionList.stream()
.limit(5)
.collect(Collectors.toList());Copy to clipboardErrorCopied

count

仅获取Stream中元素的个数。

1
2
3
4
// count操作:获取所有目录权限的个数
long dirPermissionCount = permissionList.stream()
.filter(permission -> permission.getType() == 0)
.count();Copy to clipboardErrorCopied

sorted

对Stream中元素按指定规则进行排序。

对单个属性排序:


  1. 根据年龄升序排序
1
2
3
students.stream()
.sorted(Comparator.comparing(Student::getAge))
.collect(Collectors.toList());
  1. 根据年龄降序排序(先升序,后逆序)
1
2
3
students.stream()
.sorted(Comparator.comparing(Student::getAge).reversed())
.collect(Collectors.toList());

这个是先根据年龄升序排序,然后利用reversed()逆序;

  1. 根据年龄降序排序(直接逆序)
1
2
3
students.stream()
.sorted(Comparator.comparing(Student::getAge,Comparator.reverseOrder()))
.collect(Collectors.toList());

利用Comparator.reverseOrder()直接就是降序排序


对多个属性排序

  1. 根据年龄降序,生日升序
1
2
3
4
students.stream()
.sorted(Comparator.comparing(Student::getAge).reversed()
.thenComparing(Student::getBirthday))
.collect(Collectors.toList());
1
2
3
4
students.stream()
.sorted(Comparator.comparing(Student::getAge,Comparator.reverseOrder())
.thenComparing(Student::getBirthday))
.collect(Collectors.toList());

第一种是先按照年龄升序,然后逆序,第二种则是直接按照年龄降序

  1. 根据年龄降序,生日降序
1
2
3
4
students.stream()
.sorted(Comparator.comparing(Student::getAge)
.thenComparing(Student::getBirthday).reversed())
.collect(Collectors.toList());

这里要明白为什么只是用了一次reversed()年龄为什么也逆序了,reversed()的作用域是reversed()前面的所有的排序,也就是作用域为年龄和生日,如果想按照年龄升序,生日降序:则在年龄后面在加上一个reversed()逆序两次也就是升序了

1
2
3
4
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接口

1
2
3
4
5
6
7
public class StudentComparator implements Comparator<Student> {   
@Override
public int compare(Student o1, Student o2) {
// 这里可以自己定义复杂的排序算法
return o1.getAge() - o2.getAge();
}
}

使用该排序器

1
students.stream().sorted(new StudentComparator()).collect(Collectors.toList());

自定义比较复杂的排序算法,利用stream流排序(可以去详细了解stream流)分页获取数据例如:

1
students.stream().sorted(newStudentComparator()).skip(5).limit(10).collect(Collectors.toList());

小结

逆序存在两种写法:

  • Comparator.comparing(Student::getAge).reversed()
  • Comparator.comparing(Student::getAge,Comparator.reverseOrder())
  1. 第一种写法会逆序之前的全部排序规则,如果思路不清晰容易出错,如果要排序的全部字段都按照降序,推荐使用这个,直接在最后添加reversed()就好。
  2. 但是如果比较比较复杂,使用Comparator.reverseOrder()比较稳妥一些。

skip

跳过指定个数的Stream中元素,获取后面的元素。

1
2
3
4
// 跳过前5个元素,返回后面的
List<UmsPermission> skipList = permissionList.stream()
.skip(5)
.collect(Collectors.toList());Copy to clipboardErrorCopied

distinct

剔除重复的元素

Collectors

Collectors.toList()

1
2
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)
1
2
3
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()

1
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

1
Map<Integer, Student> studentMap = students.stream().collect(Collectors.toMap(Student::getId, Function.identity()));
1
Map<Integer, Student> outStudentMap = students.stream().collect(Collectors.toMap(Student::getId, Function.identity(), (o1, o2) -> o2));
1
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

1
2
3
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()));
1
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

1
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等基本类型上,它们可以用来产生类似如下的统计结果。

1
2
3
4
5
6
7
8
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 不仅仅是这些,这只是基本的使用


全部代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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);