• Java8新特性之三:Stream API


    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);

    输出结果为:

    Person(name=欧阳雪, age=18, country=中国, sex=F)
    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);

    输出结果为:

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

    2.3 distinct举例

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

    输出结果为:

    Person(name=Tom, age=24, country=美国, sex=M)
    Person(name=向天笑, age=20, country=中国, sex=M)
    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 
     7 personList.stream().map((p) -> {
     8        PersonCountry personName = new PersonCountry();
     9        personName.setCountry(p.getCountry());
    10        return personName;
    11 }).distinct().forEach(System.out::println);

    输出结果为:

    PersonName(country=中国)
    PersonName(country=美国)
    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);

    运行结果:

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

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

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

    运行结果为:

    aaabbbcccdddddd

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

    3.2 flatMap举例

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

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

    运行结果为:

    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);

    运行结果:

    Person(name=欧阳雪, age=18, country=中国, sex=F)
    Person(name=向天笑, age=20, country=中国, sex=M)
    Person(name=小梅, age=20, country=中国, sex=F)
    Person(name=何雪, age=21, country=中国, sex=F)
    Person(name=Harley, age=22, country=英国, sex=F)
    Person(name=李康, age=22, country=中国, sex=M)
    Person(name=李康, age=22, country=中国, sex=M)
    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);

    运行结果:

    是否都是成年人:true
    是否都是中国人: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());

    运行结果:

    年龄最大的人信息:Person(name=Tom, age=24, country=美国, sex=M)
    年龄最小的人信息: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);
    结果为:5050

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

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

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

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

    5.2 求所有人的年龄之和

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

    输出结果:

    [中国, 美国, 英国]

    6.2 计算出平均年龄

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

    输出结果:

    平均年龄为: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 }
  • 相关阅读:
    在Win7 x64环境中将World Wind Java SDK 2.1.0嵌入到Eclipse中的方法
    WW中文地名标注:输出*.wwp和*.wpl文件
    [转]Microsoft Robotics Studio:微软仿真机器人集成开发环境,简称MSRS
    C#中定义类时关于CLSCompliant属性的声明
    Android Studio中使用Java+OpenGL ES创建Android项目
    [转]使用Unity进行3D开发的思路和主要技术优势
    在C++中实现委托事件的方法
    VS2008新建MFC程序时提示:当前页面的脚本发送错误 不是有效的Win32应用程序的解决办法
    [Web 前端] mockjs让前端开发独立于后端
    [Web 前端] 如何构建React+Mobx+Superagent的完整框架
  • 原文地址:https://www.cnblogs.com/wuhenzhidu/p/10740091.html
Copyright © 2020-2023  润新知