Статьи

Коллекционеры Java 8 для коллекций гуавы

Автор нашей собственной  Мачей Миклас , Lean Java Expert  @comSysto 

Java 8 поставляется с потоковым API, он разделяет обработку данных на две фазы: промежуточные операции и терминальные операции. Терминальная операция может иметь несколько различных форм, и я хотел бы сосредоточиться на сокращении до коллекций, особенно до неизменных коллекций Guava. Для работы терминала требуется сборщик, который будет собирать данные и возвращать их в виде требуемой структуры, но Guava не предоставляет такой сборщик. Чтобы создать коллекцию Guava из потока, мы должны сначала уменьшить результат потока во временную коллекцию и затем передать его:

import static java.util.stream.Collectors.collectingAndThen;

import static java.util.stream.Collectors.toList;

import com.google.common.collect.ImmutableSortedSet;

...stream.map(...).filter(....).

collect(collectingAndThen(Collectors.toList(),

ImmutableSortedSet::copyOf));

Сокращение нашего потока хранит результаты во временном списке (Collectors.toList ()). По завершении потоковой обработки функция финишера преобразует содержимое этого списка в коллекцию Guava (ImmutableSortedSet :: copyOf).

Проблема этого подхода заключается в том, что… у нас есть дополнительный цикл преобразования и два массива в памяти (List и Builder). Этого можно было бы избежать, если бы у нас был коллектор, построенный на основе Guava Builder. Так…. Я реализовал один, как только мы его используем, приведенный выше код можно упростить до такой формы:

import static org.cyclop.common.Gullectors.toNaturalImmutableSortedSet;
import com.google.common.collect.ImmutableSortedSet;
 
...stream.map(...).filter(....).collect(
    toNaturalImmutableSortedSet());

Код прост, давайте сосредоточимся на реализации #toNaturalImmutableSortedSet ()

public static <T extends Comparable<?>> Collector<T, ?,
     ImmutableSortedSet<T>> toNaturalImmutableSortedSet() {
 
  Supplier<ImmutableSortedSet.Builder<T>> supplier =
     ImmutableSortedSet::naturalOrder;
 
  BiConsumer<ImmutableSortedSet.Builder<T>, T> accumulator =
     (b, v) -> b.add(v);
 
  BinaryOperator<ImmutableSortedSet.Builder<T>> combiner =
     (l, r) -> l.addAll(r.build());
 
  Function<ImmutableSortedSet.Builder<T>,
    ImmutableSortedSet<T>> finisher =
      ImmutableSortedSet.Builder::build;
 
  return Collector.of(supplier, accumulator,
     combiner, finisher);
}

Наш коллектор создается фабричным методом  Collector #,  который принимает четыре аргумента:

  • # Поставщик  — эта функция будет вызываться только один раз , чтобы создать структуру , которая будет собирать результаты потоков — в нашем случае это  полный редактор  из  ImmutableSortedSet
  • # аккумулятор  — предоставляет функцию, которая будет выполняться для каждого элемента, который достигает терминальной операции, то есть каждый элемент, который прошел через поток и должен быть собран для возврата. В нашем случае мы предоставляем функцию, которая будет выполнять  #add (v)  в  Builder,  которая была указана в первом аргументе (# supplier )
  • # combiner  — этот пример не будет использоваться в нашем примере, но он необходим для обработки параллельных потоков, он будет использоваться для их объединения
  • # Финишер  — это последний шаг, и он будет выполнен после обработки потока. Элементы, возвращаемые потоком, содержатся в Builder (# supplier ), и на этом последнем этапе мы вызываем для него  метод # build () , что приводит к  ImmutableSortedSet  !

На основе этого шаблона мы можем реализовать другие сборщики:

public static <T> Collector<T, ?, ImmutableList<T>>
    toImmutableList() {
 
    Supplier<ImmutableList.Builder<T>> supplier =
        ImmutableList.Builder::new;
 
    BiConsumer<ImmutableList.Builder<T>, T> accumulator =
        (b, v) -> b.add(v);
 
    BinaryOperator<ImmutableList.Builder<T>> combiner =
        (l, r) -> l.addAll(r.build());
 
    Function<ImmutableList.Builder<T>, ImmutableList<T>> finisher =
        ImmutableList.Builder::build;
 
    return Collector.of(supplier, accumulator, combiner, finisher);
}
 
public static <T> Collector<T, ?, ImmutableSet<T>>
    toImmutableSet() {
 
    Supplier<ImmutableSet.Builder<T>> supplier =
        ImmutableSet.Builder::new;
 
    BiConsumer<ImmutableSet.Builder<T>, T> accumulator =
        (b, v) -> b.add(v);
 
    BinaryOperator<ImmutableSet.Builder<T>> combiner =
        (l, r) -> l.addAll(r.build());
 
    Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher =
        ImmutableSet.Builder::build;
 
    return Collector.of(supplier, accumulator, combiner, finisher);
}
 
public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>>
    toImmutableMap(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends V> valueMapper) {
 
    Supplier<ImmutableMap.Builder<K, V>> supplier =
        ImmutableMap.Builder::new;
 
    BiConsumer<ImmutableMap.Builder<K, V>, T> accumulator =
         (b, t) -> b.put(keyMapper.apply(t), valueMapper.apply(t));
 
    BinaryOperator<ImmutableMap.Builder<K, V>> combiner =
         (l, r) -> l.putAll(r.build());
 
    Function<ImmutableMap.Builder<K, V>, ImmutableMap<K, V>> finisher =
       ImmutableMap.Builder::build;
 
    return Collector.of(supplier, accumulator, combiner, finisher);
}

Наконец, вот исходный код:  Gullectors.java  и модульные тесты: TestGullectors.java

Приятного сбора!