【Java】`Stream流`的中间方法

概念:中间操作的意思是,执行完此方法之后,Stream流依然可以继续执行其他操作

一、总述

常见方法会有以下六个

方法名说明
Stream<T> filter(Predicate predicate)过滤
Stream<T> limit(long maxSize)获取前几个元素
Stream<T> skip(long n)跳过前几个元素
Stream<T> distinct()元素去重,依赖(hashCode()equals())
static <T> Stream<T> concat(Stream a, Stream b)合并a和b两个流为一个大流
Stream<R> map(Function<T, R> mapper)转换流中的数据类型

注意1:中间方法会返回新的 Stream流,原来的 Steam流 只能使用一次,建议使用链式编程

注意2:修改 Steam流 中的数据,是不会影响原来集合或者数组中的数据


二、过滤

Stream<T> filter(Predicate predicate)

方法的形参是 Predicate,选中它跟进,可以发现它是一个函数式接口。

image-20240430092043367

我们先使用匿名内部类,然后再使用 Lambda表达式 简化

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");

//filter   过滤  把张开头的留下,其余数据过滤不要
list.stream().filter(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        //如果返回值为true,表示当前数据留下
        //如果返回值为false,表示当前数据舍弃不要
        return s.startsWith("张");
    }
}).forEach(s -> System.out.println(s));

简化为 Lambda表达式

list.stream()
    .filter(s -> s.startsWith("张"))
    .filter(s -> s.length() == 3)
    .forEach(s -> System.out.println(s));

注意1:中间方法会返回新的 Stream流,原来的 Steam流 只能使用一次,建议使用链式编程

例如下面不用链式编程,而是直接获取到返回值为 stream1,然后再使用 stream1 来进行过滤。

过滤完成后,再将数据打印,可以发现是没有任何的问题的。

Stream<String> stream1 = list.stream().filter(s -> s.startsWith("张"));
Stream<String> stream2 = stream1.filter(s -> s.length() == 3);
stream2.forEach(s -> System.out.println(s));

但是在下面的代码中,如果还想再次使用 stream1,可以发现就会报错:stream has already been operated upon or closed(流已经关闭了)

image-20240430092957105

原因是因为,在 42行 这里,stream1 已经用过了,就不能再次使用了。

所以一定要切记:原来的 Steam流 只能使用一次。

既然只能使用一次,那么我们就没有必要再去用一个变量去记录了,因此建议使用链式编程。

因此当 Stream流 写熟了后发现,所以的操作一行就搞定了,代码非常的简单。

但是代码变少了也会有一个小弊端:代码的阅读性降低。

因此我们一般都会将链式编程的每一个操作都折行,这样就会提高代码的阅读性。

list.stream()
    .filter(s -> s.startsWith("张"))
    .filter(s -> s.length() == 3)
    .forEach(s -> System.out.println(s));

注意2:修改 Steam流 中的数据,是不会影响原来集合或者数组中的数据

例如上面我们对集合做了两次过滤,此时再来打印集合,此时集合里面的数据是不会改变的。


三、获取前几个元素

maxSize 跟索引没有关系,就是字面意思:几个元素

Stream<T> limit(long maxSize)

代码示例

// "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤"
list.stream().limit(3).forEach(s -> System.out.println(s)); // 张无忌 周芷若 赵敏 

四、跳过前几个元素

参数 n 表示的也是个数

Stream<T> skip(long n)

代码示例

// "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤"
list.stream().skip(4).forEach(s -> System.out.println(s)); // 张三丰 张翠山 张良 王二麻子 谢广坤 

五、limitskip 练习

课堂练习:在 "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤" 中获取以下元素
"张强", "张三丰", "张翠山"

代码示例

//第一种思路:
//先获取前面6个元素:"张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山",
//然后跳过前面3个元素
list.stream().limit(6).skip(3).forEach(s -> System.out.println(s));

//第二种思路:
//先跳过3个元素:"张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤"
//然后再获取前面3个元素:"张强", "张三丰", "张翠山"
list.stream().skip(3).limit(3).forEach(s -> System.out.println(s));

六、元素去重

Stream<T> distinct()

代码示例

ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1, "张无忌","张无忌","张无忌", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");
list1.stream().distinct().forEach(s -> System.out.println(s));

PS:distinct() 在底层是依赖 hashCode()equals() 进行去重的。

由于在 String 中,Java已经帮我们重写好了这两个方法,因此我们直接使用就行了。

但如果集合中装的是 自定义对象,那么一定要手动重写。

我们可以简单看一下 distinct() 里面的源码,源码非常的复杂,我们只需要找里面的核心点就行了。

选中 distinct() ctrl + b,如果直接 ctrl +b ,那么它就是跳到接口里面的方法。

image-20240430095559034

而我们需要看的是它的实体类,因此需要采用以下方法,或者 ctrl + alt + b

image-20240430095637839

它底层会调用 makeRef(),继续跟进

image-20240430144721811

点进去后,可以发现它的源码超级多,但不要慌,我们真正看的是里面的一个核心点:keys = new HashSet<>(keys);

所以我们就知道,在底层是通过 HashSet 进行去重的。

image-20240430144821310

HashSet 在存储自定义元素的时候,需要重写 hashCode()equals()


七、合并a和b两个流为一个大流

在合并的时候,尽可能要保证两个流中的数据类型是一致的。

如果第一个流数据类型是 a,第二个流数据类型是 b,那么这个时候在合并的时候,它的类型就是 ab 共同的父类了,这样就相当于它做了一个类型的提升,在提升之后,它是无法使用子类里面的特有功能的。

static <T> Stream<T> concat(Stream a, Stream b)

这个方法是 Stream 里面的静态方法,所以在使用的时候需要使用类名调用

ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1, "张无忌","张无忌","张无忌", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");

ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2, "周芷若", "赵敏");

// 方法参数中传递需要合并的两个流,程序运行完毕发现,前面是第一个流里面的数据,后面就是第二个流中的
Stream.concat(list1.stream(),list2.stream()).forEach(s -> System.out.println(s)); // 张无忌 张无忌 张无忌 张强 张三丰 张翠山 张良 王二麻子 谢广坤 周芷若 赵敏 

八、转换流中的数据类型

Stream<R> map(Function<T, R> mapper)

需求:只获取里面的年龄并进行打印

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌-15", "周芷若-14", "赵敏-13", "张强-20", "张三丰-100", "张翠山-40", "张良-35", "王二麻子-37", "谢广坤-41");

分析:流里面原本的数据是 String 类型的,最终我需要获得 int 类型的。

因此需求其实就是将 String 变成 int类型

map() 的形参是 Function,而 Function 又是函数式接口。接口中有一个 apply()

image-20240430150416812

Function 中有两个泛型:

  • 第一个类型:流中原本的数据类型
  • 第二个类型:要转成之后的类型

apply形参s:依次表示流里面的每一个数据;返回值:表示转换之后的数据。

我们的需求是将字符串变成 int类型,但是不能直接将 int 写在泛型里,因为泛型里面不能写基本数据类型。

image-20240430151307912

因此正确写法应该是写成:Integer,同样 apply() 的返回值需要与 Function 的泛型相对应,这两个都表示转换之后的类型。

image-20240430151431118
list.stream().map(new Function<String, Integer>() {
    @Override
    public Integer apply(String s) {
        String[] arr = s.split("-");
        String ageString = arr[1];
        int age = Integer.parseInt(ageString);
        return age;
    }
})
    //当map方法执行完毕之后,流上的数据就变成了整数
	//所以在下面forEach当中,s依次表示流里面操作完毕后的每一个数据,这个数据现在就是整数了
    .forEach(s-> System.out.println(s));

改成 Lambda表达式

list.stream()
    .map(s-> Integer.parseInt(s.split("-")[1]))
    .forEach(s-> System.out.println(s));
  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值