Изучите паттерны forEach
и injectInto
в разделе Коллекции Eclipse.
Для forEach или для инъекцииInto?
В Eclipse Collections forEach
и injectInto
являются внутренними итераторами, которые обеспечивают самые основные шаблоны итерации. Разработчик может использовать оба эти шаблона для выполнения большого количества итерационных задач. В качестве внутренних итераторов эти методы инкапсулируют детали реализации того, как перебирать элементы коллекции, и оставляют на усмотрение разработчика использование лямбда-выражения для указания поведения, которое следует применять к каждому элементу коллекции.
Шаблоны forEach
и injectInto
можно использовать в качестве строительных блоков для многих других шаблонов итераций (например, select
, reject
, collect
и т. д.). Оба шаблона перебирают коллекцию от начала до конца и выполняют некоторую операцию, которая получает каждый элемент коллекции в качестве параметра. Ни шаблон forEach
, ни шаблон injectInto
нельзя эффективно использовать для реализации любого из шаблонов короткого замыкания (например, detect
, anySatisfy
, allSatisfy
, noneSatisfy
).
Функциональность forEach
и injectInto
можно резюмировать следующим образом.
forEach
принимает один аргументProcedure
и возвращаетvoid
. Каждый элемент коллекции передается вProcedure
.injectInto
принимает два аргументаFunction2
. Значение вводится в качестве первого параметраFunction2
вместе с каждым элементом коллекции. Метод возвращает окончательный результат, который возвращаетFunction2
после обработки последнего элемента.
Существуют специализации обоих шаблонов. Шаблон injectInto
имеет более примитивную специализацию, чем forEach
, потому что injectInto
возвращает некоторый тип и может возвращать один из восьми примитивных типов или Object
. Шаблоны forEach
всегда возвращают void
.
Одна из вещей, которую injectInto
может делать, а forEach
не может, — это выполнять операцию без побочного эффекта. С forEach
всегда где-то возникает побочный эффект. В этом его цель — сделать так, чтобы с элементами коллекции происходили побочные эффекты. Может быть мутация, примененная к элементу коллекции, или мутация к переменной в области действия лямбды, или элемент печатается в System.out
и т. д.
С injectInto
можно выполнять операцию без побочных эффектов, потому что она вводит, возвращает и повторно вводит некоторый результат из двух аргументов Function
. Значение, которое вводится и возвращается, может быть неизменным, например, String
или Integer
, или примитивным значением.
Я собираюсь показать несколько примеров использования forEach
и injectInto
для решения подобных задач. Это должно начать прояснять, когда один из шаблонов более подходит или желателен, чем другой. Ответ на вопрос, является ли одно более желательным, чем другое, останется за вами, читатель. В некоторых случаях forEach
будет более удобочитаемым. Во многих случаях injectInto
будет более гибким, безопасным и/или производительным.
Ниже приведены примеры, которые я покажу:
- Считать
- Сумма
- Преобразовать в набор
Пример: подсчет
В следующих примерах я буду считать четные целые числа, содержащиеся в MutableList<Integer>
и MutableIntList
. Существует метод с именем count
, определенный для коллекций Object и Primitive в Eclipse Collections, который принимает Predicate
в качестве параметра. Цель этих примеров — показать некоторые варианты реализации count
с использованием шаблонов forEach
или injectInto
.
1. Подсчет: forEach (список объектов)
Мне пришлось создать изменяемый Counter
, чтобы записать общее количество четных значений. Я использовал тернарный оператор, чтобы написать упрощенное выражение if, чтобы определить, следует ли добавлять значение 1
или 0
к Counter
.
2. Подсчет: forEach (примитивный список)
Единственная разница между forEach
в списках объектов и примитивных списков заключается в том, что значения int
в примитиве List
не требуют распаковки, как это делают значения Integer
в объекте List
. Этот подход по-прежнему требует наличия действительно финального изменяемого объекта Counter
для увеличения лямбда-выражения.
3. Подсчет: injectInto (список объектов)
Эта реализация не имеет побочных эффектов. Мы начинаем с начального значения Integer
, равного 0
, и добавляем к этому результату 1
или 0
для каждого элемента, если он четный. Здесь много распаковки и автоупаковки, так как результат каждой функции распаковывает значения, а затем автоматически упаковывает результат.
4. Подсчет: injectInto (примитивный список)
В этом примере используется MutableIntList
, который не включает значения int
в List
. Результат injectInto
по-прежнему является объектом, поэтому результаты автоматически упаковываются для каждого элемента.
5. Счетчик: injectIntoInt (список объектов)
Специализация injectIntoInt
позволяет вводить в итерацию int
значение 0
и возвращать его в качестве результата. Здесь не будет автоматической упаковки результатов, но значения распаковываются, чтобы проверить, являются ли они четными. Это решение не имеет побочных эффектов и не приводит к автоматическому упаковыванию. Однако значения в целых числах List
по-прежнему заключены в рамку, поскольку это MutableList<Integer>
.
6. Счетчик: injectIntoInt (список примитивов)
Специализация injectIntoInt
на MutableIntList
позволяет реализовать подсчет без побочных эффектов без упаковки int
значений в Integer
объектов ни в списке, ни в вычислениях.
Пример: Сумма
В следующих примерах я суммирую целые числа, содержащиеся в MutableList<Integer>
и MutableIntList
. Существует метод с именем sumOfInt
, определенный для коллекций объектов, и просто sum
для коллекций Primitive в коллекциях Eclipse. Цель этих примеров — показать некоторые варианты реализации sum
с использованием шаблонов forEach
или injectInto
.
1. Сумма: forEach (список объектов)
Я использовал LongAdder
из JDK для накопления суммы. На LongAdder
есть метод add
, который принимает long
. Каждый объект Integer
в списке целых чисел распаковывается и преобразуется в long
.
2. Сумма: forEach (PrimitiveList)
Примитивная версия sum с forEach
по сути такая же, как и объектная версия, только без необходимости распаковки объектов Integer
при вызове add
на LongAdder
.
3. Сумма: injectInto (список объектов)
Вводя начальное значение Long.valueOf(0)
, возвращаемый тип injectInto
будет Long
. Я использовал ссылку на метод Long::sum
, которая принимает два значения long
в качестве параметров. Результат распаковывается из Long
в long
, а значения Integer
в List
распаковываются и преобразуются в long
.
4. Сумма: injectInto (примитивный список)
Примитивная версия sum с использованием injectInto
с использованием Long
в качестве введенного значения по существу такая же, как и объектная версия. Единственное отличие состоит в том, что не требуется распаковка Integer
объектов.
5. Сумма: injectIntoLong (список объектов)
Используя injectIntoLong
в списке объектов и вводя начальное long
значение 0L
, возвращаемый тип injectIntoLong
является примитивным long
. Я использовал ссылку на метод Long::sum
, которая принимает два значения long
в качестве параметров. Результат передается как long
, а значения Integer
в списке распаковываются и приводятся к long
.
6. Сумма: injectIntoLong (примитивный список)
Используя injectIntoLong
в примитивном списке и вводя начальное long
значение 0L
, возвращаемый тип injectIntoLong
является примитивом long
. Я использовал ссылку на метод Long::sum, который принимает два значения long
в качестве параметров. Результат передается как long, а значения int
в примитивном списке преобразуются в long
.
Пример: преобразовать в набор
В следующих примерах я добавлю целые числа, содержащиеся в MutableList<Integer>
или MutableIntList
, в MutableSet<Integer>
или MutableIntSet
. Я также покажу, как injectInto
можно использовать для добавления к ImmutableSet<Integer>
или ImmutableIntSet
.
В коллекциях Object и Primitive есть методы преобразования для преобразования из одного типа контейнера в другой (например, toSet
, toImmutableSet
). Цель этих примеров — показать некоторые варианты реализации toSet
и toImmutableSet
с использованием шаблонов forEach
или injectInto
.
1. В изменяемый набор: forEach (список объектов)
Я создаю MutableSet<Integer>
с именем set
для хранения элементов, которые я хочу передать из integers
. Затем, используя forEach, я передаю ссылку на метод set::add
. Этот код действительно прост и понятен.
2. В набор изменяемых целых чисел: forEach (список примитивов)
Примитивная версия по существу такая же, как и объектная версия, с целью set
равной MutableIntSet
.
3. В MutableSet: injectInto (список объектов)
Используя injectInto
, я ввожу MutableSet<Integer>
в качестве первого параметра Function2
. Я использую ссылку на метод MutableSet::with
, которая соответствует типам с двумя параметрами, требуемыми Function2
.
Первый тип параметра — MutableSet<Integer>
, а второй тип параметра — тип каждого элемента коллекции (Integer
).
Тип возвращаемого значения with
для любого MutableCollection
в коллекциях Eclipse — это сам тип коллекции, поскольку with
вызывает add
, а затем возвращает this
.
4. В MutableIntSet: injectInto (список примитивов)
Примитивная версия по существу такая же, как и объектная версия, с целью set
равной MutableIntSet
.
5. В ImmutableSet: injectInto (список объектов)
Преобразование идентификатора MutableList
в идентификатор ImmutableSet
— еще один пример, демонстрирующий гибкость injectInto
по сравнению с forEach
. ImmutableSet
не имеет методов мутации. Нет такого метода add
, как MutableSet
. Существует метод newWith
, который создает новый ImmutableSet
путем копирования исходного набора и добавления элемента для создания нового ImmutableSet
. Я передаю пустой ImmutableSet
в injectInto
вместе со ссылкой на метод ImmutableSet::newWith
.
6. В Immutable Int Set: injectInto (примитивный список)
Примитивная версия по существу такая же, как и объектная версия, с целью set
равной MutableIntSet
.
Дополнительные примеры forEach
Иногда вам может понадобиться индекс с вашим forEach
. К счастью, в коллекциях Eclipse для этой цели есть forEachWithIndex
. Вот блог, в котором описываются методы, предоставляющие индексы, включая forEachWithIndex
.
Существует также блог, в котором описывается специализированная форма forEach
по имени forEachInBoth
. Поскольку этот блог изначально был написан, я добавил forEachInBoth
непосредственно в контейнеры Object и примитивные контейнеры List
в коллекциях Eclipse.
Дополнительные примеры injectInto
Есть несколько блогов, в которых рассказывается о других примерах injectInto
, а также о специализированных формах injectInto
, таких как injectIntoKeyValue
. В следующем блоге рассматриваются некоторые общие примеры injectInto
.
В следующем блоге описаны и показаны примеры injectIntoKeyValue
, которые работают с картами и примитивными картами в коллекциях Eclipse. Блог был написан Эмили Робишо.
Некоторые заключительные мысли о forEach и injectInto
Я считаю, что очень легко начать думать о проблеме, используя forEach
. Если вам нужно вызвать какой-то основной побочный эффект, forEach
— это очень простой метод.
Иногда после того, как я написал что-то, используя forEach
, мне интересно, как бы выглядел тот же код, если бы я использовал injectInto
. Я буду использовать injectInto
, если я хочу иметь операцию без побочных эффектов или полностью контролировать любые побочные эффекты в контексте Function2
, который я передаю injectInto
.
Итак, вы должны использовать forEach
или injectInto
в коллекциях Eclipse? Как и во многих случаях, лучшее решение может быть субъективным, и я бы рекомендовал использовать тот код, который вам легче всего читать и понимать.
Если вы научитесь добавлять injectInto
к своему набору трюков, вы начнете удивляться его силе и гибкости, даже если его тайна так никогда и не исчезнет.
Источник для примеров
Want to Connect? I am a Project Lead and Committer for the Eclipse Collections OSS project at the Eclipse Foundation. Eclipse Collections is open for contributions. If you like the library, you can let us know by starring it on GitHub.