gargPankaj
gargPankaj

Reputation: 27

Behaviour of Stream when the source of the Stream is changed after the Stream is created

Consider this code below :

     List<Integer> l=new ArrayList<>();
     l.add(23);l.add(45);l.add(90);

     Stream<Integer> str=l.stream();     // mark A

     l.add(111);
     l=null;

     System.out.println(str.collect(Collectors.toList())); // mark B

OUTPUT IS :

[23, 45, 90, 111]

I am assuming here that when terminal operation is called at mark B then RHS of mark A is evaluated which means recent list (with element "111") is getting picked but the question is why we aren't getting NullPointerException here.. if we aren't getting the exception then we shouldn't be getting the "111" in the output as well.. please help.

Upvotes: 0

Views: 515

Answers (2)

Holger
Holger

Reputation: 298263

This behavior is explicitly stated in the documentation:

For well-behaved stream sources, the source can be modified before the terminal operation commences and those modifications will be reflected in the covered elements. For example, consider the following code:

List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
l.add("three");
String s = sl.collect(joining(" "));

First a list is created consisting of two strings: "one"; and "two". Then a stream is created from that list. Next the list is modified by adding a third string: "three". Finally the elements of the stream are collected and joined together. Since the list was modified before the terminal collect operation commenced the result will be a string of "one two three". All the streams returned from JDK collections, and most other JDK classes, are well-behaved in this manner; for streams generated by other libraries, see Low-level stream construction for requirements for building well-behaved streams.

Upvotes: 6

Eran
Eran

Reputation: 393856

l.stream() creates a Stream that probably keeps a reference to the source List (I'm saying probably because that's an implementation detail).

At the time you consume the Stream, the List already has 4 elements, so 4 elements are consumed.

Changing the l reference to null doesn't affect the reference stored within the Stream instance.

if we aren't getting the excpetion then we shouldn't be getting the "111" in the output as well.

That would be true only if the Stream implementation created a copy of the source List instead of just keeping a reference to the original List. Since that would be wasteful in terms of memory usage, it's not surprising that this is not the behavior.

Looking at the implementation of Collection's stream() (at least in Java 8), I see:

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

spliterator() is overridden by ArrayList:

public Spliterator<E> spliterator() {
    return new ArrayListSpliterator<>(this, 0, -1, 0);
}

and ArrayListSpliterator keeps a reference to the source List, as anticipated:

static final class ArrayListSpliterator<E> implements Spliterator<E> {
    ....
    private final ArrayList<E> list;
    private int index; // current index, modified on advance/split
    private int fence; // -1 until used; then one past last index
    private int expectedModCount; // initialized when fence set

    /** Create new spliterator covering the given  range */
    ArrayListSpliterator(ArrayList<E> list, int origin, int fence,
                         int expectedModCount) {
        this.list = list; // OK if null unless traversed
        this.index = origin;
        this.fence = fence;
        this.expectedModCount = expectedModCount;
    }
    ....
}

Upvotes: 2

Related Questions