• Java Stream流


    Stream流

    1、Stream例子

    需求:

    ​ 创建一个集合,存储多个字符串元素,完成:

    1. 把所有以”张“开头的元素存储到新的集合中。
    2. 把”张“开头,且长度为3的元素存储的新的集合中。
    3. 打印最终结果。

    不使用Stream:

          ArrayList<String> list = new ArrayList<String>() {{
            add("张无忌");
            add("周芷若");
            add("赵敏");
            add("张强");
            add("张三丰");
          }};
          // 1、把所有以”张“开头的元素存储到新的集合中。
          ArrayList<String> list2 = new ArrayList<String>();
          for (String s : list) {
            if (s.startsWith("张")) {
              list2.add(s);
            }
          }
          // 2、把”张“开头,且长度为3的元素存储的新的集合中。
          ArrayList<String> list3 = new ArrayList<String>();
          for (String s : list2) {
            if (s.length() == 3) {
              list3.add(s);
            }
          }
          // 3、输出结果
          list3.forEach(System.out::println);
    

    使用Stream:

      ArrayList<String> list = new ArrayList<String>() {{
        add("张无忌");
        add("周芷若");
        add("赵敏");
        add("张强");
        add("张三丰");
      }};
      list.stream().filter(s -> s.startsWith("张"))
        			.filter(s -> s.length() == 3)
        			.forEach(System.out::println);
    

    分析:

    ​ 可见,Stream流极大简化了代码,使代码十分简洁。

    2、流的思想

    ​ 可以把流理解成流水线,每一步都对数据进行一定的操作。

    上面流的例子中,就类似于工程流水线,做了如下操作:

    过滤以'张'开头的元素
    过滤三个字的元素
    张无忌、周芷若、赵敏、张强、张三丰
    张无忌、张强、张三丰
    张无忌、张三丰
    遍历输出

    3、流的作用

    ​ 结合lambda表达式,简化对流、数组的操作。

    4、流的使用

    使用步骤:

    1. 先得到一条Stream流、并将数据放上去。
    2. 使用Stream流中的API对数据进行各种操作(过滤、转换、统计、打印等)。

    其中各种API被分为:

    ​ **中间方法:**方法返回值还是Stream流,也就是说中间方法操作完后,还可以调用其他API。

    ​ **终结方法:**最后一步,调用完毕之后,不能再调用其他API了。

    a、获取Stream流
    获取方式方法名说明
    单列集合default Stream stream()Collection中默认方法
    双列集合无法直接使用stream流,需要使用keySet、values、entrySet转为Set后再使用Set的流操作。
    数组public static Stream stream(T[] array)Arrays工具类中的静态方法
    一堆零散数据public static Stream of(T…values)Stream接口中的静态方法

    单列集合:

      ArrayList<Integer> list = new ArrayList<>();// 创建单列集合
      Collections.addAll(list, 1, 2, 3, 4, 5);// 添加数据
      Stream<Integer> stream = list.stream();// 获取单列集合的stream流
    

    双列集合:

    	HashMap<String,Integer> map = new HashMap<String,Integer>(){
        {
          put("aaa",111);
          put("bbb",111);
          put("ccc",111);
          put("ddd",111);
        }
      };// 创建双列集合并添加数据
      Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream(); //获取键值对流
      Stream<String> keyStream = map.keySet().stream();// 获取键的流
      Stream<Integer> valueStream = map.values().stream();// 获取值的流
    

    数组:

      int[] arr1 = {1, 2, 3, 4}; // 创建基础数据类型数组
      String[] arr2 = {"a","b","c"}; // 创建引用数据类型数组
      IntStream intStream = Arrays.stream(arr1); // 创建基础数据类型数组的流
      Stream<String> stringStream = Arrays.stream(arr2);// 创建引用类型数组的流
    

    零散数据:

      Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5); //注意,基础数据类型自动会装箱为引用数据类型
      Stream<String> stringStream1 = Stream.of("a", "b", "c");// 引用数据类型流
    

    Stream.of()源码:

    ​ 可见,源码中传入的是一个泛型的可变参数,也就是说可以传递数组。

    ​ 但是要注意,这里可以传递引用数据类型的数组,不可以传递基础数据类型数组。

    ​ 因为会将基础数据类型数组整体当作一个参数,并不自动装箱。

        @SafeVarargs
        @SuppressWarnings("varargs") // Creating a stream from an array is safe
        public static<T> Stream<T> of(T... values) {
            return Arrays.stream(values);
        }
    
      // 基础数据类型数组使用Stream.of()方法构造流 
      int[] arr1 = {1, 2, 3, 4};
      Stream.of(arr1).forEach(System.out::println); //这里会直接输出arr1的地址,而不会一次输出值
    	// 意味着基础数据类型会被当作一个整体,并不进行自动装箱。
    
    b、Stream中间方法

    ​ 中间方法都是对流做一定操作后返回操作后的流。

    ​ 因为返回值还是流,所以可以继续进行流操作,因此叫做中间方法。

    常见方法:

    方法作用
    Stream filter(Predicate predicate)过滤,根据传入的方法返回值过滤
    Stream limit(long maxSize)截取前指定参数个数的数据
    Stream skip(long n)跳过指定参数个数的数据,返回由该流的剩余元素组成的流
    Stream distinct()元素去重,依赖hasCode()equals()方法
    static Stream concat(Stream a, Stream b)合并a和b两个流为一个流,如果两个流类型不同则会转换为二者的共同父类
    Stream map(Function mapper)返回由给定函数应用于此流的元素的结果组成的流,即转换流中的元素

    注意:

    1. 中间方法都是对流进行一定操作后返回新的流,原来的流就不存在了,建议使用链式编程。
      这里说的原来流不存在指的是:
      1. 将流1赋值给变量stream1
      2. 操作stream1后将新的流赋值给变量stream2
      3. 此时再次操作stream1就会报错:stream has already bean operated or closed
    2. 修改集合生成的流里的数据,不会影响集合或数组原来的数据。
    1.filter():过滤

    ​ 方法名:Stream filter(Predicate predicate)

    ​ Predicate:是一个函数式接口,需要实现boolean test(T t);方法。

    ​ 该方法的返回值为true则留下当前元素,否则过滤掉。

    ​ 因此,可以使用lambda去实现这个函数式接口。

    2.limit()和skip():

    ​ 这两个方法中传递的参数和索引无关,都是多少个。

    ​ 比如limit(3)指的是只取前三个元素,而不是索引为3的元素前所有元素。

    3.distinct():去重

    ​ 底层使用HashSet存储元素,而HashSet依赖hasCode()equals()方法进行去重。

    4.concat():合并

    ​ 合并两个流的时候,尽可能让两个流类型一致,不然合并后就是两个流的共同父类,这样就会使得子类特有方法无法使用。

    5.map():转换
            List<String> list = new ArrayList<>();
            Collections.addAll(list, "张三-20", "李四-21", "王五-22", "赵六-23");
            // 需求:打印每个人的年龄
            // 分析:就是将字符串转换为了数值类型
            /**
             * 这个函数式接口有两个泛型:
             *  第一个是传入参数的泛型,在本例中为String。
             *  第二个是返回值参数的泛型,在本例中为Integer。
             * 需要实现方法apply(),其参数就是第一个泛型,返回值类型就是第二个泛型
             * 注意:泛型不能为基础数据类型
             */
            list.stream().map(new Function<String, Integer>() {
                @Override
                public Integer apply(String s) {
                    String[] split = s.split("-");
                    String result = split[1];
                    return Integer.parseInt(result);
                }
                // 在map()方法后,新流的类型就已经变成了Integer
            }).forEach(System.out::println);
    
            // 上述代码使用lambda就可以简写为:
            list.stream().map(s -> Integer.parseInt(s.split("-")[1])).forEach(System.out::println);
    
    c、Stream终结方法

    ​ 终结方法的返回值不再是流,因此无法继续进行流操作。

    常见方法:

    方法作用
    void forEach(Consumer action)遍历
    long count()统计
    toArray()收集流中数据,放到数组中。
    collect(Collector collector)收集流中数据,放到集合中。
    1.forEach():遍历
    				List<String> list = new ArrayList<>();
            Collections.addAll(list, "张三-20", "李四-21", "王五-22", "赵六-23");
            // 需求:打印每个人的年龄
            // 分析:就是将字符串转换为了数值类型
            /**
             * Consumer泛型:表示流中数据的类型。
             * 函数式接口需实现方法action()参数 s:依次表示流里的每一个数据
             */
            list.stream().forEach(new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s.split("-")[1]);
                }
            });
    
            // 上述代码使用lambda就可以简写为:
            list.stream().forEach(s -> System.out.println(s.split("-")[1]));
    
    2.count():统计

    ​ 该方法主要用来统计流过滤后的数据个数,返回值为long类型的整数。

    3.toArray():收集到数组
    //重载了两个同名方法
    // 1.无参数:返回一个包含此流的元素的数组(Object类型)
    Object[] toArray();
    // 2.返回一个包含此流元素的数组,使用提供的 generator 函数来分配返回的数组
    //  以及分区执行或调整大小可能需要的任何其他数组。
    <A> A[] toArray(IntFunction<A[]> generator);
    

    带参数toArray():

    				List<String> list = new ArrayList<>();
            Collections.addAll(list, "张三-20", "李四-21", "王五-22", "赵六-23");
            /**
             * 泛型:? extends Object[]:指的是继承至Object类型的数组,本例中为字符串数组
             * 函数式方法apply()参数:流中数据的个数,要跟流中个数一致
             *  返回值:具体类型的数组
             *  方法体:创建数组
             * toArray()方法作用就是:创建一个指定类型的数组。
             *  底层实现:会依次得到流里的每一个数据,并把数据放到数组中。
             */
            list.stream().toArray(new IntFunction<String[]>() {
                @Override
                public String[] apply(int value) {
                    return new String[value];
                }
            });
            // 使用lambda简写
            list.stream().toArray(value -> new String[value]);
    
    4.collect():收集到集合
    			/*
                collect(Collector collector)收集流中的数据,放到集合中 (List Set Map)
                注意点:
                    如果我们要收集到Map集合当中,键不能重复,否则会报错
           */
            ArrayList<String> list = new ArrayList<>();
            Collections.addAll(list, "张无忌-男-15", "周芷若-女-14", "赵敏-女-13", "张强-男-20",
                    "张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");
    
            //收集List集合当中
            //需求:
            //我要把所有的男性收集起来
            List<String> newList1 = list.stream()
                    .filter(s -> "男".equals(s.split("-")[1]))
                    .collect(Collectors.toList());
    
            //收集Set集合当中
            //需求:
            //我要把所有的男性收集起来
            Set<String> newList2 = list.stream().filter(s -> "男".equals(s.split("-")[1]))
                    .collect(Collectors.toSet());
            //System.out.println(newList2);
    
            //收集Map集合当中
            //谁作为键,谁作为值.
            //我要把所有的男性收集起来
            //键:姓名。 值:年龄
            Map<String, Integer> map = list.stream()
                    .filter(s -> "男".equals(s.split("-")[1]))
                    /*
                     *   toMap : 参数一表示键的生成规则
                     *           参数二表示值的生成规则
                     *
                     * 参数一:
                     *       Function泛型一:表示流中每一个数据的类型
                     *               泛型二:表示Map集合中键的数据类型
                     *
                     *        方法apply形参:依次表示流里面的每一个数据
                     *               方法体:生成键的代码
                     *               返回值:已经生成的键
                     *
                     *
                     * 参数二:
                     *        Function泛型一:表示流中每一个数据的类型
                     *                泛型二:表示Map集合中值的数据类型
                     *
                     *       方法apply形参:依次表示流里面的每一个数据
                     *               方法体:生成值的代码
                     *               返回值:已经生成的值
                     *
                     * */
                    .collect(Collectors.toMap(
                      			new Function<String, String>() {
                              @Override
                              public String apply(String s) {
                                //张无忌-男-15
                                return s.split("-")[0];
                              }
                            },
                            new Function<String, Integer>() {
                                @Override
                                public Integer apply(String s) {
                                    return Integer.parseInt(s.split("-")[2]);
                                }
                            }));
            Map<String, Integer> map2 = list.stream()
                    .filter(s -> "男".equals(s.split("-")[1]))
                    .collect(Collectors.toMap(
                            s -> s.split("-")[0],
                            s -> Integer.parseInt(s.split("-")[2])));
    
  • 相关阅读:
    02-分布式协调服务ZooKeeper
    Azure DevOps 介绍
    开启全新教学模式!vLive虚拟直播如何赋能线上教培
    SQL(及存储过程)跑得太慢怎么办?
    Windows使用进程监视器查看进程读写的文件名
    mpvue小程序 vant组件单选
    表格的单元格合并和表头的合并——vxe-table
    Python统计学08——一元线性回归
    LeetCode·每日一题·779.第K个语法符合·递归
    Spark 面试题(十六)
  • 原文地址:https://blog.csdn.net/qq_63635872/article/details/139296368