Collector的Javadoc演示了如何将流中的元素收集到一个新的List中。是否有一行程序将结果添加到现有的数组列表中?
当前回答
注意:nosid的回答展示了如何使用forEachOrdered()添加到现有的集合。对于改变现有集合,这是一种有用且有效的技术。我的回答说明了为什么不应该使用Collector来改变现有的集合。
简短的回答是否定的,至少不是一般情况下,您不应该使用Collector来修改现有的集合。
原因是,收集器被设计为支持并行性,即使是在不线程安全的集合上。他们这样做的方式是让每个线程在自己的中间结果集合上独立操作。每个线程获取自己的集合的方法是调用Collector.supplier(),它每次都需要返回一个新的集合。
然后,这些中间结果集合再次以线程受限的方式合并,直到形成单个结果集合。这是collect()操作的最终结果。
Balder和assylias的几个回答建议使用collections . tocollection(),然后传递一个返回现有列表而不是新列表的供应商。这违反了对供应商的要求,即它每次都返回一个新的空集合。
这将适用于简单的情况,如他们的答案中的例子所示。但是,它将失败,特别是如果流是并行运行的。(库的未来版本可能会以某种不可预见的方式更改,导致它失败,即使是在顺序的情况下。)
让我们举一个简单的例子:
List<String> destList = new ArrayList<>(Arrays.asList("foo"));
List<String> newList = Arrays.asList("0", "1", "2", "3", "4", "5");
newList.parallelStream()
.collect(Collectors.toCollection(() -> destList));
System.out.println(destList);
当我运行这个程序时,我经常得到一个ArrayIndexOutOfBoundsException。这是因为多个线程在ArrayList上操作,这是一个线程不安全的数据结构。好的,让我们同步一下:
List<String> destList =
Collections.synchronizedList(new ArrayList<>(Arrays.asList("foo")));
这将不再出现异常而失败。但事与愿违:
[foo, 0, 1, 2, 3]
它会给出这样奇怪的结果:
[foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0, foo, 2, 3, foo, 2, 3, 1, 0]
这是我上面描述的线程受限的累积/合并操作的结果。在并行流中,每个线程调用提供者来获取自己的集合以进行中间积累。如果传递一个返回相同集合的提供者,则每个线程将其结果追加到该集合。由于线程之间没有顺序,结果将以任意顺序追加。
Then, when these intermediate collections are merged, this basically merges the list with itself. Lists are merged using List.addAll(), which says that the results are undefined if the source collection is modified during the operation. In this case, ArrayList.addAll() does an array-copy operation, so it ends up duplicating itself, which is sort-of what one would expect, I guess. (Note that other List implementations might have completely different behavior.) Anyway, this explains the weird results and duplicated elements in the destination.
您可能会说,“我将确保按顺序运行我的流”,然后继续编写这样的代码
stream.collect(Collectors.toCollection(() -> existingList))
anyway. I'd recommend against doing this. If you control the stream, sure, you can guarantee that it won't run in parallel. I expect that a style of programming will emerge where streams get handed around instead of collections. If somebody hands you a stream and you use this code, it'll fail if the stream happens to be parallel. Worse, somebody might hand you a sequential stream and this code will work fine for a while, pass all tests, etc. Then, some arbitrary amount of time later, code elsewhere in the system might change to use parallel streams which will cause your code to break.
好了,那么在你使用这段代码之前,一定要记得在任何流上调用sequential():
stream.sequential().collect(Collectors.toCollection(() -> existingList))
当然,你每次都会记得这么做,对吧?:-)假设你有。然后,性能团队会疑惑为什么他们所有精心设计的并行实现都不能提供任何加速。他们会再一次追踪到你的代码,迫使整个流按顺序运行。
不要这样做。
其他回答
Erik Kaplun已经给出了很好的理由,为什么您不希望将流的元素收集到现有的List中。
无论如何,如果您确实需要这个功能,您可以使用下面的一行程序。
但正如在其他答案中指出的那样,你永远不应该这样做,特别是如果流可能是并行流——使用风险自负……
list.stream().collect(Collectors.toCollection(() -> myExistingList));
简短的回答是没有(或者应该是没有)。编辑:是的,这是可能的(见下面assylias的答案),但继续读下去。编辑2:但是看看斯图尔特·马克斯的回答,你仍然不应该这样做的另一个原因!
更长的答案是:
Java 8中这些构造的目的是将函数式编程的一些概念引入该语言;在函数式编程中,数据结构通常不会被修改,相反,通过映射、过滤、折叠/缩减和许多其他转换,可以从旧的数据结构中创建新的数据结构。
如果你必须修改旧列表,只需将映射项收集到一个新列表中:
final List<Integer> newList = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
然后执行list.addAll(newList) -如果你真的必须这样做。
(或者构造一个连接新旧列表的新列表,并将其赋值给list变量——这比addAll更符合FP的精神)
至于API:即使API允许这样做(再次,参见assylias的回答),你应该尽量避免这样做,至少在一般情况下。最好不要对抗范式(FP),而是尝试学习它,而不是对抗它(尽管Java通常不是FP语言),只有在绝对需要时才采用“更肮脏”的策略。
很长的答案:(也就是说,如果你把寻找和阅读FP介绍/书籍的努力包括在内的话)
To find out why modifying existing lists is in general a bad idea and leads to less maintainable code—unless you're modifying a local variable and your algorithm is short and/or trivial, which is out of the scope of the question of code maintainability—find a good introduction to Functional Programming (there are hundreds) and start reading. A "preview" explanation would be something like: it's more mathematically sound and easier to reason about to not modify data (in most parts of your program) and leads to higher level and less technical (as well as more human friendly, once your brain transitions away from the old-style imperative thinking) definitions of program logic.
targetList = sourceList.stream().flatmap(List::stream).collect(Collectors.toList());
据我所知,到目前为止,所有其他答案都使用收集器向现有流添加元素。然而,有一个更短的解决方案,它既适用于顺序流,也适用于并行流。您可以简单地将forEachOrdered方法与方法引用结合使用。
List<String> source = ...;
List<Integer> target = ...;
source.stream()
.map(String::length)
.forEachOrdered(target::add);
唯一的限制是,源和目标是不同的列表,因为只要流正在处理,您就不允许对流的源进行更改。
注意,此解决方案同时适用于顺序流和并行流。但是,它并不能从并发性中获益。传递给forEachOrdered的方法引用将始终按顺序执行。
假设我们有一个现有的列表,并将使用java 8来进行这个活动 `
import java.util.*;
import java.util.stream.Collectors;
public class AddingArray {
public void addArrayInList(){
List<Integer> list = Arrays.asList(3, 7, 9);
// And we have an array of Integer type
int nums[] = {4, 6, 7};
//Now lets add them all in list
// converting array to a list through stream and adding that list to previous list
list.addAll(Arrays.stream(nums).map(num ->
num).boxed().collect(Collectors.toList()));
}
}
`