【问题标题】:Java 8 modify stream elementsJava 8 修改流元素
【发布时间】:2016-07-11 08:38:16
【问题描述】:

我想用 Java 8 编写纯函数,它将一个集合作为参数,对该集合的每个对象应用一些更改,并在更新后返回一个新集合。我想遵循 FP 原则,所以我不想更新/修改作为参数传递的集合。

有没有什么方法可以用 Stream API 做到这一点,而无需先创建原始集合的副本(然后使用 forEach 或“正常”for 循环)?

下面的示例对象,假设我想将文本附加到对象属性之一:

public class SampleDTO {
    private String text;
}

所以我想做类似于下面的事情,但不修改集合。假设“列表”是List<SampleDTO>

list.forEach(s -> {
    s.setText(s.getText()+"xxx");
});

【问题讨论】:

  • 我想说这确实有点违背函数式编程的原则。这个想法不是改变状态,而是创建新状态作为函数的产物。这就是为什么您应该使用 Java 流 API 提供的 map 函数来创建新列表,而不是改变原始列表。

标签: java java-8 java-stream


【解决方案1】:

您必须有一些方法/构造函数来生成现有SampleDTO 实例的副本,例如复制构造函数。

然后你可以将map每个原来的SampleDTO实例转化为一个新的SampleDTO实例,并将collect它们转化为一个新的List

List<SampleDTO> output = 
    list.stream()
        .map(s-> {
                     SampleDTO n = new SampleDTO(s); // create new instance
                     n.setText(n.getText()+"xxx"); // mutate its state
                     return n; // return mutated instance
                 })
       .collect(Collectors.toList());

【讨论】:

【解决方案2】:

为了让这种方式更优雅,我建议在课堂上创建一个Method

 public class SampleDTO {
 private String text;
public String getText() {
    return text;
}

public void setText(String text) {
    this.text = text;
}

public SampleDTO(String text) {
    this.text = text;
}

public SampleDTO getSampleDTO() {
    this.setText(getText()+"xxx");
    return this;
}
    }

并像这样添加它:

List<SampleDTO> output =list.stream().map(SampleDTO::getSampleDTO).collect(Collectors.toList();

【讨论】:

  • @FedericoPeraltaSchaffner 感谢您的评论和投票。我已经解决了这个问题。现在它将编译
  • 我投了反对票,因为你的答案有 1 票,我想推广其他正确的答案,如果这太苛刻了,抱歉。既然你已经纠正了它,我不仅恢复了我的反对票,而且还赞成了。
  • 但是,现在原始对象已被修改,OP 既不想改变原始列表也不想改变它的元素。您应该考虑创建原始对象的副本并修改其文本属性。
  • getSampleDTO 方法修改其文本?改变方法的错误命名和非常讨厌的副作用
【解决方案3】:

流与字符串一样是不可变的,因此您无法绕过需要创建新的流/列表/数组

话虽如此,您可以使用 .Collect() 来返回一个新的集合发布更改所以

List<Integer> result = inList.stream().map().Collect()

【讨论】:

    【解决方案4】:

    我认为将原始列表流式传输到新的修改列表或其他任何需要的列表中会更好,尤其是在进行多线程工作时。

    可以创建新的列表或地图或您想要的任何其他结构作为流式处理过程的一部分。

    流式传输过程完成后,只需将原件换成新件即可。

    所有这些都应该发生在一个同步块中。

    通过这种方式,您可以获得最大的性能和并行性,用于 reduce 或您正在执行的任何操作,并以原子交换结束。

    【讨论】:

      【解决方案5】:

      我认为更简洁、更易读的解决方案是:

      List<SampleDTO> unmodifiable = Arrays.asList(new SampleDTO("1"), new SampleDTO("2"));
      
      List<SampleDTO> modified = unmodifiable.stream()
                  .map(s -> new SampleDTO(s.getText())) // '.map(SampleDTO::new)' with copy constructor
                  .peek(s -> s.setText(s.getText() + "xxx"))
                  .collect(Collectors.toList());
      

      或者,如果您有 SampleDTO 的复制构造函数,您可以将 map 函数替换为 .map(SampleDTO::new)

      peek 函数将对 Stream 的元素提供操作,而不改变 Stream 的结果类型。

      【讨论】:

      • 注意:您不应该在生产代码中使用 peek()。仅用于调试。
      • peek() 并不完全打算用于更改流元素(尽管它肯定可以,并且根据文档,“主要用于支持调试,您希望在其中查看元素流过管道中的某个点"
      • @ghost28147 这意味着 'peek' 是惰性方法,不能对流中的所有元素执行,例如当您使用方法 'findFirst' 时(您可以找到流中的第一个元素和 ' peek' 不会对其他元素执行),但是如果你在 'collect' 方法之前使用它,这意味着你的所有元素都将用于插入到集合中,你可以使用 'peek'。一般来说,如果您知道流内部是如何工作的,您可以使用“窥视”方法。 Docs 说“主要是为了支持调试”,而不是“不能在生产中使用”。
      • @alex87 见我上面的评论。
      猜你喜欢
      • 1970-01-01
      • 2018-03-06
      • 1970-01-01
      • 2015-02-24
      • 1970-01-01
      • 2023-03-19
      • 1970-01-01
      • 2015-08-15
      • 1970-01-01
      相关资源
      最近更新 更多