Java8的两个重大改变,一个是Lambda表达式,另一个就是本节要讲的Stream API表达式。Stream 是Java8中处理集合的关键抽象概念,它可以对集合进行非常复杂的查找、过滤、筛选等操作,在新版的JPA中,也已经加入了Stream。如:

  1. @Query("select u from User u")
  2. Stream<User> findAllByCustomQueryAndStream();
  3.  
  4. Stream<User> readAllByFirstnameNotNull();
  5.  
  6. @Query("select u from User u")
  7. Stream<User> streamAllPaged(Pageable pageable);

Stream API给我们操作集合带来了强大的功用,同时Stream API操作简单,容易上手。

1、Stream的操作步骤

Stream有如下三个操作步骤:

一、创建Stream

从一个数据源,如集合、数组中获取流。

二、中间操作

一个操作的中间链,对数据源的数据进行操作。

三、终止操作

一个终止操作,执行中间操作链,并产生结果。

要注意的是,对流的操作完成后需要进行关闭操作(或者用JAVA7的try-with-resources)。

举个简单的例子:

假设有一个Person类和一个Person列表,现在有两个需求:1)找到年龄大于18岁的人并输出;2)找出所有中国人的数量。

  1. @Data
  2. class Person {
  3. private String name;
  4. private Integer age;
  5. private String country;
  6. private char sex;
  7.  
  8. public Person(String name, Integer age, String country, char sex) {
  9. this.name = name;
  10. this.age = age;
  11. this.country = country;
  12. this.sex = sex;
  13. }
  14. }
  1. List<Person> personList = new ArrayList<>();
  2. personList.add(new Person("欧阳雪",18,"中国",'F'));
  3. personList.add(new Person("Tom",24,"美国",'M'));
  4. personList.add(new Person("Harley",22,"英国",'F'));
  5. personList.add(new Person("向天笑",20,"中国",'M'));
  6. personList.add(new Person("李康",22,"中国",'M'));
  7. personList.add(new Person("小梅",20,"中国",'F'));
  8. personList.add(new Person("何雪",21,"中国",'F'));
  9. personList.add(new Person("李康",22,"中国",'M'));

在JDK8以前,我们可以通过遍历列表来完成。但是在有了Stream API后,可以这样来实现:

  1. public static void main(String[] args) {
  2.  
  3. // 1)找到年龄大于18岁的人并输出;
  4. personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);
  5.  
  6. System.out.println("-------------------------------------------");
  7.  
  8. // 2)找出所有中国人的数量
  9. long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
  10. System.out.println("中国人有:" + chinaPersonNum + "个");
  11. }

输出结果:

Person(name=Tom, age=24, country=美国, sex=M)
Person(name=Harley, age=22, country=英国, sex=F)
Person(name=向天笑, age=20, country=中国, sex=M)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=小梅, age=20, country=中国, sex=F)
Person(name=何雪, age=21, country=中国, sex=F)
Person(name=李康, age=22, country=中国, sex=M)
-------------------------------------------
中国人有:6

在这个例子中,personList.stream()是创建流,filter()属于中间操作,forEach、count()是终止操作。

2、Stream中间操作--筛选与切片

  • filter:接收Lambda,从流中排除某些操作;
  • limit:截断流,使其元素不超过给定对象
  • skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
  • distinct:筛选,通过流所生成元素的hashCode()和equals()去除重复元素。

2.1 limit举例

需求,从Person列表中取出两个女性。

  1. personList.stream().filter((p) -> p.getSex() == 'F').limit(2).forEach(System.out::println);

输出结果为:

  1. Person(name=欧阳雪, age=18, country=中国, sex=F)
  2. Person(name=Harley, age=22, country=英国, sex=F)

2.2 skip举例

从Person列表中从第2个女性开始,取出所有的女性。

  1. personList.stream().filter((p) -> p.getSex() == 'F').skip(1).forEach(System.out::println);

输出结果为:

  1. Person(name=Harley, age=22, country=英国, sex=F)
  2. Person(name=小梅, age=20, country=中国, sex=F)
  3. Person(name=何雪, age=21, country=中国, sex=F)

2.3 distinct举例

  1. personList.stream().filter((p) -> p.getSex() == 'M').distinct().forEach(System.out::println);

输出结果为:

  1. Person(name=Tom, age=24, country=美国, sex=M)
  2. Person(name=向天笑, age=20, country=中国, sex=M)
  3. Person(name=李康, age=22, country=中国, sex=M)

男性中有两个李康,去除掉了一个重复的。

3、Stream中间操作--映射

  • map--接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap--接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流

3.1 map举例

例1:比如,我们用一个PersonCountry类来接收所有的国家信息:

  1. @Data
  2. class PersonCountry {
  3. private String country;
  4. }
  5.  
  6. personList.stream().map((p) -> {
  7. PersonCountry personName = new PersonCountry();
  8. personName.setCountry(p.getCountry());
  9. return personName;
  10. }).distinct().forEach(System.out::println);

输出结果为:

  1. PersonName(country=中国)
  2. PersonName(country=美国)
  3. PersonName(country=英国)

例2:假如有一个字符列表,需要提出每一个字符

  1. List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","ddd");

代码如下:

根据字符串获取字符方法:

  1. public static Stream<Character> getCharacterByString(String str) {
  2.  
  3. List<Character> characterList = new ArrayList<>();
  4.  
  5. for (Character character : str.toCharArray()) {
  6. characterList.add(character);
  7. }
  8.  
  9. return characterList.stream();
  10. }
  1. List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","ddd");
  2.  
  3. final Stream<Stream<Character>> streamStream
  4. = list.stream().map(TestStreamAPI::getCharacterByString);
  5. streamStream.forEach(System.out::println);

运行结果:

  1. java.util.stream.ReferencePipeline$Head@3f91beef
  2. java.util.stream.ReferencePipeline$Head@1a6c5a9e
  3. java.util.stream.ReferencePipeline$Head@37bba400
  4. java.util.stream.ReferencePipeline$Head@179d3b25
  5. java.util.stream.ReferencePipeline$Head@254989ff

从输出结果及返回结果类型(Stream<Stream<Character>>)可以看出这是一个流中流,要想打印出我们想要的结果,需要对流中的每个流进行打印:

  1. streamStream.forEach(sm -> sm.forEach(System.out::print));

运行结果为:

  1. aaabbbcccdddddd

但我们希望的是返回的是一个流,而不是一个包含了多个流的流,而flatMap可以帮助我们做到这一点。

3.2 flatMap举例

改写上面的方法,将map改成flatMap:

  1. final Stream<Character> characterStream = list.stream().flatMap(TestStreamAPI::getCharacterByString);
  2. characterStream.forEach(System.out::print);

运行结果为:

  1. aaabbbcccdddddd

3.3 map和flatMap的图解

map图解:

map在接收到流后,直接将Stream放入到一个Stream中,最终整体返回一个包含了多个Stream的Stream。

flatMap图解:

flatMap在接收到Stream后,会将接收到的Stream中的每个元素取出来放入一个Stream中,最后将一个包含多个元素的Stream返回。

ps:图画得丑,将就一下。

4、Stream中间操作--排序

  • sorted()--自然排序(Comparable)
  • sorted(Comparator com)--定制排序(Comparator)

自然排序比较好理解,这里只讲一下定制排序,对前面的personList按年龄从小到大排序,年龄相同,则再按姓名排序:

  1. final Stream<Person> sorted = personList.stream().sorted((p1, p2) -> {
  2.  
  3. if (p1.getAge().equals(p2.getAge())) {
  4. return p1.getName().compareTo(p2.getName());
  5. } else {
  6. return p1.getAge().compareTo(p2.getAge());
  7. }
  8. });
  9. sorted.forEach(System.out::println);

运行结果:

  1. Person(name=欧阳雪, age=18, country=中国, sex=F)
  2. Person(name=向天笑, age=20, country=中国, sex=M)
  3. Person(name=小梅, age=20, country=中国, sex=F)
  4. Person(name=何雪, age=21, country=中国, sex=F)
  5. Person(name=Harley, age=22, country=英国, sex=F)
  6. Person(name=李康, age=22, country=中国, sex=M)
  7. Person(name=李康, age=22, country=中国, sex=M)
  8. Person(name=Tom, age=24, country=美国, sex=M)

5、终止操作--查找与匹配

  • allMatch--检查是否匹配所有元素
  • anyMatch--检查是否至少匹配一个元素
  • noneMatch--检查是否没有匹配所有元素
  • findFirst--返回第一个元素
  • findAny--返回当前流中的任意元素
  • count--返回流中元素的总个数
  • max--返回流中最大值
  • min--返回流中最小值

这些方面在Stream类中都有说明,这里不一一举例,只对allMatch、max各举一例进行说明。

4.1 allMatch

判断personList中的人是否都是成年人:

  1. final boolean adult = personList.stream().allMatch(p -> p.getAge() >= 18);
  2. System.out.println("是否都是成年人:" + adult);
  3.  
  4. final boolean chinaese = personList.stream().allMatch(p -> p.getCountry().equals("中国"));
  5. System.out.println("是否都是中国人:" + chinaese);

运行结果:

  1. 是否都是成年人:true
  2. 是否都是中国人:false

4.1 max min

  1. final Optional<Person> maxAge = personList.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
  2. System.out.println("年龄最大的人信息:" + maxAge.get());
  3.  
  4. final Optional<Person> minAge = personList.stream().min((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
  5. System.out.println("年龄最小的人信息:" + minAge.get());

运行结果:

  1. 年龄最大的人信息:Person(name=Tom, age=24, country=美国, sex=M)
  2. 年龄最小的人信息:Person(name=欧阳雪, age=18, country=中国, sex=F)

5、归约

Stream API的归约操作可以将流中元素反复结合起来,得到一个值,有:

  1. Optional<T> reduce(BinaryOperator<T> accumulator);
  2.  
  3. T reduce(T identity, BinaryOperator<T> accumulator);
  4.  
  5. <U> U reduce(U identity,
  6. BiFunction<U, ? super T, U> accumulator,
  7. BinaryOperator<U> combiner);

5.1 求一个1到100的和

  1. List<Integer> integerList = new ArrayList<>(100);
  2. for(int i = 1;i <= 100;i++) {
  3. integerList.add(i);
  4. }
  5. final Integer reduce = integerList.stream().reduce(0, (x, y) -> x + y);
  6. System.out.println("结果为:" + reduce);
  1. 结果为:5050

这个例子用到了reduce第二个方法:T reduce(T identity, BinaryOperator<T> accumulator)

把这个动作拆解一下,其运算步骤模拟如下:

  1. 0 (1,2) -> 1 + 2 + 0
  2. 3 (3,4) -> 3 + 4 + 3
  3. 10 (5,6) -> 5 + 6 + 10
  4. .
  5. .
  6. .

其运算步骤是,每次将列表的两个元素相加,并将结果与前一次的两个元素的相加结果进行累加,因此,在开始时,将identity设为0,因为第1个元素和第2个元素在相加的时候,前面还没有元素操作过。

5.2 求所有人的年龄之和

  1. final Optional<Integer> reduce = personList.stream().map(Person::getAge).reduce(Integer::sum);
  2. System.out.println("年龄总和:" + reduce);
  1. 年龄总和:169

6、收集

collect:将流转换为其他形式,接收一个Collector接口实现 ,用于给Stream中汇总的方法

  1. <R, A> R collect(Collector<? super T, A, R> collector);
  2.  
  3. <R> R collect(Supplier<R> supplier,
  4. BiConsumer<R, ? super T> accumulator,
  5. BiConsumer<R, R> combiner);

collect不光可以将流转换成其他集合等形式,还可以进行归约等操作,具体实现也很简单,主要是与Collectors类搭配使用。

6.1 改写3.1 map举例中的的例子,将国家收集起来转换成List

  1. final List<String> collect = personList.stream().map(p -> p.getCountry()).distinct().collect(Collectors.toList());
  2. System.out.println(collect);

输出结果:

  1. [中国, 美国, 英国]

6.2 计算出平均年龄

  1. final Double collect1 = personList.stream().collect(Collectors.averagingInt(p -> p.getAge()));
  2. 2 System.out.println("平均年龄为:" + collect1);

输出结果:

  1. 平均年龄为:21.125

6.3 找出最小年龄、最大年龄

  1. final Optional<Integer> maxAge2 = personList.stream().map(Person::getAge).collect(Collectors.maxBy(Integer::compareTo));
  2. System.out.println(maxAge2.get());

最小年龄类型。

还有其他很操作,可以参考java.util.stream.Collectors。

7 注意流的关闭

  1. try(final Stream<Integer> integerStream = personList.stream().map(Person::getAge)) {
  2. final Optional<Integer> minAge = integerStream.collect(Collectors.minBy(Integer::compareTo));
  3. System.out.println(minAge.get());
  4. }

最好将流的操作放到try-with-resources,本章前面内容为了方便,没有放到try-with-resources中。

8 完整测试代码

  1. import lombok.Data;
  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import java.util.Optional;
  6. import java.util.stream.Collectors;
  7. import java.util.stream.Stream;
  8.  
  9. public class TestStreamAPI {
  10.  
  11. public static void main(String[] args) {
  12. List<Person> personList = new ArrayList<>();
  13. personList.add(new Person("欧阳雪",18,"中国",'F'));
  14. personList.add(new Person("Tom",24,"美国",'M'));
  15. personList.add(new Person("Harley",22,"英国",'F'));
  16. personList.add(new Person("向天笑",20,"中国",'M'));
  17. personList.add(new Person("李康",22,"中国",'M'));
  18. personList.add(new Person("小梅",20,"中国",'F'));
  19. personList.add(new Person("何雪",21,"中国",'F'));
  20. personList.add(new Person("李康",22,"中国",'M'));
  21.  
  22. // 1)找到年龄大于18岁的人并输出;
  23. personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);
  24.  
  25. System.out.println("-------------------------------------------");
  26.  
  27. // 2)找出所有中国人的数量
  28. long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
  29. System.out.println("中国人有:" + chinaPersonNum);
  30.  
  31. // limit
  32. personList.stream().filter((p) -> p.getSex() == 'F').limit(2).forEach(System.out::println);
  33. System.out.println();
  34. // skip
  35. personList.stream().filter((p) -> p.getSex() == 'F').skip(1).forEach(System.out::println);
  36.  
  37. // distinct
  38. personList.stream().filter((p) -> p.getSex() == 'M').distinct().forEach(System.out::println);
  39.  
  40. // map
  41. personList.stream().map((p) -> {
  42. PersonCountry personName = new PersonCountry();
  43. personName.setCountry(p.getCountry());
  44. return personName;
  45. }).distinct().forEach(System.out::println);
  46.  
  47. // map2
  48. List<String> list = Arrays.asList("aaa","bbb","ccc","ddd","ddd");
  49.  
  50. final Stream<Stream<Character>> streamStream
  51. = list.stream().map(TestStreamAPI::getCharacterByString);
  52. // streamStream.forEach(System.out::println);
  53. streamStream.forEach(sm -> sm.forEach(System.out::print));
  54.  
  55. // flatMap
  56. final Stream<Character> characterStream = list.stream().flatMap(TestStreamAPI::getCharacterByString);
  57. characterStream.forEach(System.out::print);
  58.  
  59. // sort
  60. final Stream<Person> sorted = personList.stream().sorted((p1, p2) -> {
  61.  
  62. if (p1.getAge().equals(p2.getAge())) {
  63. return p1.getName().compareTo(p2.getName());
  64. } else {
  65. return p1.getAge().compareTo(p2.getAge());
  66. }
  67. });
  68. sorted.forEach(System.out::println);
  69.  
  70. // allMatch
  71. final Stream<Person> stream = personList.stream();
  72. final boolean adult = stream.allMatch(p -> p.getAge() >= 18);
  73. System.out.println("是否都是成年人:" + adult);
  74.  
  75. final boolean chinaese = personList.stream().allMatch(p -> p.getCountry().equals("中国"));
  76. System.out.println("是否都是中国人:" + chinaese);
  77.  
  78. // max min
  79. final Optional<Person> maxAge = personList.stream().max((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
  80. System.out.println("年龄最大的人信息:" + maxAge.get());
  81.  
  82. final Optional<Person> minAge = personList.stream().min((p1, p2) -> p1.getAge().compareTo(p2.getAge()));
  83. System.out.println("年龄最小的人信息:" + minAge.get());
  84.  
  85. // reduce
  86. List<Integer> integerList = new ArrayList<>(100);
  87. for(int i = 1;i <= 100;i++) {
  88. integerList.add(i);
  89. }
  90. final Integer reduce = integerList.stream().reduce(0, (x, y) -> x + y);
  91. System.out.println("结果为:" + reduce);
  92.  
  93. final Optional<Integer> totalAge = personList.stream().map(Person::getAge).reduce(Integer::sum);
  94. System.out.println("年龄总和:" + totalAge);
  95.  
  96. // collect
  97. final List<String> collect = personList.stream().map(p -> p.getCountry()).distinct().collect(Collectors.toList());
  98. System.out.println(collect);
  99.  
  100. final Double collect1 = personList.stream().collect(Collectors.averagingInt(p -> p.getAge()));
  101. System.out.println("平均年龄为:" + collect1);
  102.  
  103. final Optional<Integer> maxAge2 = personList.stream().map(Person::getAge).collect(Collectors.maxBy(Integer::compareTo));
  104. System.out.println(maxAge2.get());
  105.  
  106. try(final Stream<Integer> integerStream = personList.stream().map(Person::getAge)) {
  107. final Optional<Integer> minAge2 = integerStream.collect(Collectors.minBy(Integer::compareTo));
  108. System.out.println(minAge2.get());
  109. }
  110. }
  111.  
  112. public static Stream<Character> getCharacterByString(String str) {
  113.  
  114. List<Character> characterList = new ArrayList<>();
  115.  
  116. for (Character character : str.toCharArray()) {
  117. characterList.add(character);
  118. }
  119.  
  120. return characterList.stream();
  121. }
  122. }
  123.  
  124. @Data
  125. class PersonCountry {
  126. private String country;
  127. }
  128.  
  129. @Data
  130. class Person {
  131. private String name;
  132. private Integer age;
  133. private String country;
  134. private char sex;
  135.  
  136. public Person(String name, Integer age, String country, char sex) {
  137. this.name = name;
  138. this.age = age;
  139. this.country = country;
  140. this.sex = sex;
  141. }
  142. }

Java8新特性之三:Stream API的更多相关文章

  1. Java8 新特性之Stream API

    1. Stream 概述 Stream 是Java8中处理集合的关键抽象概念,可以对集合执行非常复杂的查找,过滤和映射数据等操作; 使用 Stream API 对集合数据进行操作,就类似于使用 SQL ...

  2. 【Java8新特性】Stream API有哪些中间操作?看完你也可以吊打面试官!!

    写在前面 在上一篇<[Java8新特性]面试官问我:Java8中创建Stream流有哪几种方式?>中,一名读者去面试被面试官暴虐!归根结底,那哥儿们还是对Java8的新特性不是很了解呀!那 ...

  3. Java8 新特性 Lambda & Stream API

    目录 Lambda & Stream API 1 Lambda表达式 1.1 为什么要使用lambda表达式 1.2 Lambda表达式语法 1.3 函数式接口 1.3.1 什么是函数式接口? ...

  4. 乐字节-Java8新特性之Stream流(上)

    上一篇文章,小乐给大家介绍了<Java8新特性之方法引用>,下面接下来小乐将会给大家介绍Java8新特性之Stream,称之为流,本篇文章为上半部分. 1.什么是流? Java Se中对于 ...

  5. 乐字节-Java8新特性之Date API

    上一篇文章,小乐给大家带来了Java8新特性之Optional,接下来本文将会给大家介绍Java8新特性之Date API 前言: Java 8通过发布新的Date-Time API来进一步加强对日期 ...

  6. 【Java8新特性】- Stream流

    Java8新特性 - Stream流的应用 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! ...

  7. Java8 新特性 Data Time API

    Java8新的日期类型 在Java8以前,Date日期API对我们非常的不友好,它无法表示日期,只能以毫秒的精试来表示时间,并且可以修改,他的线程还不是安全的.所以Java8中引入了全新的日期和时间A ...

  8. java8 新特性入门 stream/lambda

    Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利.高效的聚合操作(aggregate operation),或者大批量数据操作 (b ...

  9. Java8新特性之Stream

    原文链接:http://ifeve.com/stream/ Java8初体验(二)Stream语法详解 感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com上篇文章Java8初体验( ...

随机推荐

  1. 我写的python代码的规则

    1.Python文件的命名: 采用每个单词的首字母大写,不使用下划线 2.Python类的命名: 采用每个单词的首字母大写,不使用下划线 3.Python包名的命名:采用每个单词都是小写,不使用下划线 ...

  2. HTML标签fieldset

    一个不常用的HTML标签fieldset,不过我觉得比较有意思,其语法如下: <fieldset> <legend>fieldset名称</legend> < ...

  3. 14.Ubuntu基本命令

    vi编辑器 {  :上一段diamante } :下一段代码 dw: 删除一个单词 权限 前面的分三组 第一: 文件拥有者的权限 第二:同组者拥有的权限 第三:其他人拥有的权限 前面“-”表示是文件 ...

  4. hi-nginx-javascript vs node.js

    hi-nginx-1.4.9已经支持javascript,这意味着把javascript应用于后端开发,将不再只有nodejs这唯一的途径和方法.由于java本身对javascript的极好支持,使得 ...

  5. 使用on-my-zsh时,php 输出内容后面多个%号

    今天用php写个命令行的小工具时,突然发现在echo输出后,总是会多个%号,开始以为是代码的问题,然后新建了一个代码文件: <?php echo 'hello world'; 输出结果: hel ...

  6. 【转】数据库事务ACID以及事务隔离

      本篇讲诉数据库中事务的四大特性(ACID),并且将会详细地说明事务的隔离级别. 如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ 原子性(Atomicity) 原子性是指 ...

  7. idea快捷

    IntelliJ Idea 常用快捷键 列表(实战终极总结!!!!) 1. -----------自动代码-------- 常用的有fori/sout/psvm+Tab即可生成循环.System.ou ...

  8. 获取GRIDVIEW中的TemplateField显示的文本值

    GRIDVIEW中数据源绑定后的属性绑定我一般采取2种办法 一个是BoundField,只要设置DataField的对应属性名即可: 如: <asp:BoundField HeaderText ...

  9. 【手记】走近科学之为什么明明实现了IEnumerable<T>的类型却不能调用LINQ扩展方法

    比如Json.NET的JObject明明实现了IEnumerable<T>,具体来说是IEnumerable<KeyValuePair<string, JToken>&g ...

  10. SSIS 检查点

    在SSIS中,检查点实际上是一个记录系统,用于记录控制流中Task组件的执行状态.通过合理地配置Checkpoint,在Package运行出错之后,重新执行Package,可以跳过上一次已经成功执行的 ...