В этом посте приводится пример использования метода Collections2.transform() из библиотеки Google Guava.
Недавно я столкнулся с типичной ситуацией, когда имея коллекцию объектов мне понадобилось получить коллекцию, в которой бы содержались не сами объекты, а их идентификаторы. Сделать это просто: создать коллекцию, проитерироваться по исходной, вызвать метод getId() у каждого элемента и его результат сохранить в новую коллекцию.
Но есть ещё один способ сделать это. Если я не ошибаюсь, этот подход называют функциональным. В частности мы можем объявить метод, в котором инкапсулируем нашу логику (извлечение идентификатора) и вызвать метод transform(), которому передать наш исходный массив и наш метод. transform() сам сделает обход коллекции и вызовет наш метод для каждого элемента.
Какие плюсы у этого подхода? (обратите внимание, что я говорю не про конкретную реализацию, а про сам подход.)
Вот пример «в лоб», который иллюстрирует функциональный подход с использованием Collections2.transform():
В результате программа выведет
Первым аргументом transform() принимает исходную коллекцию, а вторым функтор (класс реализующий интерфейс Function) Нам необходимо реализовать метод apply() которому в качестве аргумента передаётся наша сущность, а он возвращает её идентификатор.
Несмотря на то, что локальный класс достаточно прост я всё же рекомендую вынести его в отдельную переменную. Во-первых, это повысит читаемость, т.к. переменной можно дать говорящее имя. Во-вторых, эту переменную можно использовать ещё раз.
Пример:
Теперь важное уточнение о том, как это работает, а вернее, реализовано. Вопреки вашим ожиданием, и в соответствии с документацией, transform() возвращает не новую коллекцию, а отображение существующей (live view). Это важная деталь, которую нужно иметь ввиду. В действительности это означает что:
Недавно я столкнулся с типичной ситуацией, когда имея коллекцию объектов мне понадобилось получить коллекцию, в которой бы содержались не сами объекты, а их идентификаторы. Сделать это просто: создать коллекцию, проитерироваться по исходной, вызвать метод getId() у каждого элемента и его результат сохранить в новую коллекцию.
Но есть ещё один способ сделать это. Если я не ошибаюсь, этот подход называют функциональным. В частности мы можем объявить метод, в котором инкапсулируем нашу логику (извлечение идентификатора) и вызвать метод transform(), которому передать наш исходный массив и наш метод. transform() сам сделает обход коллекции и вызовет наш метод для каждого элемента.
Какие плюсы у этого подхода? (обратите внимание, что я говорю не про конкретную реализацию, а про сам подход.)
- Инкапсуляция алгоритма
- Как следствие первого пункта повышается возможность этот код использовать повторно
- Позволяет распараллелиться при обработке данных (использование цикла вынуждает нас быть последовательными, в случае же использования transform() у нас такого ограничения нет)
Вот пример «в лоб», который иллюстрирует функциональный подход с использованием Collections2.transform():
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Arrays; | |
import java.util.Collection; | |
import com.google.common.base.Function; | |
import com.google.common.collect.Collections2; | |
class Entity { | |
private final Integer id; | |
public Entity(final Integer id) { | |
this.id = id; | |
} | |
public Integer getId() { | |
return id; | |
} | |
} | |
public class GuavaTransform { | |
public static void main(final String [] args) { | |
Collection<Entity> entities = Arrays.asList( | |
new Entity(1), | |
new Entity(2), | |
new Entity(3) | |
); | |
Collection<Integer> ids = Collections2.transform( | |
entities, | |
new Function<Entity, Integer>() { | |
@Override | |
public Integer apply(final Entity entity) { | |
return entity.getId(); | |
} | |
} | |
); | |
System.out.println(ids); | |
} | |
} |
В результате программа выведет
[1, 2, 3]
Первым аргументом transform() принимает исходную коллекцию, а вторым функтор (класс реализующий интерфейс Function) Нам необходимо реализовать метод apply() которому в качестве аргумента передаётся наша сущность, а он возвращает её идентификатор.
Несмотря на то, что локальный класс достаточно прост я всё же рекомендую вынести его в отдельную переменную. Во-первых, это повысит читаемость, т.к. переменной можно дать говорящее имя. Во-вторых, эту переменную можно использовать ещё раз.
Пример:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static Function<Entity, Integer> INVOKE_GET_ID = | |
new Function<Entity, Integer>() { | |
@Override | |
public Integer apply(final Entity entity) { | |
return entity.getId(); | |
} | |
}; | |
... | |
Collection<Integer> ids = | |
Collections2.transform(entities, INVOKE_GET_ID); |
Теперь важное уточнение о том, как это работает, а вернее, реализовано. Вопреки вашим ожиданием, и в соответствии с документацией, transform() возвращает не новую коллекцию, а отображение существующей (live view). Это важная деталь, которую нужно иметь ввиду. В действительности это означает что:
- результат вычисляется тогда, когда вы к нему обратитесь (и даже более того: при каждом обращении он будет вычисляться снова и снова, поэтому если вам нужно использовать результат несколько раз, то лучше его закэшировать)
- изменения в исходной коллекции будут влиять на результат (иными словами результат вызова transform() до и после добавления нового элемента в исходную коллекцию будет отличаться)
- при попытке добавить элемент в результирующую коллекцию вы получите UnsupportedOperationException (т.е. добавить вы ничего не сможете, а вот удалить — запросто)
В Guava также есть Iterables.transform() и Lists.transform(), которые можно применять к соответствующим типам.
Также имеется несколько готовых реализаций интерфейса Function в классе Functions. Например:
- constant() всегда возвращает определённое значение
- toStringFunction вызывает toString() на каждом аргументе
- compose() позволяет объединять в цепочку несколько функторов
Комментариев нет:
Отправить комментарий