22.Java Stream流的总结

本文介绍了Java8中Stream流的概念、创建方式、中间和终端操作,强调了其函数式编程特性、延迟执行以及非破坏性操作,有助于简化集合数据处理和多线程利用。

Java 8 引入的 Stream 流是一种用于处理集合数据的高级抽象。它提供了一种函数式编程的方式来操作数据,使得代码更简洁、可读性更高,并且可以并行处理数据。

下面是 Java Stream 流的总结:

  1. 创建流:

    • 从集合创建流:stream() 或 parallelStream() 方法可以从集合中创建串行或并行流。
    • 从数组创建流:Arrays.stream(array) 可以从数组中创建流。
    • 使用 Stream.of() 创建流:Stream.of(value1, value2, ...) 可以直接创建流。
  2. 中间操作:

    • filter:根据指定条件过滤元素。
    • map:对流中的每个元素应用一个函数。
    • flatMap:将多个流合并成一个流。
    • distinct:去重,去除流中重复的元素。
    • sorted:对流中的元素排序。
    • limit:限制流中元素的数量。
    • skip:跳过流中的前几个元素。
    • ...
  3. 终端操作:

    • forEach:对流中的每个元素执行指定操作。
    • toArray:将流转换为数组。
    • reduce:对流中的元素进行归约操作。
    • collect:将流中的元素收集到集合中。
    • min、max:找出流中的最小值或最大值。
    • count:返回流中的元素数量。
    • anyMatch、allMatch、noneMatch:判断流中是否存在满足指定条件的元素。
    • findFirst、findAny:返回流中的第一个元素或任意元素。
    • ...
  4. Stream流的每一次中间操作都会返回一个新的Stream对象,而不会改变原来的Stream对象。这意味着每次执行中间操作时,都会创建一个新的Stream对象,而不是在原来的Stream对象上进行修改。
  5. 线程安全问题。
    • 串行流是线程安全的,默认是单线程。
    • 并行流是多线程执行的,非线程安全。
  6. 并行流:

    • 使用 parallelStream() 方法可以将流转换为并行流,充分利用多核处理器的优势。
  7. 流的延迟执行:

    • Stream 操作一般是延迟执行的,只有在终端操作调用时才会真正执行。
    • 在对流进行多次操作时,在没有调用终端操作之前,所有中间操作不会立即执行。(如果你不调用终端操作那中间操作就不会被执行,因为你不使用这些操作后的数据,所以就连中间操作都不执行了)
  8. 流的使用注意事项:

    • 流只能被消费一次,一旦对流进行终端操作后,就无法再对同一流进行操作。
    • 流操作可能会改变原始集合的状态,因此需要注意不要对同一集合进行多个流操作。

Stream 是 Java 8 引入的一个强大的工具,它提供了丰富的操作方法,可以大大简化集合数据的处理。通过灵活地使用中间操作和终端操作,我们可以实现各种复杂的数据处理需求。

在 Stream 流中对集合进行过滤操作,并不会改变原始集合的内容。Stream 操作是基于流的元素创建一个新的流,而不会对原始数据源进行修改。

例如,考虑以下示例代码:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> filteredNumbers = numbers.stream()
                                       .filter(n -> n % 2 == 0)
                                       .collect(Collectors.toList());

System.out.println(numbers);          // [1, 2, 3, 4, 5]
System.out.println(filteredNumbers);  // [2, 4]

在上面的代码中,我们通过 filter 操作筛选出了偶数,但是原始的 numbers 集合并没有改变。相反,我们使用 collect 方法将筛选后的结果收集到了一个新的集合 filteredNumbers 中。

这种非破坏性的操作是 Stream API 的特征之一。它允许我们在不修改原始数据源的情况下对数据进行各种转换和处理操作。

 

<think>我们有一个Java的NullPointerException堆栈跟踪,需要分析原因。 堆栈信息显示: java.lang.NullPointerException: null at java.base/java.util.Objects.requireNonNull(Objects.java:233) at java.base/java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:180) ... 后续调用 关键点: 1. Objects.requireNonNull方法抛出了NullPointerException,说明传入了一个null参数,而这里不允许为null。 2. 该异常发生在Stream的Collectors.uniqKeysMapAccumulator方法中(lambda表达式内)。 根据Collectors.lambda$uniqKeysMapAccumulator$1的代码(对应Collectors.java第180行): 该方法用于收集器,当使用toMap或groupingBy等收集器时,可能会调用此函数。 具体来说,uniqKeysMapAccumulator用于处理键值对,确保键的唯一性。 在Collectors.java中,requireNonNull被调用的地方(第180行)通常是检查键是否为空。因为toMap收集器不允许键为null。 因此,这个异常的原因是在进行Stream的collect操作(可能是toMap)时,其中一个元素的键为null。 解决方案: 1. 在收集为Map之前,过滤掉键为null的元素。 2. 或者,如果允许null键,可以使用`Collectors.toMap`的重载方法并提供一个合并函数,但注意:标准的toMap不允许null键。所以更常见的做法是过滤掉null键,或者使用其他方式处理。 另外,也可以考虑使用`Collectors.groupingBy`,但同样需要注意null键的问题。 根据堆栈,我们看到异常发生在ArrayList的操作中,因此可以推断出是在一个ArrayList的操作中使用了toMap(或类似收集器)且遇到了null键。 总结:NullPointerException是因为在Stream的toMap操作中,某个元素的键为null。 以下是一个示例代码,演示了如何避免这种情况: 问题代码(可能): Map<String, Object> map = list.stream() .collect(Collectors.toMap(MyObject::getKey, Function.identity())); 如果getKey()返回null,则抛出NullPointerException。 修复方式(过滤null键): Map<String, Object> map = list.stream() .filter(obj -> obj.getKey() != null) .collect(Collectors.toMap(MyObject::getKey, Function.identity())); 或者,如果允许null键,可以考虑使用HashMap等,但标准toMap不支持。另一种选择是使用Collectors.toMap的重载方法,允许指定一个Map的具体实现(但依然不能解决null键问题,因为内部仍然会调用Objects.requireNonNull)。实际上,Java标准库的toMap收集器设计上就不允许null键。 因此,过滤掉null键是最常见的做法。 §§相关问题§§ 1.Java Stream的toMap操作中,除了键为null会导致空指针异常,还有哪些情况会导致类似异常? 2. 如果我想在Map中保留null键,应该使用哪种收集器或方法? 3. 除了使用filter过滤,还有哪些方法可以避免在Stream操作中出现NullPointerException? 4. 在Collectors.toMap中,如果值函数返回null,是否也会抛出异常? 5. 当使用Collectors.groupingBy时,是否允许键为null?如果允许,那么当键为null时,分组结果会怎样? </think> ### 分析报错原因: 该 `NullPointerException` 发生在 Java Stream 操作中,具体原因是 **使用 `Collectors.toMap()` 时出现了 `null` 键**。核心线索: 1. 异常触发点: `Collectors.lambda$uniqKeysMapAccumulator$1`(对应 `Collectors.java:180`)调用了 `Objects.requireNonNull()`,强制要求键不能为 `null`。 2. 根本原因: 在 `stream.collect(Collectors.toMap(...))` 操作中,某个元素的 **键(Key)为 `null`**,违反了 `toMap()` 的键非空约束。 ### 解决方案: #### 方法 1:过滤空键(推荐) ```java Map<K, V> result = list.stream() .filter(obj -> obj.getKey() != null) // 过滤掉键为null的元素 .collect(Collectors.toMap(MyClass::getKey, MyClass::getValue)); ``` #### 方法 2:自定义合并函数(处理键冲突时) ```java Map<K, V> result = list.stream() .collect(Collectors.toMap( MyClass::getKey, MyClass::getValue, (oldVal, newVal) -> newVal, // 键冲突时保留新值 HashMap::new // 指定HashMap允许null键(需显式声明) )); ``` > ⚠️ 注意:`toMap()` 本身不支持 `null` 键,即使使用 `HashMap` 仍会抛 NPE。**必须配合键非空过滤**。 ### 关键总结: | 环节 | 约束条件 | 解决方案 | |-------------|------------------------|----------------------------| | `toMap()` | 键 **禁止** 为 `null` | 用 `filter()` 提前过滤空键 | | 值(Value) | **允许** 为 `null` | 无需特殊处理 |
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值