Изучите паттерны 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.