105

In Java8 streams, am I allowed to modify/update objects within? For eg. List<User> users:

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

10 Answers 10

119

Yes, you can modify state of objects inside your stream, but most often you should avoid modifying state of source of stream. From non-interference section of stream package documentation we can read that:

For most data sources, preventing interference means ensuring that the data source is not modified at all during the execution of the stream pipeline. The notable exception to this are streams whose sources are concurrent collections, which are specifically designed to handle concurrent modification. Concurrent stream sources are those whose Spliterator reports the CONCURRENT characteristic.

So this is OK

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

but this in most cases is not

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

and may throw ConcurrentModificationException or even other unexpected exceptions like 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
Sign up to request clarification or add additional context in comments.

10 Comments

What are solutions if you want to modify users?
@Augustas It all depends on how you want to modify this list. But generally you should avoid Iterator which prevents modifying list (removing/adding new elements) while iterating and both streams and for-each loops are using it. You could try using simple loop like for(int i=0; ..; ..) which doesn't have this problem (but will not stop you when other thread will modify your list). You could also use methods like list.removeAll(Collection), list.removeIf(Predicate). Also java.util.Collections class have few methods which could be useful like addAll(CollectionOfNewElements,list).
@Pshemo, one solution to that is to create a new instance of a Collection like ArrayList with the items inside your primary list; iterate over the new list, and do the operation on the primary list: new ArrayList<>(users).stream.forEach(u -> users.remove(u));
@Blauhirn Solve what? We are not allowed to modify source of stream while using stream, but are allowed to modify state of its elements. It doesn't matter if we use map, foreach or other stream methods.
Instead of modifying the collection I would suggest using filter()
|
5

The functional way would imho be:

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

In this solution the original list is not modified, but should contain your expected result in a new list that is accessible under the same variable as the old one

Comments

4

To do structural modification on the source of the stream, as Pshemo mentioned in his answer, one solution is to create a new instance of a Collection like ArrayList with the items inside your primary list; iterate over the new list, and do the operations on the primary list.

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

Comments

2

You can make use of the removeIf to remove data from a list conditionally.

Eg:- If you want to remove all even numbers from a list, you can do it as follows.

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

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

Comments

1

To get rid from ConcurrentModificationException Use CopyOnWriteArrayList

2 Comments

That is actually correct, but: this depends on the specific class that is used here. But when you only know that you have a List<Something> - you have no idea if that is a CopyOnWriteArrayList. So this more like a mediocre comment, but not a real answer.
If he had posted it as a comment, someone most likely would have told him he shouldn't post answers as comments.
1

Instead of creating strange things, you can just filter() and then map() your result.

This is much more readable and sure. Streams will make it in only one loop.

Comments

1

As it was mentioned before - you can't modify original list, but you can stream, modify and collect items into new list. Here is simple example how to modify string element.

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

Comments

1

.peek() is the answer.

users.stream().peek(u -> u.setProperty("value")).foreach(i->{
    ...
    ...
});

for new list

users.stream().peek(u -> u.setProperty("value")).collect(Collectors.toList());

2 Comments

Beware, as the peek() documentation says “This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline“, so it shouldnt be used like this.
Getting "Stream.peek" should not be used (squid:S3864)
-2

This might be a little late. But here is one of the usage. This to get the count of the number of files.

Create a pointer to memory (a new obj in this case) and have the property of the object modified. Java 8 stream doesn't allow to modify the pointer itself and hence if you declare just count as a variable and try to increment within the stream it will never work and throw a compiler exception in the first place

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 + "]";
        }



    }

Comments

-2

Yes, you can modify or update the values of objects in the list in your case likewise:

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

However, the above statement will make updates on the source objects. Which may not be acceptable in most cases.

Luckily, we do have another way like:

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

Which returns an updated list back, without hampering the old one.

1 Comment

total useless. Unless .setProperty() return this, otherwise it is a syntax error

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.