假设我有这份水果 list :-
List<String> f = Arrays.asList("Banana", "Apple", "Grape", "Orange", "Kiwi");
我需要为每个水果添加一个序列号并打印出来。水果或序列号的顺序无关紧要。所以这是一个有效的输出:-
4. Kiwi
3. Orange
1. Grape
2. Apple
5. Banana
解决方案#1
AtomicInteger number = new AtomicInteger(0);
String result = f.parallelStream()
.map(i -> String.format("%d. %s", number.incrementAndGet(), i))
.collect(Collectors.joining("\n"));
解决方案 #2
String result = IntStream.rangeClosed(1, f.size())
.parallel()
.mapToObj(i -> String.format("%d. %s", i, f.get(i - 1)))
.collect(Collectors.joining("\n"));
问题
为什么解决方案 1 是不好的做法?我在很多地方都看到基于 AtomicInteger
的解决方案很糟糕(比如 this answer ),特别是在并行流处理中(这就是我在上面使用并行流的原因,试图遇到问题) .
我看了这些问题/答案:-
In which cases Stream operations should be stateful?
Is use of AtomicInteger for indexing in Stream a legit way?
Java 8: Preferred way to count iterations of a lambda?
他们只是提到(除非我遗漏了什么)“可能会出现意想不到的结果”。像什么?在这个例子中会发生吗?如果没有,您能否提供一个可能发生的示例?
至于“不保证应用映射器函数的顺序”,好吧,这是并行处理的本质,所以我接受它,而且顺序不在此特定示例中很重要。
AtomicInteger
是线程安全的,因此在并行处理中应该不是问题。
有人可以举例说明在使用这种基于状态的解决方案时会出现哪些问题吗?
最佳答案
好吧,看看 Stuart Marks 的回答是什么 here - 他正在使用有状态谓词。
这是一些潜在的问题,但如果您不关心它们或真正理解它们 - 您应该没问题。
首先是顺序,在并行处理的当前实现下展示,但如果您不关心顺序,就像在您的示例中一样,就可以了。
第二个是潜在速度 AtomicInteger
递增一个简单的 int 会慢很多倍,如前所述,如果您关心这一点。
第三个更微妙。有时根本无法保证 map
会被执行,例如 java-9:
someStream.map(i -> /* do something with i and numbers */)
.count();
这里的重点是因为你是在计数,所以不需要做映射,所以跳过了。一般来说,命中某些中间操作的元素不能保证到达终端。想象一个 map.filter.map
的情况,第一个 map 可能比第二个 map “看到”更多的元素,因为一些元素可能被过滤了。所以不建议依赖这个,除非你能准确地推断出发生了什么。
在你的例子中,IMO,你做你做的事是非常安全的;但是如果你稍微改变了你的代码,这需要额外的推理来证明它的正确性。我会选择解决方案 2,只是因为它对我来说更容易理解,而且它没有上面列出的潜在问题。
关于java - 为什么不推荐基于 AtomicInteger 的 Stream 解决方案?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53329809/