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<? super T> 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<T,R> mapper)返回由给定函数应用于此流的元素的结果组成的流,即转换流中的元素

注意:

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

​ 方法名:Stream<T> filter(Predicate<? super T> 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])));
  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值