Необычное поведение списка

Я определил список из 0-9(Integer), как показано ниже:

List<Integer> list = 
                IntStream.range(0, 10)
                         .boxed()
                         .collect(Collectors.toCollection(ArrayList::new));

Когда я пытаюсь удалить элементы, используя приведенный ниже код:

list.stream()
            .peek(list::remove)
            .forEach(System.out::println);

Он должен бросить ConcurrentModificationException, но интересно, что он работает для некоторых элементов и выводит ниже вывод (исключение в конце и удаление некоторых элементов):

0
2
4
6
8
null
null
null
null
null
Exception in thread "main" java.util.ConcurrentModificationException

Но если я добавил sorted(), как показано ниже:

list.stream()
            .sorted()
            .peek(list::remove)
            .forEach(System.out::println);

Это работало отлично и удалило все элементы, я смущен, почему stream ведет себя таким образом.

Ответы

Ответ 1

Посмотрели ли вы на исходный код, как эти классы реализованы?

Метод sorted() сделает копию вашего списка для сортировки.

Таким образом, он ведет себя по-другому: вы выполняете итерацию по отсортированной копии, но удаляете только из первого списка.

peek() не рекомендуется

Метод peek не рекомендуется использовать; из JavaDoc:

Этот метод существует главным образом для поддержки отладки

Вполне возможно поставить System::println там или аналогичный для целей разработки, но это не функциональность, которую вы должны использовать для своей окончательной программы.

Никогда не пытайтесь изменить текущий поток

Прочитайте спецификацию API пакета потока:

https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#NonInterference

Для большинства источников данных предотвращение помех означает , гарантируя, что источник данных вообще не будет изменен во время выполнения конвейера потока.

Итак, другими словами: вам не удастся удалить из списка, который вы сейчас используете, нет гарантий, что он будет работать.

Что происходит

Ваш начальный список

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]        pos = 0

Вы печатаете 0 и удаляете его, затем переходите к следующей позиции. Новый массив тогда

[1, 2, 3, 4, 5, 6, 7, 8, 9, null]     pos = 1

В следующей позиции теперь значение 2, поэтому распечатайте и удалите 2:

[1, 3, 4, 5, 6, 7, 8, 9, null, null]  pos = 2

и т.д. Проблема состоит в том, что из-за ваших одновременных удалений оставшаяся часть списка продолжает двигаться вперед.

ConcurrentModificationException бросается в конец потока. Это похоже на решение о производительности (только один раз для одновременных изменений, а не на каждой итерации) только для forEachRemaining, поэтому этот поток не работает быстро