【问题标题】:Modifying Objects within stream in Java8 while iterating迭代时在Java8中修改流中的对象
【发布时间】:2015-08-26 23:38:15
【问题描述】:

在 Java8 流中,我可以修改/更新其中的对象吗? 例如。 List<User> users:

users.stream().forEach(u -> u.setProperty("value"))

【问题讨论】:

    标签: java java-8 java-stream


    【解决方案1】:

    是的,您可以修改流中对象的状态,但通常您应该避免修改流的的状态。从流包文档的non-interference 部分我们可以读到:

    对于大多数数据源,防止干扰意味着确保在流管道执行期间根本不修改数据源。值得注意的例外是其源是并发集合的流,这些集合专门设计用于处理并发修改。并发流源是那些Spliterator 报告CONCURRENT 特征的源。

    这样就好了

      List<User> users = getUsers();
      users.stream().forEach(u -> u.setProperty(value));
    //                       ^    ^^^^^^^^^^^^^
    //                        \__/
    

    但这在大多数情况下不是

      users.stream().forEach(u -> users.remove(u));
    //^^^^^                       ^^^^^^^^^^^^
    //     \_____________________/
    

    并可能抛出ConcurrentModificationException 甚至其他意外异常,例如 NPE:

    List<Integer> list = IntStream.range(0, 10).boxed().collect(Collectors.toList());
    
    list.stream()
        .filter(i -> i > 5)
        .forEach(i -> list.remove(i));  //throws NullPointerException
    

    【讨论】:

    • 修改用户有什么解决办法?
    • @Augustas 这完全取决于您要如何修改此列表。但通常您应该避免使用Iterator,它会在迭代时阻止修改列表(删除/添加新元素),并且流和 for-each 循环都在使用它。你可以尝试使用像for(int i=0; ..; ..) 这样的简单循环,它没有这个问题(但当其他线程修改你的列表时不会阻止你)。您也可以使用list.removeAll(Collection)list.removeIf(Predicate) 等方法。 java.util.Collections 类也有一些有用的方法,例如 addAll(CollectionOfNewElements,list)
    • @Pshemo,一种解决方案是创建一个新的 Collection 实例,如 ArrayList ,其中包含主列表中的项目;遍历新列表,并对主列表执行操作: new ArrayList(users).stream.forEach(u -> users.remove(u));
    • @Blauhirn 解决什么?我们不允许在使用流时修改流的来源,但可以修改其元素的状态。我们使用 map、foreach 还是其他流方法都没关系。
    • 我建议不要修改集合,而是使用filter()
    【解决方案2】:

    要对流的源进行结构修改,正如 Pshemo 在他的回答中提到的那样,一种解决方案是使用主列表中的项目创建 Collection 的新实例,例如 ArrayList;遍历新列表,并在主列表上执行操作。

    new ArrayList<>(users).stream().forEach(u -> users.remove(u));
    

    【讨论】:

      【解决方案3】:

      要摆脱 ConcurrentModificationException 使用 CopyOnWriteArrayList

      【讨论】:

      • 这实际上是正确的,但是:这取决于此处使用的特定类。但是当你只知道你有一个List&lt;Something&gt; - 你不知道那是否是一个CopyOnWriteArrayList。所以这更像是一个平庸的评论,但不是一个真正的答案。
      • 如果他把它作为评论发布,很可能有人会告诉他他不应该以 cmets 的形式发布答案。
      【解决方案4】:

      恕我直言,功能方式是:

      import static java.util.stream.Collectors.toList;
      import java.util.Arrays;
      import java.util.List;
      import java.util.function.Predicate;
      
      public class PredicateTestRun {
      
          public static void main(String[] args) {
      
              List<String> lines = Arrays.asList("a", "b", "c");
              System.out.println(lines); // [a, b, c]
              Predicate<? super String> predicate = value -> "b".equals(value);
              lines = lines.stream().filter(predicate.negate()).collect(toList());
      
              System.out.println(lines); // [a, c]
          }
      }
      

      在这个解决方案中,原始列表没有被修改,但应该在一个新列表中包含您的预期结果,该列表可在与旧列表相同的变量下访问

      【讨论】:

        【解决方案5】:

        您可以只filter() 然后map() 您的结果,而不是创建奇怪的东西。

        这更具可读性和确定性。 Streams 只会在一个循环中完成。

        【讨论】:

          【解决方案6】:

          如前所述 - 您无法修改原始列表,但您可以将项目流式传输、修改和收集到新列表中。这是如何修改字符串元素的简单示例。

          public class StreamTest {
          
              @Test
              public void replaceInsideStream()  {
                  List<String> list = Arrays.asList("test1", "test2_attr", "test3");
                  List<String> output = list.stream().map(value -> value.replace("_attr", "")).collect(Collectors.toList());
                  System.out.println("Output: " + output); // Output: [test1, test2, test3]
              }
          }
          

          【讨论】:

            【解决方案7】:

            您可以使用removeIf 有条件地从列表中删除数据。

            例如:- 如果你想从一个列表中删除所有偶数,你可以这样做。

                final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());
            
                list.removeIf(number -> number % 2 == 0);
            

            【讨论】:

              【解决方案8】:

              这可能有点晚了。但这是其中一种用法。这是为了获取文件数的计数。

              创建一个指向内存的指针(在这种情况下是一个新的 obj)并修改对象的属性。 Java 8 流不允许修改指针本身,因此如果您将 count 声明为变量并尝试在流中递增,它将永远不会工作并首先引发编译器异常

              Path path = Paths.get("/Users/XXXX/static/test.txt");
              
              
              
              Count c = new Count();
                          c.setCount(0);
                          Files.lines(path).forEach(item -> {
                              c.setCount(c.getCount()+1);
                              System.out.println(item);});
                          System.out.println("line count,"+c);
              
              public static class Count{
                      private int count;
              
                      public int getCount() {
                          return count;
                      }
              
                      public void setCount(int count) {
                          this.count = count;
                      }
              
                      @Override
                      public String toString() {
                          return "Count [count=" + count + "]";
                      }
              
              
              
                  }
              

              【讨论】:

                【解决方案9】:

                是的,您同样可以修改或更新列表中对象的值:

                users.stream().forEach(u -> u.setProperty("some_value"))
                

                但是,上述语句将对源对象进行更新。在大多数情况下这可能是不可接受的。

                幸运的是,我们确实有另一种方法:

                List<Users> updatedUsers = users.stream().map(u -> u.setProperty("some_value")).collect(Collectors.toList());
                

                它会返回一个更新的列表,而不妨碍旧列表。

                【讨论】:

                • 完全没用。除非.setProperty()返回this,否则是语法错误
                猜你喜欢
                • 2018-03-08
                • 2011-03-31
                • 2020-05-04
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-12-09
                • 2021-05-22
                • 1970-01-01
                相关资源
                最近更新 更多