迭代时在Java8中修改流中的对象


Answers:


101

是的,您可以修改流内部对象的状态,但是大多数情况下应避免修改流的状态。从流软件包文档的“无干扰”部分,我们可以了解到:

对于大多数数据源,防止干扰意味着确保在流管道执行期间根本不修改数据源。值得注意的例外是流的源是并发集合,这些流是专门为处理并发修改而设计的。并发流源是那些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

4
如果要修改用户,有什么解决方案?
奥古斯塔斯16-3-15

2
@Augustas这完全取决于您要如何修改此列表。但是通常应该避免Iterator这样做,以免在迭代时修改列表(删除/添加新元素),并且流和for-each循环都在使用它。您可以尝试使用for(int i=0; ..; ..)不存在此问题的简单循环(但当其他线程修改您的列表时,它不会阻止您)。你也可以使用类似的方法list.removeAll(Collection)list.removeIf(Predicate)。而且java.util.Collectionsclass很少有像有用的方法addAll(CollectionOfNewElements,list)
Pshemo '16

@Pshemo,一种解决方案是创建一个新的Collection实例,例如ArrayList,并在主列表中添加项目;遍历新列表,并对主列表执行操作:new ArrayList <>(users).stream.forEach(u-> users.remove(u));
MNZ

2
@Blauhirn解决什么问题?我们不允许在使用流时修改流的源,但可以修改其元素的状态。我们使用map,foreach还是其他流方法都没关系。
Pshemo'1

1
我建议不要修改集合,而是使用filter()
jontro '17

5

功能性方法应为:

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]
    }
}

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


3

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

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

1

要摆脱ConcurrentModificationException,请使用CopyOnWriteArrayList


2
这实际上是正确的,但是:这取决于此处使用的特定类。但是,当您只知道自己有一个List<Something>-时,您就不知道这是否是一个CopyOnWriteArrayList。因此,这更像是平庸的评论,而不是真实的答案。
GhostCat

如果他将其发布为评论,则很可能有人会告诉他他不应将答案发布为评论。
比尔K,


1

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

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

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

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

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

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


0

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

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]
    }
}

0

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

例如:-如果要从列表中删除所有偶数,则可以按以下步骤进行操作。

    final List<Integer> list = IntStream.range(1,100).boxed().collect(Collectors.toList());

    list.removeIf(number -> number % 2 == 0);

-1

这可能会晚一点。但这是用法之一。这可以获取文件数量的计数。

创建一个指向内存的指针(在这种情况下为新的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 + "]";
        }



    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.