Java stream流的使用、详解

一、概述

stream流是jdk8的新增特性,为的就是方便对集合、数组的操作

二、Stream流的思想

流,其实可以理解成工厂的流水线,在流水线上,会对物品进行各种操作

在这里插入图片描述

java中流的思想与这个一样,我们会先对数据(集合、数组)开启流(相当与是将数据放在流水线上),然后调用过滤方法(对数据进行操作),最后调用终结方法(对剩余的数据进行输出操作)

使用步骤:

  1. 先得到一条Stream流(流水线),并把数据放上去
  2. 使用中间方法,对流水线上的数据进行操作
  3. 使用终结方法,对流水线上的数据进行操作

三、流的种类

流,有串行流和并行流两种

串行流:在串行流中,对于每个元素,依次执行完所有中间操作,然后再处理下一个元素。这意味着在一个元素上执行完所有中间操作之后,才会开始下一个元素的处理。

并行流:在并行流中,元素会被分割成多个子集,在不同的线程中并行处理。这意味着一个元素的中间操作可能与其他元素的中间操作交叉进行,因为它们在不同的线程中执行。

四、获取流

不同的数据结构获取流的方式有所不同

单列集合

单列集合,直接调用Collection中的默认方法.stream()

// 单列集合获取流
ArrayList<String> list = new ArrayList<>();
/*
// 获取到一条流水线,并把集合数据放到流水线上
Stream<String> stream = list.stream();
// 调用终结方法打印流水线上的数据
stream.forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
});
*/
list.stream().forEach(s-> System.out.println(s));

如果想开启并行流,可以

// 调用.parallelStream()方法开启并行流
list.parallelStream().forEach(System.out::println);

双列集合

双列集合,map,无法直接获取流,但是可以对map中所有的键获取流(键流),所有的值获取流(值流),所有的键值对获取流。

其实将map分为键、值、键值对之后,就是单列集合,所以开启并行流的方式也和单列集合一样。

// 双列集合获取流
HashMap<String, Integer> map = new HashMap<>();
Set<String> keys = map.keySet();                            //键
Collection<Integer> values = map.values();                  //值
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();  //键值对
// 分别对键、值、键值对 开启流,并调用终结方法输出
keys.stream().forEach(System.out::println);
values.stream().forEach(System.out::println);
entrySet.stream().forEach(System.out::println);

数组

数组获取流,需要通过Arrays工具类中所提供的stream()方法

// 数组获取流
int[] str = {1,2,3,4,5,6};
Arrays.stream(str).forEach(System.out::println);

默认情况下,Stream.of 返回的是串行流,除非你显式地调用 .parallel() 方法将其转换为并行流。

Arrays.stream(str).parallel().forEach(System.out::println);

零散数据

零散数据想要获取stream流,这些零散数据的类型需要是一样的

// 零散数据获取stream流
// 使用Stream接口提供的.of()方法
Stream.of(1,2,3,4,5,6).forEach(System.out::println);
Stream.of("1","2","3","4","5","6").forEach(System.out::println);

默认情况下,Stream.of 返回的也是串行流,除非你显式地调用 .parallel() 方法将其转换为并行流。

Stream.of(str).parallel().forEach(System.out::println);

小细节:of()方法的形参是一个可变类型的参数,可以传递一堆零散的数据,也可以传递数组。但是数组必须是引用类型的,如果传递基本数据类型,会把整个数组当作一个元素放到stream中

五、中间方法

  1. 中间操作是指那些在流上执行的操作,但不会立即触发流中的元素进行处理。相反,它们会返回一个新的流对象,这个新的流对象记录了在中间操作中所定义的处理步骤。这些中间操作是延迟执行的,也就是说,只有当终端操作被调用时,中间操作才会真正被触发执行。
  2. 这种延迟执行的机制有助于优化性能,因为它允许流操作在真正需要结果时才进行处理。例如,你可以在一个流上执行多个中间操作,然后根据需要选择一个终端操作来生成结果。这样可以避免在不必要的情况下进行处理,从而提高效率。
  3. 可以连续调用多个中间方法,调用中间方法之后,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程。
    在这里插入图片描述
  4. 修改了Stream流中的数据,不会影响原来的集合或者数组中的数据

Stream流中常用的6种中间方法

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

    filter的形参是一个函数式接口,返回值为true,表示留下当前数据,当前数据可以继续往下执行,返回值为false,表示舍弃当前元素

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰","张三","隔壁老王","狗蛋");
list.stream().filter(new Predicate<String>() {
    @Override
    public boolean test(String s) {
        //返回true,表示当前数据留下
        // 返回false,表示当前数据舍弃不要
        return s.startsWith("张");       //"张无忌","张三丰","张三",可以继续往下执行Foreach
    }
}).forEach(System.out::println);    
// Lambda表达式简便写法
list.stream().filter(s->false).forEach(System.out::println);
  • limit

    获取集合中前几个元素

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰","张三","隔壁老王","狗蛋");
// 选择list中的前三个元素
list.stream().limit(3).forEach(System.out::println);    //"张无忌","张三丰","张三"
  • skip

    跳过集合中前几个元素

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰","张三","隔壁老王","狗蛋");
// 跳过list中的前三个元素
list.stream().skip(3).forEach(System.out::println);     //"隔壁老王","狗蛋"
  • distinct

    去掉集合中的重复元素

//distinct底层实现源码
if (seenNull.get()) {
    // TODO Implement a more efficient set-union view, rather than copying
    keys = new HashSet<>(keys);
    keys.add(null);
}
return Nodes.node(keys);

我们可以看到list.stream().distinct() 的底层实现使用了 Set 来进行去重操作。具体来说,它使用了一个 HashSet 或类似的数据结构来存储已经出现过的元素,从而确保流中的元素保持唯一。

Set 是一种不允许重复元素的集合数据结构,它通过使用元素的 hashCode()equals() 方法来确定元素的唯一性。当你调用 distinct() 操作时,流会通过这些方法来判断流中的元素是否已经在 Set 中存在,如果存在则会被跳过,从而实现去重。

尽管底层使用了 Set 数据结构,但仍然需要注意,元素的 hashCode()equals() 方法必须正确地实现,以便确保去重操作的准确性。如果这些方法没有正确地被重写,即使使用了 Set,仍然可能出现错误的去重结果。

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张无忌","张无忌","张无忌","张三丰","张三","隔壁老王","狗蛋");
// 去掉list中重复的元素
// 注意:string底层是重写了hashCode()和equals()方法的,如果我们需要去重的是我们自己写的类,则需要重写这些类中的hashCode()和equals()方法
list.stream().distinct().forEach(System.out::println);
  • concat

    合并流

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰");
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1,"张三","隔壁老王","狗蛋");
Stream<String> stream1 = list.stream();
Stream<String> stream2 = list1.stream();
Stream.concat(stream1,stream2).forEach(System.out::println); // 张无忌张三丰张三隔壁老王狗蛋
  • map

    映射,将stream流中的元素映射成新的流

new Function<String, Integer>(){}第一个泛型表示流中原本的数据类型,第二个泛型表示转换后的流的数据类型

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"张无忌","张三丰");
list.stream().map(new Function<String, Integer>() { // <String, Integer>第一个参数是形参类型,第二个参数是返回值类型
    @Override
    public Integer apply(String s) {
        return 6;
    }
}).forEach(System.out::print);	// 66

// Lambda表达式简便写法
list.stream().map(s->6).forEach(System.out::println);

六、终结方法

终端操作是流操作的最后一步,它们会触发实际的数据处理,并产生最终的结果。

以下是一些常见的流终端操作:

名称说明
void forEach(Consumer action)遍历
long count()统计
toArray()收集流中的数据,放到数组中
collect(Collector collector)收集流中的数据,放到集合中
  • forEach()

    遍历流中的元素

//forEach()的参数是Consumer,消费型接口,表示只有输入值,没有输出值
//Consumer的泛型:表示流中数据的类型
//accept方法的形参s:依次表示流里的每一个数据
//方法体:对每个数据的处理操作(打印)
list.stream().forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
});

//Lambda表达式的简便写法
list.stream().forEach(System.out::println);
  • count()

    统计流中数据的个数

long count = list.stream().count();
System.out.println(count);  // 2
  • toArray()

    收集流中的数据,放到数组中。

toArray()有两种用法
在这里插入图片描述

// toArray()的空参用法

//说明:将list中的数据放到数组中,并返回这个数组
Object[] array = list.stream().toArray();
System.out.println(array.toString());
//IntFunction的泛型:具体类型的数组
//apply的形参:流中数据的个数,需要和数组的长度保持一致
//apply的返回值:具体类型的数组
//方法体:创建数组

// toArray的参数的作用:创建一个指定类型的数组
// 底层实现:依次得到流中的每一个数据,并把数据放到数组中
// 返回值:一个装着流里面所有数据的数组
String[] arr = list.stream().toArray(new IntFunction<String[]>() {
    @Override
    public String[] apply(int value) {
        return new String[value];
    }
});

// Lambda表达式简便写法
String[] arr1 = list.stream().toArray((value) -> new String[value]);
  • collect()

收集到LIST集合中

List<String> list1 = list.stream().collect(Collectors.toList());

收集到SET集合中,由于set的特性,收集的集合里的元素是去重的

Set<String> set = list.stream().collect(Collectors.toSet());

收集到MAP集合中

Map<String, String> map = list.stream().collect(Collectors
        /**
         * toMap(参数1,参数2)
         * 参数一:表示键的生成规则
         * 参数二:表示值的生成规则
         *
         * 参数一:  Function泛型一:表示流中每一个数据的类型
         *                  泛型二:表示MAP中键的数据类型
         *          apply()形参:依次表示流中的每一个数据
         *                  方法体:生成键的代码
         *                  返回值:生成的键
         *  参数二和参数一 一样
         */
        .toMap(new Function<String, String>() {
                     @Override
                     public String apply(String s) {
                         return s;
                     }
                 },
        new Function<String, String>() {
            @Override
            public String apply(String s) {
                return s;
            }
        }));

// Lambda表达式简便写法
Map<String, String> map = list.stream().collect(Collectors.toMap(
                s -> s,
                s -> s
        ));

七、注意点

  • 串行流的处理方式是,依次对每个元素执行中间操作,并在所有元素都完成中间操作后,再执行终端操作。这意味着中间操作会逐个应用于每个元素,然后等所有元素都完成中间操作后,终端操作才会被触发。这种方式确保了处理顺序的一致性。
// 在这个例子中,map操作会被逐个应用于每个元素,然后reduce操作会在所有元素都完成map操作后被触发。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                .map(n -> n * 2)      // 中间操作:将每个元素加倍
                .reduce(0, Integer::sum); // 终端操作:计算总和
System.out.println(sum); // 输出结果:30

在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值