美文网首页
Java8新特性之流式操作

Java8新特性之流式操作

作者: 青年心路 | 来源:发表于2020-03-08 14:15 被阅读0次

    什么是流式操作

    Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

    Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

    Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

    这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

    元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。

    1.流式操作举例

    1.1创建实体类

    public class Person {
    
        private String name;
        private Integer age;
        private Integer score;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Integer getScore() {
            return score;
        }
    
        public void setScore(Integer score) {
            this.score = score;
        }
    
        public Person() {
        }
    
        public Person(String name, Integer age, Integer score) {
            this.name = name;
            this.age = age;
            this.score = score;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", score=" + score +
                    '}';
        }
    }
    

    1.2 传统的对象初始化方式

    public class Program {
        public static void main(String[] args) {
            //使用构造器设置对象信息
    //        Person xiaomign = new Person("小明", 28, 90);
    
            //使用getter、setter方式设置对象信息
            Person xiaoming = new Person();
            xiaoming.setName("小明");
            xiaoming.setAge(18);
            xiaoming.setScore(90);
        }
    }
    

    1.3 使用流式操作初始化对象

    1.3.1 修改实体类

    public class Person {
    
        private String name;
        private Integer age;
        private Integer score;
    
        public String getName() {
            return name;
        }
    
        public Person setName(String name) {
            this.name = name;
            return this;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public Person setAge(Integer age) {
            this.age = age;
            return this;
        }
    
        public Integer getScore() {
            return score;
        }
    
        public Person setScore(Integer score) {
            this.score = score;
            return this;
        }
    
        public Person() {
        }
    
        public Person(String name, Integer age, Integer score) {
            this.name = name;
            this.age = age;
            this.score = score;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", score=" + score +
                    '}';
        }
    }
    

    1.3.2 使用流式操作

    //流式操作
    xiaoming.setName("小明").setAge(20).setScore(100);
    

    2.集合的流式操作

    集合的流式操作是Java8的一个新特性,流式操作不是一个数据结构,不负责任何的数据存储,它更像是一个迭代器,可以有序的获取数据源中的每一个数据,并且可以对这些数据进行一些操作。流式操作的每一个方法的返回值都是这个流的本身

    2.1 流式操作的三个步骤

    2.1.1 获取数据源:集合、数组

    • 设置数据源

      public class Data {
      
          /**
           * 数据源
           */
          public static ArrayList<Person> getData() {
              ArrayList<Person> list = new ArrayList<Person>();
      
              list.add(new Person("小明", 18, 100));
              list.add(new Person("小丽", 19, 70));
              list.add(new Person("小王", 22, 85));
              list.add(new Person("小张", 20, 90));
              list.add(new Person("小黑", 21, 95));
              return list;
          }
      }
      
    • 获取数据源的方式

      public class Program {
          public static void main(String[] args) {
      
              // 获取数据源方式1
              Stream stream = Data.getData().stream();
      
              // 获取数据源方式2
              Stream.of(Data.getData());
              
              // 获取数据源方式3
                //数据源为数组
          }
      }
      

    2.1.2 对数据进行处理的过程:过滤、排序、映射等(中间操作)

    中间操作1:filter

    • 使用filter自定义条件过滤数据

      // 中间操作1: filter
      // filter是一个过滤器,可以自定义一个过滤条件,将流中满足条件的元素保留
      // 查找集合中成绩小于80的学生
      List<Person> list = Data.getData().stream()
          .filter(ele -> ele.getScore() < 80)
          .collect(Collectors.toList());
      System.out.println(list);
      
    image

    中间操作2:distinct

    • 使用distinct实现去重操作

      在数据源中添加重复的数据

      list.add(new Person("小黑", 21, 95));   //此时list中有两个小黑
      

      在实体类中重写hashCode()和equals()方法

      @Override
      public boolean equals(Object o) {
          if (this == o) return true;
          if (o == null || getClass() != o.getClass()) return false;
          Person person = (Person) o;
          return Objects.equals(name, person.name) &&
              Objects.equals(age, person.age) &&
              Objects.equals(score, person.score);
      }
      
      @Override
      public int hashCode() {
          return Objects.hash(name, age, score);
      }
      

      去重规则:

      • 先判断对象的hashCode()

      • 如果hashCode()相同再判断equals()

      // 中间操作2: distinct
      // distinct: 取出集合中不同的元素
      // 去重规则:
      // 1.先判断对象的hashCode()
      // 2.如果hashCode()相同再判断equals()
      Data.getData().stream().distinct().forEach(System.out::println);
      
    image

    注意:如果小黑的数据相同却要保存两份,可以在hashCode()方法中返回一个随机数,随机数很小概率会相同,为了确保稳定性,可以将equals()方法改为返回false,这样可以保留两个信息相同的小黑。

    中间操作3:sorted

    • 使用sorted()方法以成绩进行升序排序

      要求实体类实现Comparable接口并重写方法

      // 中间操作3: sorted
      // sorted: 对返回的元素进行排序
      // sorted(): 要求实体类实现Comparable接口并重写方法
      Data.getData().stream().sorted().forEach(System.out::println);
      
    image

    中间操作4:limit

    • 在数据源中取前三个数据

      // 中间操作4: limit
      // limit: 限制,只取流中前指定位的数据
      // 在数据源中取前三个数据
      Data.getData().stream().limit(3).forEach(System.out::println);
      
    image

    中间操作5:skip

    • 跳过前三个元素,取后面剩下的元素

      // 中间操作5: skip
      // skip: 跳过
      // 跳过前三个元素,取后面剩下的元素
      Data.getData().stream().skip(3).forEach(System.out::println);
      
    image

    中间操作6:map

    元素映射,用指定的元素替换掉流中的元素

    • 使用map将对象替换为对象的名字

      // 中间操作6: map
      // map: 元素映射,用指定的元素替换掉流中的元素
      // 将流中的Person对象替换位他们的姓名
      Data.getData().stream().map(ele -> ele.getName()).forEach(System.out::println);
      
    image

    2.1.3 对流中数据的整合:转成集合、数量(最终操作)

    最终操作1:collect

    • 转换为List

      public class Program {
          public static void main(String[] args) {
      
              // 获取数据源方式1
              Stream<Person> stream = Data.getData().stream();
      
              // 最终操作1: collect,配合Collectors使用
              // 将集合中的元素转换成List
              List<Person> list = stream.collect(Collectors.toList());
      
              System.out.println(list);
          }
      }
      

      运行结果

    image
    • 转换为set

      // 将集合中的元素转换为Set
      Set<Person> set = stream.collect(Collectors.toSet());
      System.out.println(set);
      

      运行结果

    image
    • 转换为map

      // 转换为Map(name为键,score为值)
              // 方式1
      //        Map<String, Integer> map = stream.collect(Collectors.toMap(
      //                ele -> ele.getName(),
      //                ele -> ele.getScore()
      //        ));  
              
              // 方式2        
              Map<String, Integer> map = stream.collect(Collectors.toMap(
                      Person::getName,
                      Person::getScore
              ));
      

      运行结果

    image

    最终操作2:reduce

    reduce的思想

    比如在计算一个数组中的元素的和时,首先会计算前两个数的和,然后拿着前两个数的和与第三个数求和,计算出结果后将三个数的和与第四个数相加,以此类推。

    image
    • 计算数组中数据的和

      // 最终操作2: reduce(将数据汇总在一起)
      Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
      Optional<Integer> res = stream1.reduce((n1, n2) -> n1 + n2);
      // 获取到最终的返回值
      System.out.println(res.get());
      
    image
    • 使用reduce计算Person对象中成绩的和

      // 计算Person中Score的和
      Optional<Person> res = stream.reduce(
          (n1, n2) -> new Person().setScore(n1.getScore() + n2.getScore())
      );
      System.out.println(res.get().getScore());
      
    image

    缺点:上面的写法每次都会产生一个临时的对象,产生了不必要的性能损耗

    • 使用reduce计算Person对象中成绩的和(优化)

      // 计算Person中Score的和(使用临时变量,减少性能开销)
      Person temp = new Person();
      Optional<Person> res = stream.reduce(
          (n1, n2) -> temp.setScore(n1.getScore() + n2.getScore())
      );
      System.out.println(res.get().getScore());
      
    image

    最终操作3:max和min

    • 使用max找出Person中成绩最高的人

      // 最终操作3: max和min
      // 需求1: 找到集合中成绩最高的人的信息
      Person max = stream.max(
          (ele1, ele2) -> ele1.getScore() - ele2.getScore()
      ).get();
      System.out.println(max);
      
    image
    • 使用min找出Person中成绩最低的人

      // 需求2: 找到集合中成绩最低的人的信息
      Person min = stream.min(
          (ele1, ele2) -> ele1.getScore() - ele2.getScore()
      ).get();
      System.out.println(min);
      
    image

    最终操作4:matching

    • 使用anyMatch查看集合中是否有成绩高于80的人

      // 判断集合中是否包含成绩大于80的学员
      boolean res1 = stream.anyMatch((ele) -> ele.getScore() > 80);
      System.out.println(res1);
      
    image
    • 使用allMatch查看集合中的成绩是否全部高于60

      //查看集合中的人的成绩是否全部高于60
      boolean res2 = stream.allMatch((ele) -> ele.getScore() > 60);
      System.out.println(res2);
      
    image
    • 使用noneMatch查看集合中的人的分数是否不包含80以下的

      boolean res3 = stream.noneMatch((ele) -> ele.getScore() < 80);
      System.out.println(res3);
      
    image

    最终操作5:count

    • 使用count计算元数据中有多少条数据

      // 最终操作5: 求元数据中有多少个元素
      long count = stream.count();
      System.out.println(count);
      
    image

    最终操作6:forEach

    • 使用forEach遍历集合中的元素

      // 最终操作6: forEach
      // stream.forEach(ele -> System.out.println(ele));
      stream.forEach(System.out::println);
      
    image

    最终操作7:findFirst和findAny

    • FindFirst: 获取流中的第一个元素
      FindAny: 获取流中任意一个元素(并不是随机获取元素)
      对于串行流,结果等同于findFirst
      findAny用于并行流中可能会与findFirst一样,也可能不一样
    // FindFirst: 获取流中的第一个元素
    // FindAny: 获取流中任意一个元素(并不是随机获取元素)
    //          对于串行流,结果等同于findFirst
    //          findAny用于并行流中可能会与findFirst一样,也可能不一样
    System.out.println(Data.getData().parallelStream().findFirst());
    System.out.println(Data.getData().stream().findFirst());
    System.out.println(Data.getData().parallelStream().findAny());
    System.out.println(Data.getData().stream().findAny());
    

    最终操作的注意事项

    • 为什么会被称为最终操作?

      Person max = stream.max(
          (ele1, ele2) -> ele1.getScore() - ele2.getScore()
      ).get();
      Person min = stream.min(
          (ele1, ele2) -> ele1.getScore() - ele2.getScore()
      ).get();
      
    image

    报错信息表示流正在被处理或者已经被关闭了,如果已经被关闭了再次调用当然会报错,这也是为什么叫最终操作的原因。

    3.并行流

    3.1 获取并行流的方式

    // 并行流
    // 获取并行流的两种方式
    Data.getData().stream().parallel();
    Data.getData().parallelStream();
    

    3.2 并行流与串行流对比

    // 串行流: 19920ms
    // 并行流: 12204ms
    long startTime = System.currentTimeMillis();
    //LongStream.rangeClosed(0L, 50000000000L)
    //    .reduce(Long::sum);
    LongStream.rangeClosed(0L, 50000000000L)
        .parallel()
        .reduce(Long::sum);
    long endTime = System.currentTimeMillis();
    
    System.out.println(endTime - startTime);
    

    3.3 flatMap

    String[] array = {"hello", "world"};
    // 需要获取所有字符 List -> h, e, l, l, o, w, o, r, l, d
    //        Arrays.stream(array)
    //                .map(ele -> ele.split(""))
    //                .forEach(ele -> System.out.println(ele.length));
    System.out.println(Arrays.stream(array)
                       .map(ele -> ele.split(""))
                       .flatMap(Arrays::stream)
                       .collect(Collectors.toList()));
    
    image

    4.Collectors

    Collectors是一个工具类,提供着若干个方法,返回一个Collector接口的实现类对象

    4.1 maxBy

    ​ 通过指定的规则获取流中最大的元素

    System.out.println(Data.getData().stream()
                    .collect(Collectors.maxBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));
    
    image

    4.2 minBy

    ​ 通过指定的规则获取流中最小的元素

    System.out.println(Data.getData().stream()
                    .collect(Collectors.minBy((ele1, ele2) -> ele1.getScore() - ele2.getScore())));
    
    image

    4.3 joining

    合并,将流中的元素,以字符串的形式拼接起来

    // 把Person中的姓名拼成一个字符串
    String res1 = Data.getData().stream()
        .map(Person::getName)
        .collect(Collectors.joining());
    System.out.println(res1);
    
    image
    String res2 = Data.getData().stream()
        .map(Person::getName)
        .collect(Collectors.joining("-"));
    System.out.println(res2);
    
    image
    String res3 = Data.getData().stream()
        .map(Person::getName)
        .collect(Collectors.joining("-", "{", "}"));
    System.out.println(res3);
    
    image

    4.4 summingInt

    计算int类型的和,将流中的元素映射为int类型的元素进行求和

    • 将Person对象的成绩进行求和

      // 将Person对象的成绩进行求和
      System.out.println(Data.getData().stream()
                         .collect(Collectors.summingInt(ele -> ele.getScore())));
      
    image

    4.5 averagingInt

    计算int类型的平均值

    • 计算不及格学生的平均成绩

      System.out.println(Data.getData().stream()
                         .filter(ele -> ele.getScore() < 60)
                         .collect(Collectors.averagingInt(Person::getScore)));
      

    4.6 summarizingInt

    将流中的元素映射成int类型的元素,获取这些数据的描述信息

    System.out.println(Data.getData().stream()
                       .collect(Collectors.summarizingInt(ele -> ele.getScore())));
    
    image

    相关文章

      网友评论

          本文标题:Java8新特性之流式操作

          本文链接:https://www.haomeiwen.com/subject/zkvddhtx.html