Понимание характеристик глубокого разделения

Чтобы попытаться глубоко понять потоки 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, сохраняя все характеристики, имеющие отношение к реализации.