Reputation: 405
I was trying the following code Java 8 SE I ran it directly from eclipse, it has the below-mentioned exception also I ran it with command prompt it produces the same result.
List<String> test = new ArrayList<>();
test.add("A");
test.add("B");
test.add("c");
test = test.subList(0, 2);
Stream<String> s = test.stream();
test.add("d");
s.forEach(System.out::println);
I am not sure as to why exactly it gives the following exception
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
Java version I am running with
java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)
Upvotes: 9
Views: 9633
Reputation: 31878
List<String> test = new ArrayList<>(Arrays.asList("java-8", "subList", "bug")).subList(0, 2);
Stream<String> stream = test.stream();
test.add("java-9");
stream.forEach(System.out::println); // any terminal operation
The code above executed with Java-8 throws a CME. According to the javadoc of ArrayList
The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a
ConcurrentModificationException
.Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
Output:
java-8 subList Exception in thread "main" java.util.ConcurrentModificationException
Under the similar guidelines, modifying a collection while it's being iterated is considered to be a programming error and consequently throwing of ConcurrentModificationException
is performed on a "best-effort" basis.
But then the question was, in the code above did we actually end up modifying the collection while it was being iterated or rather before that?
shouldn't stream be lazy?
On searching further for such expected behavior, found something similar reported and fixed as a bug - ArrayList.subList().spliterator() is not late-binding and this has been fixed with Java-9.
Another bug related to this - ArrayList.subList().iterator().forEachRemaining() off-by-one-error
Though fixed in Java-9 according to the bug report, the actual test that I was performing was on the LTS version and the code as shared above works without any exception.
Output:
java-8 subList java-9
Upvotes: 8
Reputation: 40048
This is because of subList
,let me explain with different scenarios
From java docs docs
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.
Except for the escape-hatch operations iterator() and spliterator(), execution begins when the terminal operation is invoked, and ends when the terminal operation completes.
Case 1: Successful (because the source can be modified before the terminal operation commences)
List<String> test = new ArrayList<>();
test.add("A");
test.add("B");
test.add("c");
//test = test.subList(0, 2);
Stream s = test.stream();
test.add("d");
s.forEach(System.out::println);
Output :
A
B
c
d
Case 2: Failed for sublist
different reference
List<String> test = new ArrayList<>();
test.add("A");
test.add("B");
test.add("c");
List<String> test1 = test.subList(0, 2);
Stream s = test1.stream();
test1.add("d");
s.forEach(System.out::println);
Output :
A
BException in thread "main"
java.util.ConcurrentModificationException
at
java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.demo.Example.Main2.main(Main2.java:30)
case 3: Failed for sublist
same reference, Both lists are different
List<String> test = new ArrayList<>();
test.add("A");
test.add("B");
test.add("c");
System.out.println(test.hashCode()); //94401
test = test.subList(0, 2);
System.out.println(test.hashCode()); //3042
Stream s = test.stream();
test.add("d");
s.forEach(System.out::println);
Output :
94401
3042
A
B
Exception in thread "main" java.util.ConcurrentModificationException
at
java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.demo.Example.Main2.main(Main2.java:32)
Final Conclusion
The semantics of the list returned by this method become undefined if the backing list (i.e., this list) is structurally modified in any way other than via the returned list. (Structural modifications are those that change the size of this list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.)
From docs subList docs
Upvotes: 5
Reputation: 311188
One way to get this exception is by updating the underlying list after creating a stream from it, which is what happened here.
Just perform all your add
calls before calling stream()
, and you should be fine:
List<String> test = new ArrayList<>();
test.add("A");
test.add("B");
test.add("c");
test.add("d"); // Moved here
test = test.subList(0, 2);
Stream s = test.stream();
// test.add("d") removed here
s.forEach(System.out::println);
Upvotes: 3
Reputation: 4423
The problem is that you modify the content of the ArrayList while stream is in use (not closed).
Stream s = test.stream();
test.add("d"); <<<- here
s.forEach(System.out::println);
As javadoc of ConcurrentModificationException mentions:
For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it.
This is true for the streams as well, as in simple case they are based on iterators.
Additionally,
Note that this exception does not always indicate that an object has been concurrently modified by a different thread.
Upvotes: 3