Javadoc of Collector показывает, как собирать элементы потока в новый список. Есть ли одна строка, которая добавляет результаты в существующий ArrayList?
java
java-8
java-stream
collectors
codefx
источник
источник
Collection
»Ответы:
ПРИМЕЧАНИЕ: ответ nosid показывает, как добавить в существующую коллекцию с помощью
forEachOrdered()
. Это полезный и эффективный метод для изменения существующих коллекций. В моем ответе объясняется, почему вы не должны использоватьCollector
для изменения существующей коллекции.Краткий ответ - нет , по крайней мере, вообще, вы не должны использовать
Collector
для изменения существующей коллекции.Причина в том, что коллекторы предназначены для поддержки параллелизма даже над коллекциями, которые не являются поточно-ориентированными. Они делают так, чтобы каждый поток работал независимо со своей собственной коллекцией промежуточных результатов. Способ, которым каждый поток получает свою собственную коллекцию, состоит в том, чтобы вызывать метод,
Collector.supplier()
который требуется для возврата новой коллекции каждый раз.Эти коллекции промежуточных результатов затем объединяются, снова в потоке-ограниченном режиме, пока не будет единой коллекции результатов. Это конечный результат
collect()
операции.Пара ответы от Бальдра и assylias предложили использовать
Collectors.toCollection()
и затем передать поставщик , который возвращает существующий список вместо нового списка. Это нарушает требование к поставщику, которое заключается в том, что он каждый раз возвращает новую пустую коллекцию.Это будет работать для простых случаев, как показывают примеры в их ответах. Однако это не удастся, особенно если поток работает параллельно. (Будущая версия библиотеки может измениться непредвиденным образом, что приведет к ее сбою, даже в последовательном случае.)
Давайте рассмотрим простой пример:
Когда я запускаю эту программу, я часто получаю
ArrayIndexOutOfBoundsException
. Это потому, что работают несколько потоковArrayList
, небезопасная структура данных. Хорошо, давайте сделаем это синхронизировано:Это больше не приведет к ошибке за исключением. Но вместо ожидаемого результата:
это дает странные результаты, как это:
Это результат операций накопления / объединения, ограниченных потоками, которые я описал выше. При параллельном потоке каждый поток вызывает поставщика, чтобы получить собственную коллекцию для промежуточного накопления. Если вы передаете поставщика, который возвращает ту же коллекцию, каждый поток добавляет свои результаты в эту коллекцию. Поскольку в потоках нет упорядочения, результаты будут добавляться в произвольном порядке.
Затем, когда эти промежуточные коллекции объединяются, это в основном объединяет список с самим собой. Списки объединяются с помощью
List.addAll()
, который говорит, что результаты не определены, если исходная коллекция была изменена во время операции. В этом случаеArrayList.addAll()
выполняется операция копирования массива, поэтому она в конечном итоге дублирует себя, что , скорее всего, и следовало ожидать. (Обратите внимание, что другие реализации List могут иметь совершенно другое поведение.) В любом случае, это объясняет странные результаты и дублированные элементы в месте назначения.Вы можете сказать: «Я просто позабочусь о том, чтобы мой поток запускался последовательно», а затем напишу такой код
тем не мение. Я бы рекомендовал не делать этого. Конечно, если вы управляете потоком, вы можете гарантировать, что он не будет работать параллельно. Я ожидаю, что появится стиль программирования, в котором вместо коллекций будут передаваться потоки. Если кто-то передает вам поток и вы используете этот код, произойдет сбой, если поток окажется параллельным. Хуже того, кто-то может передать вам последовательный поток, и этот код некоторое время будет нормально работать, проходить все тесты и т. Д. Затем, через некоторое произвольное время, код в другом месте системы может измениться на использование параллельных потоков, что вызовет ваш код сломать.
Хорошо, тогда просто не забудьте позвонить
sequential()
в любой поток, прежде чем использовать этот код:Конечно, вы будете помнить делать это каждый раз, верно? :-) Допустим, вы делаете. Тогда команде по производительности будет интересно, почему все их тщательно разработанные параллельные реализации не обеспечивают никакого ускорения. И снова они проследят это до вашего кода, который заставляет весь поток работать последовательно.
Не делай этого.
источник
toCollection
метода возвращал новую и пустую коллекцию каждый раз, убеждает меня не делать этого. Я действительно хочу разорвать контракт Javadoc на основные классы Java.forEachOrdered
. Побочные эффекты включают добавление элементов в существующую коллекцию независимо от того, есть ли у нее уже элементы. Если вы хотите поместить элементы потока в новую коллекцию, используйтеcollect(Collectors.toList())
илиtoSet()
илиtoCollection()
.Насколько я вижу, все остальные ответы до сих пор использовали коллектор для добавления элементов в существующий поток. Однако есть более короткое решение, и оно работает как для последовательных, так и для параллельных потоков. Вы можете просто использовать метод forEachOrdered в сочетании со ссылкой на метод.
Единственным ограничением является то, что источник и цель - это разные списки, потому что вы не можете вносить изменения в источник потока, пока он обрабатывается.
Обратите внимание, что это решение работает как для последовательных, так и для параллельных потоков. Тем не менее, он не выигрывает от параллелизма. Ссылка на метод, переданная в forEachOrdered , всегда будет выполняться последовательно.
источник
forEach(existing::add)
в качестве возможности в ответ два месяца назад . Я должен был также добавитьforEachOrdered
...forEachOrdered
вместоforEach
?forEachOrdered
работает как для последовательных, так и для параллельных потоков. Напротив,forEach
может выполнять переданный объект функции одновременно для параллельных потоков. В этом случае объект функции должен быть правильно синхронизирован, например, с помощьюVector<Integer>
.target::add
. Независимо от того, из каких потоков вызывается метод, данных не происходит . Я бы ожидал, что вы это знаете.Краткий ответ - нет (или должно быть нет). РЕДАКТИРОВАТЬ: да, это возможно (см. Ответ Ассилии ниже), но продолжайте читать. РЕДАКТИРОВАТЬ 2: но посмотрите ответ Стюарта Маркса по еще одной причине, почему вы все еще не должны это делать!
Чем дольше ответ:
Цель этих конструкций в Java 8 состоит в том, чтобы ввести некоторые концепции функционального программирования в язык; в функциональном программировании структуры данных обычно не модифицируются, вместо этого новые создаются из старых с помощью таких преобразований, как карта, фильтр, сложение / уменьшение и многие другие.
Если вам нужно изменить старый список, просто соберите сопоставленные элементы в новый список:
а затем сделайте
list.addAll(newList)
- снова: если вы действительно должны.(или создайте новый список, объединяющий старый и новый, и назначьте его обратно
list
переменной - это немного больше в духе FP, чемaddAll
)Что касается API: даже если API это позволяет (опять же, смотрите ответ ассилий), вы должны стараться избегать этого независимо, по крайней мере, в целом. Лучше не бороться с парадигмой (FP) и пытаться изучать ее, а не бороться с ней (хотя Java обычно не является языком FP), а прибегать к «более грязной» тактике только в случае крайней необходимости.
Очень длинный ответ: (т. Е. Если вы включите усилия по нахождению и чтению вступления / книги по FP, как это было предложено)
Чтобы выяснить, почему изменение существующих списков, как правило, является плохой идеей и приводит к снижению удобства сопровождения кода - если вы не изменяете локальную переменную, а ваш алгоритм не является коротким и / или тривиальным, что выходит за рамки вопроса поддерживаемости кода - найти хорошее введение в функциональное программирование (их сотни) и начать чтение. Объяснение «предварительного просмотра» будет выглядеть примерно так: оно более математически обосновано и легче рассуждать о том, что не нужно изменять данные (в большинстве частей вашей программы), и приводит к более высокому уровню и менее техническому (а также более дружественному к человеку, как только ваш мозг) переходы от императивного мышления старого стиля) определения программной логики.
источник
Эрик Аллик уже привел очень веские причины, по которым вы, скорее всего, не захотите собирать элементы потока в существующий список.
В любом случае, вы можете использовать следующий однострочный, если вам действительно нужна эта функциональность.
Но, как объясняет Стюарт Маркс в своем ответе, вы никогда не должны этого делать, если потоки могут быть параллельными потоками - используйте на свой страх и риск ...
источник
Вы просто должны сослаться на свой первоначальный список, чтобы быть тем, который
Collectors.toList()
возвращает.Вот демо:
И вот как вы можете добавить вновь созданные элементы в ваш исходный список в одну строку.
Вот что обеспечивает эта парадигма функционального программирования.
источник
targetList = sourceList.stream (). flatmap (List :: stream) .collect (Collectors.toList ());
источник
Я бы объединял старый список и новый список в виде потоков и сохранял результаты в списке назначения. Параллельно работает тоже хорошо.
Я буду использовать пример принятого ответа, который дал Стюарт Маркс:
Надеюсь, поможет.
источник