Должен признаться, что у меня тоже были трудности, когда я впервые пытался выяснить фактическое значение характеристик, и у меня было ощущение, что их значение не было четко определено на этапе реализации Java 8 и по этой причине используется непоследовательно.
Рассмотрим Spliterator.IMMUTABLE
:
Значение характеристики, означающее, что источник элемента не может быть структурно изменен; то есть элементы нельзя добавлять, заменять или удалять, поэтому такие изменения не могут происходить во время обхода.
Странно видеть «заменено» в этом списке, что обычно не считается структурной модификацией, когда речь идет о List
или массиве, и, следовательно, потоковые и сплиттерные фабрики, принимающие массив (который не клонирован), сообщают IMMUTABLE
, как LongStream.of(…)
или Arrays.spliterator(long[])
.
Если мы интерпретируем это более широко как «до тех пор, пока клиент не заметит», нет существенной разницы с CONCURRENT
, так как в любом случае некоторые элементы будут сообщены клиенту без какой-либо возможности распознать были ли они добавлены во время обхода или некоторые не были зарегистрированы из-за удаления, так как нет возможности перемотать сплитератор и сравнить.
Спецификация продолжается:
Ожидается, что Spliterator, который не сообщает IMMUTABLE
или CONCURRENT
, будет иметь задокументированную политику (например, выдачу ConcurrentModificationException
) в отношении структурных помех, обнаруженных во время обхода.
И это единственная существенная вещь: сплитератор, сообщающий либо IMMUTABLE
, либо CONCURRENT
, гарантированно никогда не выдаст ConcurrentModificationException
. Конечно, CONCURRENT
семантически исключает SIZED
, но это не имеет никакого значения для клиентского кода.
На самом деле эти характеристики ни для чего не используются в Stream API, поэтому их непоследовательное использование никогда и нигде не будет замечено.
Это также объясняет, почему каждая промежуточная операция приводит к очистке характеристик CONCURRENT
, IMMUTABLE
и NONNULL
: реализация Stream не использует их, а его внутренние классы, представляющие состояние потока, не поддерживают их.
Аналогично, NONNULL
нигде не используется, так что его отсутствие для определенных потоков не влияет. Я мог бы отследить проблему LongStream.of(…)
до внутреннего использования Arrays.spliterator(long[], int, int)
, которое делегирует
Spliterators.spliterator(long[] array, int fromIndex, int toIndex, int additionalCharacteristics)
:
Возвращенный разделитель всегда сообщает характеристики SIZED
и SUBSIZED
. Вызывающий может предоставить разделителю дополнительные характеристики для отчета. (Например, если известно, что массив больше не будет изменяться, укажите IMMUTABLE
; если считается, что данные массива имеют порядок обнаружения, укажите ORDERED
). Вместо этого часто можно использовать метод Arrays.spliterator(long[], int, int)
, который возвращает разделитель, сообщающий SIZED
, SUBSIZED
, IMMUTABLE
и ORDERED
.
Обратите внимание (снова) на непоследовательное использование характеристики IMMUTABLE
. Это снова рассматривается как обязанность гарантировать отсутствие каких-либо модификаций, в то время как Arrays.spliterator
и, в свою очередь, Arrays.stream
и LongStream.of(…)
будут сообщать о характеристике IMMUTABLE
, даже по спецификации, не имея возможности гарантировать, что вызывающая сторона не изменит свои множество. Если мы не считаем, что установка элемента не является структурной модификацией, но тогда все различие снова становится бессмысленным, поскольку массивы не могут быть структурно изменены.
И в нем четко не указано NONNULL
characteristic. Хотя очевидно, что примитивные значения не могут быть null
, а классы Spliterator.Abstract<Primitive>Spliterator
неизменно вводят характеристику NONNULL
, разделитель, возвращаемый Spliterators.spliterator(long[],int,int,int)
, не наследуется от Spliterator.AbstractLongSpliterator
.
Плохо то, что это нельзя исправить без изменения спецификации, хорошо то, что это все равно не имеет последствий.
Поэтому, если мы проигнорируем любые проблемы с CONCURRENT
, IMMUTABLE
или NONNULL
, которые не имеют последствий, мы
SIZED
и skip
и limit
. Это хорошо известная проблема, возникшая в результате того, как skip
и limit
были реализованы в Stream API. Возможны и другие реализации. Это также относится к комбинации бесконечного потока с limit
, который должен иметь предсказуемый размер, но, учитывая текущую реализацию, не имеет.
Объединение Stream.concat(…)
с Stream.empty()
. Звучит разумно, что пустой поток не накладывает ограничения на порядок результатов. Но поведение Stream.concat(…)
по снятию ордера, когда только один вход не имеет ордера, сомнительно. Обратите внимание, что слишком агрессивное отношение к упорядочению не является чем-то новым, см. эти вопросы и ответы относительно поведения, которое сначала считалось преднамеренным, но затем исправлено еще в Java 8, обновление 60. Возможно, Stream.concat
тоже стоило обсудить именно в этот момент…
Поведение .boxed()
легко объяснить. Когда он реализован наивно, как .mapToObj(Long::valueOf)
, он просто потеряет все знания, так как mapToObj
не может предположить, что результат все еще отсортирован или различен. Но это было исправлено в Java 9. Там LongStream.range(0,10).boxed()
имеет характеристики SUBSIZED|SIZED|ORDERED|SORTED|DISTINCT
, сохраняя все характеристики, имеющие отношение к реализации.
person
Holger
schedule
09.10.2017
.mapToObj()
может потерять NULL, IMMUTABLE и DISTI[N]CT. Почему не СОРТИРОВАТЬ? - person shmosel   schedule 20.12.2017