Чтобы попытаться глубоко понять потоки java и разделители, у меня есть некоторые тонкие вопросы о характеристиках spliterator:
Q1: Stream.empty()
vs Stream.of()
(Stream.of() без аргументов)
-
Stream.empty()
: SUBSIZED, SIZED
-
Stream.of()
: SUBSIZED, IMMUTABLE, SIZED, ORDERED
Почему Stream.empty()
не имеет одинаковых характеристик Stream.of()
? Обратите внимание, что он имеет влияние при использовании в сочетании с Stream.concat() (специально не имея ORDERED
). Я бы сказал, что Stream.empty()
должен иметь не только IMMUTABLE и ORDERED, но также DISTINCT и NONNULL. Также имеет смысл Stream.of()
с одним аргументом, имеющим DISTICT.
Q2: LongStream.of()
не имеет NONNULL
Только что заметил, что NONNULL недоступен в LongStream.of
.
Не NONNULL
основные характеристики всех LongStream
s, IntStream
и DoubleStream
s?
Q3: LongStream.range(,)
vs LongStream.range(,).boxed()
-
LongRange.range(,)
: SUBSIZED, IMMUTABLE, NONNULL, SIZED, ORDERED, СОРТИРОВАН, DISTINCT
-
LongStream.range(,).boxed()
: SUBSIZED, SIZED, ORDERED
Почему .boxed()
теряет все эти характеристики? Он не должен терять.
Я понимаю, что .mapToObj()
может потерять NONNULL, IMMUTABLE и DISTICT, но .boxed()
... не имеет смысла.
Q4: .peek()
проигрывает IMMUTABLE и NONNULL
LongStream.of(1)
: СУБИЗНЕС, НЕМЕДЛЕННО, НЕТУЛЬНО, РАЗМЕР,...
LongStream.of(1).peek()
: SUBSIZED, SIZED,...
Почему .peek()
теряет эти характеристики? .peek
не должен действительно терять.
Q5: .skip()
, .limit()
теряет SUBSIZED, IMMUTABLE, NONNULL, SIZED
Просто обратите внимание, что эти операции теряют SUBSIZED, IMMUTABLE, NONNULL, SIZED. Зачем? Если размер доступен, то также легко вычислить конечный размер.
Q6: .filter()
проигрывает IMMUTABLE, NONNULL
Просто заметьте, что эта операция также теряет СУБИЗНЕС, НЕМЕДЛЕННО, НЕТУЛЬНО, РАЗМЕР. Имеет смысл потерять SUBSIZED и SIZED, но остальные два не имеют смысла. Почему?
Буду признателен, если кто-то, кто глубоко понимает, разделитель может принести определенную ясность. Спасибо.
Ответ 1
Я должен признать, что у меня тоже были трудности, когда я впервые попытался выяснить фактическое значение характеристик и почувствовал, что их смысл не был четко урегулирован на этапе реализации Java 8 и используется непоследовательно по этой причине.
Рассмотрим Spliterator.IMMUTABLE
:
Характеристическое значение, означающее, что источник элемента не может быть структурно изменен; то есть элементы не могут быть добавлены, заменены или удалены, поэтому такие изменения не могут произойти во время обхода.
Странно видеть "замененный" в этом списке, который обычно не рассматривается как структурная модификация, когда речь идет о List
или массиве, и, следовательно, заводы потока и spliterator, принимающие массив (который не клонируется) отчет IMMUTABLE
, например LongStream.of(…)
или Arrays.spliterator(long[])
.
Если мы интерпретируем это более щедро как "до тех пор, пока не будет наблюдаться клиентом", нет существенной разницы в CONCURRENT
, так как в любом случае некоторые элементы будут сообщаться клиенту без какого-либо способа узнать, были добавлены во время обхода или некоторые из них были не зарегистрированы из-за удаления, так как нет возможности перемотать разделитель и сравнить его.
Спецификация продолжается:
Ожидается, что разделитель, который не сообщает 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)
:
Возвращаемый spliterator всегда сообщает характеристики SIZED
и SUBSIZED
. Вызывающий абонент может предоставить дополнительные характеристики для сообщения разделителя. (Например, если известно, что массив не будет дополнительно изменен, укажите IMMUTABLE
; если данные массива имеют порядок встреч, укажите ORDERED
). Вместо этого часто можно использовать метод Arrays.spliterator(long[], int, int)
, который возвращает разделитель, который сообщает SIZED
, SUBSIZED
, IMMUTABLE
и ORDERED
.
Обратите внимание (снова) на непоследовательное использование характеристики IMMUTABLE
. Он снова рассматривается как необходимость гарантировать отсутствие каких-либо изменений, в то же время, Arrays.spliterator
и, в свою очередь, Arrays.stream
и LongStream.of(…)
будет сообщать характеристику IMMUTABLE
даже по спецификации, не имея возможности гарантировать что вызывающий не изменит свой массив. Если мы не считаем, что элемент не является структурной модификацией, но затем все различие снова становится бессмысленным, поскольку массивы не могут быть структурно изменены.
И в нем четко указана характеристика NONNULL
. Хотя очевидно, что примитивные значения не могут быть null
, а классы Spliterator.Abstract<Primitive>Spliterator
неизменно вводят a 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(…)
освобождения порядка, когда только один вход не имеет порядка, сомнительно. Обратите внимание, что быть слишком агрессивным в отношении заказа не является чем-то новым, см. этот Q & A в отношении поведения, которое сначала считалось преднамеренным, но затем было исправлено уже на Java 8, обновление 60. Возможно, Stream.concat
должен был обсуждаться прямо в этот момент времени...
Поведение .boxed()
легко объяснить. Когда он был реализован наивно, как .mapToObj(Long::valueOf)
, он просто потеряет все знания, так как mapToObj
не может предположить, что результат все еще отсортирован или разный. Но это было исправлено с помощью Java 9. Там LongStream.range(0,10).boxed()
имеет характеристики SUBSIZED|SIZED|ORDERED|SORTED|DISTINCT
, сохраняя все характеристики, имеющие отношение к реализации.