我应该在Java 8 Streams中使用共享的可变变量更新吗?

问题描述 投票:7回答:3

只需在列表下方迭代并通过java 8流添加到另一个共享的可变列表中。

List<String> list1 = Arrays.asList("A1","A2","A3","A4","A5","A6","A7","A8","B1","B2","B3");
List<String> list2 = new ArrayList<>();

Consumer<String> c = t -> list2.add(t.startsWith("A") ? t : "EMPTY");

list1.stream().forEach(c);
list1.parallelStream().forEach(c);
list1.forEach(c);

上面三次迭代和我们需要使用哪一次有什么区别。有什么考虑吗?

java java-8 java-stream
3个回答
3
投票

从功能上讲,对于简单的情况它们几乎是相同的,但一般来说,存在一些隐藏的差异:

  1. 让我们首先引用forEach的Javadoc的可迭代用例来说明:

对Iterable的每个元素执行给定的操作,直到处理完所有元素或者操作抛出异常为止。

我们还可以迭代一个集合并对每个元素执行一个给定的操作 - 只需传递一个实现Consumer接口的类

void forEach(Consumer<? super T> action)

https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html#forEach-java.util.function.Consumer-


  1. Stream.forEach的顺序是随机的,而Iterable.forEach总是以Iterable的迭代顺序执行。

  1. 如果Iterable.forEach正在迭代同步集合,Iterable.forEach会将集合的锁定一次并将其保存在对action方法的所有调用中。 Stream.forEach调用使用集合的spliterator,它不会锁定

  1. Stream.forEach中指定的动作必须是非干扰的,而Iterable.forEach允许在基础ArrayList中设置值而没有问题。

  1. 在Java中,Collection类返回的迭代器,例如ArrayList,HashSet,Vector等都快速失败。这意味着如果你尝试在迭代它时从底层数据结构中添加()或remove(),你会得到一个ConcurrentModificationException.

https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html#fail-fast


更多信息:


4
投票

无论你使用并行还是顺序Stream,当你的目标是生成forEach时,你不应该使用List。使用mapcollect

List<String> list2 = 
    list2.stream()
         .map(item -> item.startsWith("A") ? item : "EMPTY")
         .collect(Collectors.toList());

0
投票

我个人认为,在使用流时,您应该以一种方式编写代码,如果切换到并行流,它不会破坏您的代码(产生错误的结果)。想象一下,如果在你的代码中你正在读取和写入相同的共享内存(list2),并将流程分配到多个线程(使用并行流)。然后你就被诅咒了。因此,您有几种选择。

使您的共享内存(list2)线程安全。例如,通过使用AtomicReferences

List<String> list2 = new ArrayList<>();
AtomicReference<List<String>> listSafe = new AtomicReference<>();
listSafe.getAndUpdate(strings -> {strings.add("newvalue"); return strings;}); 

或者您可以使用纯粹的功能方法(没有副作用的代码),如@Eran解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.