当前位置: 首页 > 知识库问答 >
问题:

如何将Java8流的元素添加到现有列表中

韩寂离
2023-03-14

Collector的Javadoc展示了如何将流的元素收集到一个新列表中。是否有一个单行程序将结果添加到现有的ArrayList中?

共有3个答案

景英杰
2023-03-14

简短的回答是否定的(或者应该是否定的)。编辑:是的,这是有可能的(参见下面的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简介/书籍的话)

要弄清楚为什么修改现有列表通常是一个坏主意,并且导致代码可维护性较差--除非您修改的是局部变量,并且您的算法很短和/或很琐碎,这超出了代码可维护性问题的范围--找到一本很好的函数式编程入门(有数百个),然后开始阅读。一个“预览”的解释是这样的:不修改数据(在程序的大部分部分)在数学上更合理,更容易推理,并且导致更高水平和更少技术性(以及更友好的人类,一旦你的大脑从旧式的命令式思维转变)的程序逻辑定义。

夏飞跃
2023-03-14

据我所知,到目前为止,所有其他的答案都使用了一个收集器来向现有的流中添加元素。然而,有一个更短的解决方案,它同时适用于顺序流和并行流。您可以简单地将方法forEachOrdered与方法引用结合使用。

List<String> source = ...;
List<Integer> target = ...;

source.stream()
      .map(String::length)
      .forEachOrdered(target::add);

唯一的限制是,源和目标是不同的列表,因为只要流被处理,您就不允许对其源进行更改。

请注意,此解决方案同时适用于顺序流和并行流。但是,它并没有从并发性中获益。传递给forEachOrdered的方法引用将始终顺序执行

穆文斌
2023-03-14

注意:Nosid的回答显示了如何使用foreachOrdered()添加到现有集合中。这是对现有集合进行突变的一种有用而有效的技术。我的回答说明了为什么不应该使用collector来更改现有的集合。

简短的回答是否定的,至少不是,一般来说,您不应该使用collector来修改现有的集合。

原因是收集器的设计是为了支持并行性,即使是在不是线程安全的集合上也是如此。他们这样做的方式是让每个线程在自己的中间结果集合上独立操作。每个线程获取自己的集合的方式是调用collector.supplier(),这是每次返回新集合所必需的。

然后再以线程限制的方式合并这些中间结果集合,直到有一个结果集合。这是collection()操作的最终结果。

Balder和assylias的一些回答建议使用collectors.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]

这是我上面描述的线程限制的累加/合并操作的结果。使用并行流,每个线程调用供应商以获得自己的集合,用于中间积累。如果传递一个返回相同集合的供应商,则每个线程将其结果附加到该集合。由于线程之间没有顺序,结果将以任意顺序追加。

然后,当合并这些中间集合时,这基本上将列表与自身合并。使用list.addAll()合并列表,它表示如果在操作过程中修改了源集合,则结果是未定义的。在本例中,arraylist.addAll()执行数组复制操作,因此它最终会复制自己,我想这是我们所期望的。(请注意,其他列表实现可能具有完全不同的行为。)不管怎样,这解释了目标中奇怪的结果和重复的元素。

您可能会说,“我将确保按顺序运行我的流”,然后继续编写代码,如下所示

stream.collect(Collectors.toCollection(() -> existingList))

不管怎样。我建议不要这么做。如果您控制了流,当然,您可以保证它不会并行运行。我预计会出现一种编程风格,即流而不是集合。如果有人给你一个流,而你使用了这段代码,如果流碰巧是并行的,它就会失败。更糟的是,有人可能会给你一个顺序流,而这个代码会在一段时间内正常工作,通过所有测试等等。然后,在任意一段时间后,系统中其他地方的代码可能会改变使用并行流,这将导致您的代码中断。

好的,那么只需确保记住在使用此代码之前对任何流调用sequential():

stream.sequential().collect(Collectors.toCollection(() -> existingList))

当然,你会记得每次都这样做,对吧?:-)假设你是这样做的。那么,性能团队就会想知道为什么他们精心设计的并行实现都没有提供任何加速。他们会再一次追踪到你的代码,它迫使整个流按顺序运行。

别这么做。

 类似资料:
  • 问题内容: Collector的Javadoc显示了如何将流的元素收集到新的List中。有没有一种将结果添加到现有ArrayList中的方法? 问题答案: 注意: osid的答案 显示了如何使用来添加到现有集合。这是对现有集合进行变异的有用且有效的技术。我的答案解决了为什么您不应该使用A 来突变现有集合的原因。 简短的答案是 no ,至少在一般情况下不是这样,您不应该使用a 来修改现有集合。 原因

  • 我必须编写一些代码,将Java8流的内容多次添加到列表中,我很难找到最好的方法。根据我在SO上读到的内容(主要是这个问题:如何将Java8流的元素添加到现有列表中)和其他地方,我将其缩小到以下选项: PS:在我的示例代码中,我使用作为需要在流上执行的任何操作的占位符

  • 我是新来的laravel框架。为了使博客网址对搜索引擎优化友好,我需要为laravel网站的现有博客表添加一个额外的列。我们可以直接在数据库中的表中添加列吗?我们可以在没有命令或迁移的情况下添加列吗?你能建议一个简单的方法来添加这个列吗?

  • 问题内容: 我的问题是我想在遍历新元素的同时扩展一个包含新元素的列表,并且希望迭代器继续刚才添加的元素。 根据我的理解,会在列表中的当前元素之前而不是之后添加一个元素。是否可以通过其他方式实现这一目标? 问题答案: 除了以外,您无法在修改集合时使用进行迭代。 但是,如果使用方法,该方法返回,并对其进行迭代,则您还有更多要修改的选项。从javadoc中获取: 新元素插入到隐式光标之前:…后续调用将返

  • 问题内容: 如何添加到MySQL表的现有列? 问题答案: 我认为您想按照命令中的说明进行操作。可能是这样的: 在上面运行之前,请确保该列具有主索引。

  • 问题内容: 我想避免。我该怎么办? 问题答案: 您可以在迭代本身期间使用支持remove / add方法的。