Does Java's stream filter create a new list or point to the original list?

Viewed 581

I read the answers to this question: Will Java 8 create a new List after using Stream "filter" and "collect"?

But it did not quite match my experience... I think. And I'm just wanting to make sure I'm clear on the situation.

Consider the following code (which can be run on https://www.tutorialspoint.com/compile_java_online.php):

import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;

public class ArrayPlay {

    public static class Person {
        private String first;
        private String last;
        private int id;
        private State state;
        private boolean thing;

        public Person(String first, String last, int id, boolean isThing) {
            this.id = id;
            this.first = first;
            this.last = last;
            this.thing = isThing;
        }

        public void setFirst(String first) { this.first = first; }
        public String getFirst() { return first; }

        public void setLast(String last) { this.last = last; }
        public String getLast() { return last; }

        public void setId(int id) { this.id = id; }
        public int getId() { return id; }

        public void setIsThing(boolean thing) { this.thing = thing; }
        public boolean isThing() { return thing; }
        
        public void setState(State state) { this.state = state; }
        public State getState() { return this.state; }

        @Override
        public String toString() {
            return String.valueOf(id) + ": " + first + " " + last + 
                (thing ? " and has thing" : "") + 
                (state != null ? " and is from " + state.getStateName() : "");
        }
    }

    public static class State {
        private String stateName;
        private String stateCode;
        private String country;

        public State() { }

        public String getStateName() { return stateName; }
        public void setStateName(String name) { this.stateName = name; }

        public String getStateCode() { return stateCode; }
        public void setStateCode(String code) { this.stateCode = code; }

        public String getCountry() { return country; }
        public void setCountry(String country) { this.country = country; }

        public static State newStateCode(String code) {
            State state = new State();
            state.setStateCode(code);
            return state;
        }
        
        public static final class Builder {
            private State state;
            
            private Builder() { state = new State(); }
            
            public static Builder aState() { return new Builder(); }
            
            public Builder withName(String name) { state.setStateName(name); return this;}
            public Builder withCode(String code) { state.setStateCode(code); return this;}
            public Builder withCountry(String country) { state.setCountry(country); return this;}
            
            public State build() { return state; }
        }
    }

    public static void main(String[] args) {
        List<Person> people = new ArrayList<Person>() {{
            add(new Person("ffej", "llensog", 13482839, true));
            add(new Person("knarf", "knarfson", 39940000, false));
            add(new Person("Mary", "Contrary", 82233888, true));
        }};

        System.out.println("\nSet State for filtered Persons");
        List<Person> hasThing = people.stream().filter(p -> p.isThing()).collect(Collectors.toList());
        System.out.println("Before setting state.");
        people.forEach(p -> System.out.println("people: " + p));
        hasThing.forEach(p -> p.setState(State.Builder.aState().withName("Alabama").build()));
        System.out.println("After setting state.");
        people.forEach(p -> System.out.println("people: " + p));
    
        hasThing.forEach(p -> System.out.println("hasThing: " + p));
    }
}

The result I expect is the following:

et State for filtered Persons
Before setting state.
people: 13482839: Daphne llensog and has thing
people: 39940000: Daphne knarfson
people: 82233888: Daphne Contrary and has thing
After setting state.
people: 13482839: Daphne llensog and has thing
people: 39940000: Daphne knarfson
people: 82233888: Daphne Contrary and has thing
hasThing: 13482839: Daphne llensog and has thing and is from Alabama
hasThing: 82233888: Daphne Contrary and has thing and is from Alabama

But the actual result that I'm getting is:

et State for filtered Persons
Before setting state.
people: 13482839: Daphne llensog and has thing
people: 39940000: Daphne knarfson
people: 82233888: Daphne Contrary and has thing
After setting state.
people: 13482839: Daphne llensog and has thing and is from Alabama
people: 39940000: Daphne knarfson
people: 82233888: Daphne Contrary and has thing and is from Alabama
hasThing: 13482839: Daphne llensog and has thing and is from Alabama
hasThing: 82233888: Daphne Contrary and has thing and is from Alabama

For some reason, when I run the setState on hasThing's forEach, it is also modifying the objects in people. And this doesn't make sense to me if filter(...).collect() creates a new list. Is there a good explanation for this action that I'm not understanding?

1 Answers

Look it's simple. The List will be new as ArrayList instance. Not the objects that the list contains. Since you modify the instances that the list contain it will appear modified to both lists

it is also modifying the objects in people. And this doesn't make sense to me if filter(...).collect()

Of course it will modify those objects. A list is just a collection that holds references to object instances.

In your case you have 2 collections (Lists) which hold references to the same instances. Using 1 list to modify the state of an object will reflect to both lists.

Here is a simple graphical representation

Using ref2 to modify the state of the instance it actually modifies the same instance that ref4 points to

enter image description here

Related