Reputation: 27
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
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
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