I have following code:
public static void main(String[] args) { List<String> input = new ArrayList<>(); List<String> output = new ArrayList<>(); for(int i=0; i< 1000 ;i++){ input.add(i+""); } for(int i=0 ; i<input.size(); i++){ String value = input.get(i); if(Integer.parseInt(value) % 2 == 0){ output.add(value); input.remove(value); } } input.stream().forEach(System.out::println); System.out.println("--------------------------------------"); output.stream().forEach(System.out::println); }
I expected it to throw ConcurrentModificationException
but it is working fine. Can some explain the reason?
Advertisement
Answer
The reason is you are not technically iterating the List. Instead you are random accessing the list using a incrementing index, and removing some values. If you change to code like this to iterate the list it will throw ConcurrentModificationException
public static void main(String[] args) { List<String> input = new ArrayList<>(); List<String> output = new ArrayList<>(); for(int i=0; i< 1000 ;i++){ input.add(i+""); } for (String value : input) { if(Integer.parseInt(value) % 2 == 0){ output.add(value); input.remove(value); } } input.stream().forEach(System.out::println); System.out.println("--------------------------------------"); output.stream().forEach(System.out::println); }
A follow up on why this might not be a preferred way compared to an iterator. One reason is performance. Here is some benchmark code using JMH to test this out.
package bench; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Warmup; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.TimeUnit; import static java.util.concurrent.TimeUnit.SECONDS; @State(Scope.Benchmark) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Warmup(iterations = 1, time = 3, timeUnit = SECONDS) @Measurement(iterations = 3, time = 2, timeUnit = SECONDS) public class JmhBenchmark { private List<String> input; @Param({"100", "1000", "10000"}) public int length; @Setup(Level.Invocation) public void createInputList() { input = new ArrayList<>(); for (int i = 0; i < length; i++) { input.add(i + ""); } } @Benchmark public void iterateWithVariable() { for (int i = 0; i < input.size(); i++) { String value = input.get(i); if (Integer.parseInt(value) % 2 == 0) { input.remove(value); } } } @Benchmark public void iterateWithIterator() { final Iterator<String> iterator = input.iterator(); while (iterator.hasNext()) { String value = iterator.next(); if (Integer.parseInt(value) % 2 == 0) { iterator.remove(); } } } }
The results of the benchmark on my system were
Benchmark (length) Mode Cnt Score Error Units JmhBenchmark.iterateWithIterator 100 avgt 15 0.002 ± 0.001 ms/op JmhBenchmark.iterateWithIterator 1000 avgt 15 0.033 ± 0.001 ms/op JmhBenchmark.iterateWithIterator 10000 avgt 15 1.670 ± 0.017 ms/op JmhBenchmark.iterateWithVariable 100 avgt 15 0.005 ± 0.001 ms/op JmhBenchmark.iterateWithVariable 1000 avgt 15 0.350 ± 0.014 ms/op JmhBenchmark.iterateWithVariable 10000 avgt 15 33.591 ± 0.455 ms/op
So we can see using an iterator to remove some items from a list is a lot (>20x) faster than the approach posed by this question. Which makes sense you need to perform a random lookup in the list then determine if it needs to be removed and then do another lookup to find and remove it.