Узнайте, как перейти к типам Коллекции Eclipse, используя Collectors2 с любым Stream Java.

Анатомия коллекционера

Одним из многих замечательных дополнений к Java 8 стал интерфейс с именем Collector . Collector можно использовать с методом collect в Stream интерфейсе. Метод collect позволит вам уменьшить Stream до любого типа, который вы хотите. Java 8 включала набор стандартных Collector реализаций, которые являются частью служебного класса Collectors. Коллекции Eclipse включает еще один набор Collector реализаций, возвращающих типы Коллекций Eclipse. Имя служебного класса в Eclipse Collections - Collectors2.

Так что же такое Collector? Давайте посмотрим на интерфейс, чтобы узнать. На Collector есть пять общедоступных методов экземпляра.

  • поставщик → Supplier<A>
  • аккумулятор → BiConsumer<A, T>
  • комбайнер → BinaryOperator<A>
  • финишер → Function<A, R>
  • характеристики → Set<Characteristics>Enum (СОВПАДАЮЩИЙ, НЕЗАКАЗАННЫЙ, ИДЕНТИЧНОСТЬ_КОНЧАТЫЙ)

На Collector также есть два статических метода of, которые можно использовать для простого создания ваших собственных Collector реализаций.

Итак, давайте посмотрим, как мы можем создать Collector, чтобы лучше понять, для чего используются эти отдельные компоненты.

@Test
public void collector()
{
    Collector<String, Set<String>, Set<String>> toCOWASet =
            Collector.of(
                    HashSet::new,              // supplier
                    Set::add,                  // accumulator
                    (set1, set2) -> {          // combiner
                        set1.addAll(set2);
                        return set1;
                    },
                    CopyOnWriteArraySet::new); // finisher
    List<String> strings = Arrays.asList("a", "b", "c");
    Set<String> set =
            strings.stream().collect(toCOWASet);
    Assert.assertEquals(new HashSet<>(strings), set);
}

Здесь я использую статический метод of, который принимает пять параметров. Я оставляю здесь последний параметр var arg’d для характеристик пустым. Здесь поставщик создает новый HashSet. аккумулятор используется для указания, какую операцию применить к объекту, созданному с помощью поставщика. Элементы в Stream будут переданы add методу Set. объединитель используется, чтобы указать, как коллекции должны быть объединены в случае, когда используется parallelStream. Я не могу использовать здесь ссылку на метод для комбайнера, потому что одна из коллекций должна быть возвращена, а метод addAll на Collection возвращает boolean. Наконец, финишер доводит окончательный результат до CopyOnWriteArraySet.

Создание многоразового коллектора

Приведенный выше пример Collector был бы не очень полезен, если бы его нужно было встроить непосредственно в код, поскольку он довольно подробный. Было бы гораздо полезнее, если бы он мог обрабатывать любой тип, а не только String. Это можно легко сделать, переместив конструкцию этого Collector в статический метод и присвоив ему имя, подобное CopyOnWriteArraySet.

public static <T> Collector<T, ?, Set<T>> toCopyOnWriteArraySet()
{
    return Collector.<T, Set<T>, Set<T>>of(
            HashSet::new,              // supplier
            Set::add,                  // accumulator
            (set1, set2) -> {          // combiner
                set1.addAll(set2);
                return set1;
            },
            CopyOnWriteArraySet::new,  // finisher
            Collector.Characteristics.UNORDERED); // characteristics
}

@Test
public void reusableCollector()
{
    List<String> strings = Arrays.asList("a", "b", "c");
    Set<String> set1 =
            strings.stream().collect(toCopyOnWriteArraySet());
    Verify.assertInstanceOf(CopyOnWriteArraySet.class, set1);
    Assert.assertEquals(new HashSet<>(strings), set1);

    List<Integer> integers = Arrays.asList(1, 2, 3);
    Set<Integer> set2 =
            integers.stream().collect(toCopyOnWriteArraySet());
    Verify.assertInstanceOf(CopyOnWriteArraySet.class, set2);
    Assert.assertEquals(new HashSet<>(integers), set2);
}

Теперь я создал многоразовый Collector, который можно использовать с потоком любого типа. Я дополнительно указал Collector.Characteristics в многоразовой реализации. Эти характеристики могут использоваться методом Stream collect для оптимизации реализации сокращения. Поскольку я накапливаю Set, который в данном случае неупорядочен, имеет смысл использовать характеристику UNORDERED.

Фильтрация с помощью коллекторов2

Для фильтрации с Collectors2 вам потребуются три вещи:

  • A select, reject, or partition Collector
  • A Predicate
  • Целевая коллекция Supplier

Вот примеры использования select, reject и partition с Collectors2.

@Test
public void filtering()
{
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    Predicate<Integer> evens = i -> i % 2 == 0;

    MutableList<Integer> selectedList = list.stream().collect(
            Collectors2.select(evens, Lists.mutable::empty));
    MutableSet<Integer> selectedSet = list.stream().collect(
            Collectors2.select(evens, Sets.mutable::empty));

    MutableList<Integer> rejectedList = list.stream().collect(
            Collectors2.reject(evens, Lists.mutable::empty));
    MutableSet<Integer> rejectedSet = list.stream().collect(
            Collectors2.reject(evens, Sets.mutable::empty));

    PartitionList<Integer> partitionList = list.stream().collect(
            Collectors2.partition(evens, PartitionFastList::new));
    PartitionSet<Integer> partitionSet = list.stream().collect(
            Collectors2.partition(evens, PartitionUnifiedSet::new));

    Assert.assertEquals(selectedList, partitionList.getSelected());
    Assert.assertEquals(rejectedList, partitionList.getRejected());

    Assert.assertEquals(selectedSet, partitionSet.getSelected());
    Assert.assertEquals(rejectedSet, partitionSet.getRejected());
}

Преобразование с помощью Collectors2

Есть несколько методов, которые обеспечивают различные преобразования с использованием Collectors2. Самая простая трансформация доступна через метод collect. Чтобы использовать collect, вам понадобятся две вещи:

  • A Function
  • Целевая коллекция Supplier

Другими трансформирующими коллекторами, которые я продемонстрирую ниже, являются makeString, zip, zipWithIndex, chunk и flatCollect.

@Test
public void transforming()
{
    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
    MutableList<String> strings = list.stream().collect(
            Collectors2.collect(Object::toString,
                    Lists.mutable::empty));

    String string = list.stream().collect(Collectors2.makeString());

    Assert.assertEquals(string, strings.makeString());

    MutableList<Pair<Integer, String>> zipped =
            list.stream().collect(Collectors2.zip(strings));

    Assert.assertEquals(Tuples.pair(1, "1"), zipped.getFirst());
    Assert.assertEquals(Tuples.pair(5, "5"), zipped.getLast());

    MutableList<ObjectIntPair<Integer>> zippedWithIndex =
            list.stream().collect(Collectors2.zipWithIndex());

    Assert.assertEquals(
            PrimitiveTuples.pair(Integer.valueOf(1), 0),
            zippedWithIndex.getFirst());
    Assert.assertEquals(
            PrimitiveTuples.pair(Integer.valueOf(5), 4),
            zippedWithIndex.getLast());

    MutableList<MutableList<Integer>> chunked =
            list.stream().collect(Collectors2.chunk(2));

    Assert.assertEquals(
            Lists.mutable.with(1, 2), chunked.getFirst());
    Assert.assertEquals(
            Lists.mutable.with(5), chunked.getLast());

    MutableList<Integer> flattened = chunked.stream().collect(
            Collectors2.flatCollect(e -> e, Lists.mutable::empty));

    Assert.assertEquals(list, flattened);
}

Преобразование с помощью Collectors2

В Collectors2 доступны два набора реализаций преобразования Collector. Один набор преобразуется в MutableCollection типы. Другой преобразуется в ImmutableCollection типов.

Сборщики преобразовываются в изменяемые коллекции

@Test
public void convertingToMutable()
{
    List<Integer> source = Arrays.asList(2, 1, 4, 3, 5);
    MutableBag<Integer> bag = source.stream().collect(
            Collectors2.toBag());
    MutableSortedBag<Integer> sortedBag = source.stream().collect(
            Collectors2.toSortedBag());
    Assert.assertEquals(
            Bags.mutable.with(1, 2, 3, 4, 5), bag);
    Assert.assertEquals(
            SortedBags.mutable.with(1, 2, 3, 4, 5), sortedBag);

    MutableSet<Integer> set = source.stream().collect(
            Collectors2.toSet());
    MutableSortedSet<Integer> sortedSet = source.stream().collect(
            Collectors2.toSortedSet());
    Assert.assertEquals(
            Sets.mutable.with(1, 2, 3, 4, 5), set);
    Assert.assertEquals(
            SortedSets.mutable.with(1, 2, 3, 4, 5), sortedSet);

    MutableList<Integer> list = source.stream().collect(
            Collectors2.toList());
    MutableList<Integer> sortedList = source.stream().collect(
            Collectors2.toSortedList());
    Assert.assertEquals(
            Lists.mutable.with(2, 1, 4, 3, 5), list);
    Assert.assertEquals(
            Lists.mutable.with(1, 2, 3, 4, 5), sortedList);

    MutableMap<String, Integer> map =
            source.stream().limit(4).collect(
                    Collectors2.toMap(Object::toString, e -> e));
    Assert.assertEquals(
            Maps.mutable.with("2", 2, "1", 1, "4", 4, "3", 3), 
            map);

    MutableBiMap<String, Integer> biMap =
            source.stream().limit(4).collect(
                    Collectors2.toBiMap(Object::toString, e -> e));
    Assert.assertEquals(
            BiMaps.mutable.with("2", 2, "1", 1, "4", 4, "3", 3), 
            biMap);
}

Сборщики преобразовываются в неизменяемые коллекции

@Test
public void convertingToImmutable()
{
    List<Integer> source = Arrays.asList(2, 1, 4, 3, 5);
    ImmutableBag<Integer> bag = source.stream().collect(
            Collectors2.toImmutableBag());
    ImmutableSortedBag<Integer> sortedBag = source.stream().collect(
            Collectors2.toImmutableSortedBag());
    Assert.assertEquals(
            Bags.immutable.with(1, 2, 3, 4, 5), bag);
    Assert.assertEquals(
            SortedBags.immutable.with(1, 2, 3, 4, 5), sortedBag);

    ImmutableSet<Integer> set = source.stream().collect(
            Collectors2.toImmutableSet());
    ImmutableSortedSet<Integer> sortedSet = source.stream().collect(
            Collectors2.toImmutableSortedSet());
    Assert.assertEquals(
            Sets.immutable.with(1, 2, 3, 4, 5), set);
    Assert.assertEquals(
            SortedSets.immutable.with(1, 2, 3, 4, 5), sortedSet);

    ImmutableList<Integer> list = source.stream().collect(
            Collectors2.toImmutableList());
    ImmutableList<Integer> sortedList = source.stream().collect(
            Collectors2.toImmutableSortedList());
    Assert.assertEquals(
            Lists.immutable.with(2, 1, 4, 3, 5), list);
    Assert.assertEquals(
            Lists.immutable.with(1, 2, 3, 4, 5), sortedList);

    ImmutableMap<String, Integer> map =
            source.stream().limit(4).collect(
                    Collectors2.toImmutableMap(
                            Object::toString, e -> e));
    Assert.assertEquals(
            Maps.immutable.with("2", 2, "1", 1, "4", 4, "3", 3),
            map);

    ImmutableBiMap<String, Integer> biMap =
            source.stream().limit(4).collect(
                    Collectors2.toImmutableBiMap(
                            Object::toString, e -> e));
    Assert.assertEquals(
            BiMaps.immutable.with("2", 2, "1", 1, "4", 4, "3", 3),
            biMap);
}

Реализации Collector, преобразующие в ImmutableCollection типы, используют завершитель для преобразования из изменяемого контейнера в неизменяемый контейнер. Вот пример реализации Collector для toImmutableList().

public static <T> Collector<T, ?, ImmutableList<T>> toImmutableList()
{
    return Collector.<T, MutableList<T>, ImmutableList<T>>of(
            Lists.mutable::empty,      // supplier
            MutableList::add,          // accumulator
            MutableList::withAll,      // combiner
            MutableList::toImmutable,  // finisher
            EMPTY_CHARACTERISTICS);    // characteristics
}

Finisher - это MutableList::toImmutable справочник по методу. Это последний шаг, который преобразует MutableCollection с результатами в ImmutableCollection.

Eclipse Collections API против Collectors2

Я предпочитаю всегда использовать API Eclipse Collections напрямую, если могу. Если мне нужно работать с типом JDK Collection или если мне дан только Stream, я буду использовать Collectors2. Как вы можете видеть в приведенных выше примерах, Collectors2 - это естественный путь к типам коллекций Eclipse и их функциональным, плавным, дружелюбным и забавным API.

Ознакомьтесь с этой презентацией, чтобы узнать больше о происхождении, дизайне и эволюции Eclipse Collections API.

Я руководитель проекта и ответственный за проект OSS Коллекции Eclipse в Eclipse Foundation. Eclipse Collections открыта для пожертвований. Если вам нравится библиотека, вы можете сообщить нам об этом, отметив ее на GitHub.