Stream 流
看之前:需要一些Lambda语法知识,可以查看参考资料中的文章:万字详解,JDK1.8的Lambda、Stream和日期的使用详解。
正文
Java8 API Stream 允许你以声明性方式处理数据集合或数组(通过查询语句来表达。
Stream 的特点:
Stream 自己不会存储数据
Stream 不会改变源对象,他们会返回一个持有结果的新对象
Stream 操作是延迟执行的,意味着他们只会在需要结果的时候才会执行。
Stream 只能消费一次,消费完毕之后就会关闭。
Stream流的使用分三步:创建流、中间操作、终端操作;Stream流是延迟执行的,只有终端操作才能触发整个流的执行。
1、流的操作步骤
1.1 创建流
流的操作:
创建流:新建一个流
中间操作:在一个或者多个步骤中将初始 stream 转化为另一个 stream
终端操作:使用一个终端操生成一个结果,该操作会强制它之前的延迟操作立即执行,在这之后 stream 就无法再次使用了。
1.2 中间操作:
中间操作会返回另一个流,多个中间操作可以连接起来可以形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理。
1.3 终端操作
终端口操作会从流的流水线生成结果。其结果是任何不是流的值,比如List、 Integer,甚至void。
默认收集器Collectors,Collectors是一个工具类,是JDK预实现Collector的工具类,它内部提供了多种Collector用来帮我们生成结果集合,主要的方法如下:
2、Stream 的使用示例
学生类:
1public class StudentBO {
2 /** 学生id **/
3 Integer id;
4
5 /** 学生姓名 **/
6 String name;
7
8 /** 班级id **/
9 Integer classId;
10
11 /** 学生年龄 **/
12 Integer age;
13
14 /** 分数 **/
15 Integer score;
16
17 /**身高 cm **/
18 Integer height;
19
20 /** 体重 kg**/
21 Integer weight;
22
23 /**24 * 获取学生的身体质量指数25 * @return26 */
27 public Double getStudentBMI() {
28 return (weight * 10000.0 / (height * height));
29 }
30
31 /**32 * 根据id大小对学生进行比较33 * @param s134 * @param s235 * @return36 */
37 public static int compare(StudentBO s1, StudentBO s2) {
38 return (s1.getId().compareTo(s2.id));
39 }
40
41 @Override
42 public boolean equals(Object o) {
43 if (this == o) return true;
44 if (o == null || getClass() != o.getClass()) return false;
45 StudentBO studentBO = (StudentBO) o;
46 return Objects.equals(id, studentBO.id);
47 }
48
49 @Override
50 public int hashCode() {
51 return Objects.hash(id, name, age, score);
52 }
53 //省略属性的get和set方法
54}
先预加载需要操作的集合:
1public class StreamDemoTest {
2 private List studentBOList = new ArrayList(); 3 @Before 4 public void initTest() { 5 StudentBO studentA = new StudentBO(); 6 StudentBO studentB = new StudentBO(); 7 StudentBO studentC = new StudentBO(); 8 StudentBO studentD = new StudentBO(); 910 studentA.setId(1);11 studentA.setAge(20);12 studentA.setName("Tom");13 studentA.setScore(100);14 studentA.setHeight(175);15 studentA.setWeight(60);1617 studentB.setId(2);18 studentB.setAge(19);19 studentB.setName("Ken");20 studentB.setScore(60);21 studentB.setHeight(180);22 studentB.setWeight(60);2324 studentC.setId(3);25 studentC.setAge(19);26 studentC.setName("Bob");27 studentC.setScore(70);28 studentC.setHeight(175);29 studentC.setWeight(80);3031 studentBOList.add(studentA);32 studentBOList.add(studentB);33 studentBOList.add(studentC);34 studentBOList.add(studentD);35 }36}
2.1 创建流
1、直接通过集合对象的 stream() 方法创建
2、通过Arrays类的 stream()方法创建
3、通过Stream接口的 of()、 generate()、 iterator() 方法创建
4、通过 IntStream、LongStream、DubboStream 接口中的 of、range、rangeClosed 方法创建
2.2 中间操作
filter
filter 方法用于过滤符合条件的元素:
1 @Test
2 public void testFilter() {
3 //筛选id不为空的元素
4 List reuslt1 = studentBOList.stream().filter(studentBO -> studentBO.getId() != null).map(studentBO -> { 5 Integer res = studentBO.getScore() * studentBO.getScore(); 6 return res; 7 }).collect(Collectors.toList()); 8 9 System.out.println(reuslt1);1011 //筛选score的平方大于 700的学生,并返回 score的平方12 List reuslt2 = studentBOList.stream().filter(studentBO -> {13 if (studentBO.getId() == null) {14 return false;15 }16 Integer score2 = studentBO.getScore() * studentBO.getScore();17 return score2 > 7000;18 }).map(studentBO -> {19 Integer res = studentBO.getScore() * studentBO.getScore();20 return res;21 }).collect(Collectors.toList());22 System.out.println(reuslt2);2324 //筛选年龄大于18岁的学生集合25 List sudentList26 = studentBOList.stream().filter(bo -> bo.getAge() > 18).collect(Collectors.toList());27 }
limit
limit 方法用于截断流:按照出现顺序保留N个。
1 @Test
2 public void testLimit() {
3 List list = Arrays.asList(1, 2, 2, 3, 4, 5, 5);4 List result = list.stream().limit(5).collect(Collectors.toList());5 // 结果:[1, 2, 2, 3, 4]6 System.out.println(result);7 }
map
map 方法用于映射每个元素到对应的结果,一对一。
1 @Test
2 public void testMapA() {
3 /** 4 * 求平方 5 */
6 List list = Arrays.asList(1,2,3,4,5); 7 List result1 = list.stream().map(n -> n * n).collect(Collectors.toList()); 8 System.out.println(result1); 910 /**11 * 筛选id不为空的学生id12 */13 List reuslt2 = studentBOList.stream()14 .filter(studentBO -> studentBO.getId() != null)15 .map(studentBO -> {16 Integer res = studentBO.getScore() * studentBO.getScore();17 return res;18 }).collect(Collectors.toList());19 System.out.println(reuslt2);20 }
skip
按遇到的顺序丢弃前N个元素,如果少于N个元素,就返回空的stream。
1 @Test
2 public void testSkip() {
3 List list = Arrays.asList(1, 2, 2, 3, 4, 5, 5);4 List result = list.stream().skip(5).collect(Collectors.toList());5 // [5, 5]6 System.out.println(result);7 }
distinct
使用java8新特性stream进行List去重,使用steam的distinct()方法返回一个由不同数据组成的流,通过对象的equals()方法进行比较,所以如果使用 distinct 方法,则需要重写List 中对象的 equals 方法。
1 @Test
2 public void testDistinct() {
3 /** 4 * 对数字去重 5 */
6 List list = Arrays.asList(1, 2, 2, 3, 4, 5, 5); 7 List result = list.stream().sorted().distinct().collect(Collectors.toList()); 8 System.out.println(result); 910 /**11 * 对学生集合去重12 */13 StudentBO studentE = new StudentBO();14 studentE.setId(3);15 studentE.setAge(19);16 studentE.setName("Bob");17 studentE.setScore(70);18 studentBOList.add(studentE);19 List list1 = studentBOList.stream().distinct().collect(Collectors.toList());20 }
sorted
sorted 是有状态的中间操作,返回由该流的元素组成的流,并根据自然顺序排序。
如果此流的元素不可比较,则在执行终端操作时可能会引发 java.lang.ClassCastException 异常。
对于有序流,排序是稳定的。对于无序流,不保证稳定性。
可以使用自然排序,或者自己实现排序方法:
1// 自然排序
2Stream sorted();
3// 自定义排序规则
4Stream sorted(Comparator super T> comparator);
代码示例:
1 @Test
2 public void testSorted() {
3 // 自然排序
4 List list = Arrays.asList(1, 2, 5, 4, 3); 5 List integers = list.stream().sorted().collect(Collectors.toList()); 6 7 // 按年龄排序 8 List result = studentBOList.stream() 9 .filter(studentBO -> studentBO.getId() != null)10 .sorted((s1, s2) -> {11 return s1.getAge().compareTo(s2.getAge());12 }).collect(Collectors.toList());13 System.out.println(result);14 }
2.3 终端操作
max 和 min
max 和 min 操作可以获取最大值和最小值
1 @Test
2 public void testMaxMIn() {
3 /** 4 * 获取id最大的学生 5 */
6 StudentBO max
7 = studentBOList
8 .stream()
9 .filter( bo -> bo.getId() != null)
10 .max(StudentBO::compare).get();
11 /**12 * 获取id最小的学生13 */
14 StudentBO min
15 = studentBOList
16 .stream()
17 .filter( bo -> bo.getId() != null)
18 .min(StudentBO::compare).get();
19
20 /**21 * 获取年龄最大的学生22 */
23 StudentBO max2
24 = studentBOList
25 .stream()
26 .filter( bo -> bo.getId() != null)
27 .max(Comparator.comparing(StudentBO::getAge)).get();
28
29 /**30 * for 循环实现:获取分数最大的学生31 */
32 boolean seen = false;
33 StudentBO best = null;
34 for (StudentBO bo : studentBOList) {
35 if (bo.getId() != null) {
36 if (!seen || bo.getScore().compareTo(best.getScore()) > 0) {
37 seen = true;
38 best = bo;
39 }
40 }
41 }
42 StudentBO max3
43 = (seen ? Optional.of(best) : Optional.empty()).get();4445 /**46 * stream 实现获取分数最大的学生47 */48 StudentBO max449 = studentBOList50 .stream()51 .filter( bo -> bo.getId() != null)52 .max((s1, s2) -> {return s1.getScore().compareTo(s2.getScore());}).get();5354 }
count
对流中的元素进行统计。
1 @Test
2 public void testCount() {
3 //parallelStream 是流并行处理程序的代替方法。
4 List integers = Arrays.asList(1, 1, 2, 3, 4, 5, 6, 7, 8, 9); 5 //统计1的个数 6 long count1 = integers.parallelStream().filter(i -> {return i.equals(1);}).count(); 7 8 //统计其平方大于16的个数 9 long count2 = integers.parallelStream().filter( i -> {return i * i > 16;}).count();10 }
reduce
reduce 操作等价于:依次对流中的元素进行某种持续操作,降低了数据竞争的风险。
1T reduce(T identity, BinaryOperator accumulator);
2
3T result = identity;
4for (T element : this stream)
5 result = accumulator.apply(result, element)
6 return result;
利用reduce实现累加:
1 @Test
2 public void testReduce() {
3 List integers = Arrays.asList(1, 1, 2, 3, 4, 5, 6, 7, 8, 9);4 //求和的两种方式5 long sum1 = integers.stream().reduce(0, (a,b)->(a+b));6 long sum2 = integers.stream().reduce(0, Integer::sum);7 }
collect
对元素进行收集操作:
1 @Test
2 public void testCollectors() {
3 /** 4 * 返回包含学生id和其BMI系数的map 5 */
6 Map result1 7 = studentBOList.stream() 8 .filter( bo -> bo.getId() != null) 9 .collect(Collectors.toMap(StudentBO::getId,StudentBO::getStudentBMI));1011 /**12 * 返回包含学生id和其对象的map13 */14 Map result2 = studentBOList.stream()15 .filter( bo -> bo.getId() != null)16 .collect(Collectors.toMap(StudentBO::getId, bo -> {17 return bo;18 }));1920 /**21 * 根据某个属性进行分类22 */23 Map> result3 = studentBOList.stream()24 .filter(bo -> bo.getId() != null)25 .collect(Collectors.groupingBy(StudentBO::getScore));2627 /**28 * 根据某个条件进行区分,29 * 条件为true or false30 */31 Map> result4 = studentBOList.stream()32 .filter(bo -> bo.getId() != null)33 .collect(Collectors.partitioningBy(bo -> {34 return bo.getScore() > 60;35 }));3637 //计算所有学生的分数之和38 Integer sum1 = studentBOList.stream()39 .filter(bo -> bo.getId() != null)40 .collect(Collectors.summingInt(StudentBO::getScore));4142 /**43 * 将学生按照班级分类44 */45 Map> classStudentMap = studentBOList.stream()46 .filter(bo -> bo.getId() != null )47 .collect(Collectors48 .groupingBy(49 StudentBO::getClassId,50 TreeMap::new,51 Collectors.toList()52 )53 );54 //{1=[StudentBO{id=1, name='Tom', age=20, score=100}], 2=[StudentBO{id=2, name='Ken', age=19, score=60}, StudentBO{id=3, name='Bob', age=19, score=70}]}55 System.out.println(classStudentMap);56 }
foreach
对所有的元素进行某种操作:
1 @Test
2 public void testForeach() {
3 List integers = Arrays.asList(1, 1, 2, 3, 4, 5, 6, 7, 8, 9);4 //打印所有的元素5 integers.stream().forEach(System.out::print);6 }
有不足或写的不好的地方,希望大家能不吝赐教!
往期文章
菜鸟的Redis实战
参考资料
[1] 利用stream流对po与vo进行相互转换
[2] Java8 Stream终端操作使用详解
[3] 菜鸟:Java 8 Stream
[4] java箭头函数,lambda表达式
[5] 万字详解,JDK1.8的Lambda、Stream和日期的使用详解
[6] 本文代码地址:https://github.com/hustuhao/StreamDemo