Глубокое понимание характеристик сплиттеров

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

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

  • Stream.empty(): ДОПОЛНИТЕЛЬНО, РАЗМЕР
  • Stream.of(): ДОПОЛНИТЕЛЬНЫЕ, НЕИЗМЕННЫЕ, РАЗМЕРНЫЕ, ЗАКАЗНЫЕ

Почему Stream.empty() не имеет тех же характеристик, что и Stream.of()? Обратите внимание, что это влияет на использование в сочетании с Stream.concat() (особенно без ORDERED). Я бы сказал, что Stream.empty() должен иметь не только IMMUTABLE и ORDERED, но и DISTINCT и NONNULL. Также имеет смысл Stream.of() иметь только один аргумент, имеющий DISTICT.

Вопрос 2: LongStream.of() не имеет NONNULL

Только что заметил, что NONNULL недоступен в LongStream.of. Разве NONNULL не главная характеристика всех LongStream, IntStream и DoubleStream?

Вопрос 3: LongStream.range(,) против LongStream.range(,).boxed()

  • LongRange.range(,): ДОПОЛНИТЕЛЬНЫЕ, НЕПРЕРЫВНЫЕ, НЕНУЛЕВЫЕ, РАЗМЕРНЫЕ, ЗАКАЗАННЫЕ, СОРТИРОВАННЫЕ, ОТЛИЧНЫЕ
  • LongStream.range(,).boxed(): СУБРАЗМЕР, РАЗМЕР, ЗАКАЗ

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

Я понимаю, что .mapToObj() может потерять NONNULL, IMMUTABLE и DISTICT, но .boxed()... не имеет смысла.

Q4: .peek() проигрывает IMMUTABLE и NONNULL

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

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

Q5: .skip(), .limit() проигрывают SUBSIZED, IMMUTABLE, NONULL, SIZED

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

Вопрос 6: .filter() проигрывает IMMUTABLE, NONNULL

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


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


person Tet    schedule 09.10.2017    source источник
comment
Кто-то проголосовал за то, чтобы закрыть вопрос как слишком широкий, и у меня возник соблазн сделать то же самое, потому что он выглядит как шесть вопросов в одном. С другой стороны, возможно, что есть общее объяснение всех этих деталей...   -  person Nicolai Parlog    schedule 09.10.2017
comment
@Nicolai Сначала я проголосовал ... но это слишком хорошо, чтобы быть закрытым, поэтому я отозвал его. Мне нравится это все-в-одном для такого хорошего вопроса. Помимо нескольких пользователей Oracle (Стюарта? Или Брайана?), я ожидаю, что Хольгер знает точную их внутреннюю сущность. С нетерпением жду. :)   -  person Eugene    schedule 09.10.2017
comment
Я понимаю, что .mapToObj() может потерять NULL, IMMUTABLE и DISTI[N]CT. Почему не СОРТИРОВАТЬ?   -  person shmosel    schedule 20.12.2017
comment
@shmosel хорошее замечание! Не думал об этом, что потенциально может сломать свой род. Ницца!   -  person Eugene    schedule 18.09.2018


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

И в нем четко не указано NONNULLcharacteristic. Хотя очевидно, что примитивные значения не могут быть 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
comment
Спасибо @Holger. Немного неловко иметь так много характеристик, которые не поддерживаются. Однако, если большинство это для англичанина, чтобы увидеть (португальское выражение, которое означает, что это просто для показухи), тогда менее проблематично. Я согласился, что проблемные вопросы возникают при использовании Stream.concat с Stream.empty. И второй - это потоки без размера с limit или skip, которые обсуждались в of-a-stream/46562493#46562493" title="наиболее эффективный способ получить последний элемент потока"> stackoverflow.com/questions/27547519/. - person Tet; 09.10.2017
comment
http://richardstartin.uk/spliterator-characteristics-and-performance/это ссылка тоже помогает - person amarnath harish; 29.08.2018