четверг, 27 октября 2011 г.

Google Guava: Collections2.transform()

В этом посте приводится пример использования метода Collections2.transform() из библиотеки Google Guava.

 
Недавно я столкнулся с типичной ситуацией, когда имея коллекцию объектов мне понадобилось получить коллекцию, в которой бы содержались не сами объекты, а их идентификаторы. Сделать это просто: создать коллекцию, проитерироваться по исходной, вызвать метод getId() у каждого элемента и его результат сохранить в новую коллекцию.

Но есть ещё один способ сделать это. Если я не ошибаюсь, этот подход называют функциональным. В частности мы можем объявить метод, в котором инкапсулируем нашу логику (извлечение идентификатора) и вызвать метод transform(), которому передать наш исходный массив и наш метод. transform() сам сделает обход коллекции и вызовет наш метод для каждого элемента.

Какие плюсы у этого подхода? (обратите внимание, что я говорю не про конкретную реализацию, а про сам подход.)

  • Инкапсуляция алгоритма
  • Как следствие первого пункта повышается возможность этот код использовать повторно
  • Позволяет распараллелиться при обработке данных (использование цикла вынуждает нас быть последовательными, в случае же использования transform() у нас такого ограничения нет)

Вот пример «в лоб», который иллюстрирует функциональный подход с использованием Collections2.transform():



В результате программа выведет

[1, 2, 3]

Первым аргументом transform() принимает исходную коллекцию, а вторым функтор (класс реализующий интерфейс Function) Нам необходимо реализовать метод apply() которому в качестве аргумента передаётся наша сущность, а он возвращает её идентификатор.

Несмотря на то, что локальный класс достаточно прост я всё же рекомендую вынести его в отдельную переменную. Во-первых, это повысит читаемость, т.к. переменной можно дать говорящее имя. Во-вторых, эту переменную можно использовать ещё раз.

Пример:



Теперь важное уточнение о том, как это работает, а вернее, реализовано. Вопреки вашим ожиданием, и в соответствии с документацией, transform() возвращает не новую коллекцию, а отображение существующей (live view). Это важная деталь, которую нужно иметь ввиду. В действительности это означает что:

  • результат вычисляется тогда, когда вы к нему обратитесь (и даже более того: при каждом обращении он будет вычисляться снова и снова, поэтому если вам нужно использовать результат несколько раз, то лучше его закэшировать)
  • изменения в исходной коллекции будут влиять на результат (иными словами результат вызова transform() до и после добавления нового элемента в исходную коллекцию будет отличаться)
  • при попытке добавить элемент в результирующую коллекцию вы получите UnsupportedOperationException (т.е. добавить вы ничего не сможете, а вот удалить — запросто)


В Guava также есть Iterables.transform() и Lists.transform(), которые можно применять к соответствующим типам.

Также имеется несколько готовых реализаций интерфейса Function в классе Functions. Например:

  • constant() всегда возвращает определённое значение
  • toStringFunction вызывает toString() на каждом аргументе
  • compose() позволяет объединять в цепочку несколько функторов

Комментариев нет:

Отправить комментарий