Узнайте, как перейти к типам Коллекции 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
, orpartition
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.