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

Чтобы глубже понять потоки Java и сплитераторы, у меня есть несколько тонких вопросов о характеристиках сплитератора:

Q1: Stream.empty() против Stream.of() (Stream.of() без аргументов)

  • Stream.empty(): РАЗМЕЩЕННЫЙ, РАЗМЕРНЫЙ
  • Stream.of(): ПОДУШЕННЫЙ, НЕМЕРТНЫЙ, РАЗМЕРНЫЙ, ЗАКАЗАННЫЙ

Зачем Stream.empty() не имеет такие же характеристики Stream.of()? Обратите внимание, что это оказывает влияние при использовании совместно с Stream.concat() (особенно не имея ORDERED). Я бы сказал, что Stream.empty() должен иметь не только НЕМАТУБНЫЙ и ЗАКАЗАННЫЙ, но также ОТЛИЧНЫЙ и НЕНУЛЬНЫЙ. Также имеет смысл Stream.of() только с одним аргументом, имеющим ОГРАНИЧЕНИЕ.

Q2: LongStream.of() не имеющий NONNULL

Просто заметил, что NONNULL не доступен в LongStream.of, не NONNULL основные характеристики всего LongStream s, IntStream с и DoubleStream s?

Q3: LongStream.range(,) против LongStream.range(,).boxed()

  • LongRange.range(,): СУБСИЗИРОВАННЫЙ, НЕИЗМЕННЫЙ, НЕОБЫЧНЫЙ, РАЗМЕРНЫЙ, ЗАКАЗАННЫЙ, Сортированный, ОТЛИЧНЫЙ
  • LongStream.range(,).boxed(): РАЗМЕЩЕННЫЙ, РАЗМЕЩЕННЫЙ, ЗАКАЗАННЫЙ

Зачем .boxed() теряет все эти характеристики? Это не должно потерять ни одного.

Я это понимаю .mapToObj() может потерять NONNULL, НЕМНОГО и DISTICT, но .boxed()... не имеет смысла.

Q4: .peek() теряет НЕИЗБЕЖНЫЙ и НЕНУЛЬНЫЙ

LongStream.of(1): СУБСИЗИРОВАННЫЙ, НЕИЗМЕННЫЙ, НЕНУЛЬНЫЙ, РАЗМЕРНЫЙ,... LongStream.of(1).peek(): ПОДУШЕННЫЙ, РАЗМЕРНЫЙ,...

Зачем .peek() теряет эти характеристики? .peek не должно терять.

Вопрос 5: .skip() , .limit() теряет SUBSIZED, НЕМЕРТЕЛЬНЫЙ, NUNULL, SIZED

Просто обратите внимание, что эти операции теряют SUBSIZED, IMMUTABLE, NONNULL, SIZED. Зачем? Если размер доступен, то также легко рассчитать окончательный размер.

Q6: .filter() теряет НЕИЗБЕЖНЫЙ, НЕНУЛЬНЫЙ

Просто обратите внимание, что эта операция теряет также SUBSIZED, IMMUTABLE, NONNULL, SIZED. Имеет смысл потерять SUBSIZED и SIZED, но два других не имеют смысла. Зачем?


Я буду признателен, если кто-то, кто глубоко понимает сплитератор, может внести некоторую ясность. Благодарю.

1 ответ

Решение

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

Другие вопросы по тегам