Java 8 Stream流操作疑问

心软的碗
  • 3
新手上路,请多包涵

偶然间看到小伙伴有段代码基于Stream流通过map()修改属性,使用collect(Collectors.toList())将流转为集合, 但是为什么数据源的值会变掉 ?

我是这样理解的,不知道对不对,欢迎指正,基于数据源转为stream()流,然后基于流可以进行零个多个比如filter()map()中间操作,通过collect()sum()等终端操作进行流的收集并关闭流, 整个过程不会对数据源本身进行修改,只是基于流进行处理并转化为一个新的结果, 但是结果与预期不一致, 查看文档初步猜想是干扰导致的, 但是源码看不太懂,没法断定是什么原因导致的, 还希望大佬能够指点下,测试代码如下:


public static void main(String[] args) {
    // 准备数据
    List<HashMap<String, String>> initData = new ArrayList<HashMap<String, String>>() {{
        add(new HashMap<String, String>() {{put("name", "张三");}});
        add(new HashMap<String, String>() {{put("name", "李四");}});
        add(new HashMap<String, String>() {{put("name", "王五");}});
    }};        
    // 处理 initData
    initData.stream().map(data -> data.put("age", "18"));
    System.out.println(initData);
    // 打印结果: [{name=张三}, {name=李四}, {name=王五}]

    initData.stream().map(data ->data.put("age","18")).collect(Collectors.toList());
    System.out.println(initData);
    // 打印结果: [{name=张三, age=18}, {name=李四, age=18}, {name=王五, age=18}]
}
回复
阅读 705
2 个回答
未觉雨声
  • 1.3k
✓ 已被采纳

这是因为对象同引用的原因呀,流里得到的每个元素是一个对象的地址,你在两个 List 在同一个位置都是指向同一个 HashMap 对象,因此你的 put 操作会直接修改到这个对象。

如果不想产生相同的引用,你得对每个子元素都重新创建一个 HashMap

public static void main(String[] args) {
  // ...

  initData.stream()
    .map(HashMap::new)
    .map(data -> data.put("age", "18"))
    .collect(Collectors.toList());
}

stream 的 map (intermediate operation) 不是立即执行其中的操作,而是等到返回的 stream 被真正遍历的时候才会被执行。如果返回的 stream 没有被遍历,那就什么也不会发生。

而 collect 会遍历整个 stream 。于是加上 collect ,map 里的操作才会真正被执行。


map

Returns a stream consisting of the results of applying the given function to the elements of this stream. This is an intermediate operation.

Intermediate Operation

Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines. A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.

Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.

Terminal operations, such as Stream.forEach or IntStream.sum, may traverse the stream to produce a result or a side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used; if you need to traverse the same data source again, you must return to the data source to get a new stream. In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning. Only the terminal operations iterator() and spliterator() are not; these are provided as an "escape hatch" to enable arbitrary client-controlled pipeline traversals in the event that the existing operations are not sufficient to the task.

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏