После выхода java 8 появилась возможность писать в pipeline стиле. Т.е. создавать последовательность операций которые будут обрабатывать элементы из некоторого источника, будь то коллекция, бесконечная и конечная генерирующая функция, строки из файла и пр. Это тема достаточно обширная и она не будет освещена в данном топике.
Расскажу про collector’ы. Это такие объекты с помощью которых, можно привести результат обработки элементов к определенному виду. Существует несколько встроенных, в классе java.util.stream.Collectors
. Например, там есть toList
, он создает список из элементов потока. Есть и более сложные, например, groupingBy
, группирующий элементы по определенному критерию. Мы же напишем свой, который будет группировать строки по начальным буквам.
И так, для начала, ознакомимся с интерфейсом java.util.stream.Collector
от которого будет наследоваться:
interface Collector<T,A,R> {
Supplier<A> supplier()
BiConsumer<A,T> acumulator()
BinaryOperator<A> combiner()
Function<A,R> finisher()
Set<Characteristics> characteristics()
}
В нем пять функций. Некоторые из них не обязательно реализовывать, достаточно оставить пустое тело, но об этом позже:
supplier
создает вспомогательный объект производящий основные манипуляции над элементами.accumulator
функция суммирующая предыдущий результат операций с текущий элементом. Принимает в качестве аргумента вспомогательный объект и следующий элемент потока.combiner
— если стрим работает в многопоточном режиме, то эта функция служит для агрегирования результатов выполненных в разных потоках.finisher
возвращает итоговый результат операций.characteristics
возвращает характеристики реализуемого класса. Существует несколько характеристик, их стоит перечислить:- CONCURRENT возможность выполнения в разных потоках, если она указана, то функция combiner должна иметь осмысленную реализацию.
- UNORDERED указывает, что результат выполнения не сохраняет порядок входящих элементов, т.е. если мы на вход получим отсортированный поток, то на выходе будет не отсортированный, или имеющий измененный порядок.
- IDENTITY_FINISH указывает что функция finisher может быть пропущена, перед получением итогового результата.
Приступим к реализации. Для того, чтобы было более понятно, начнем с вспомогательного класса.
public final class CustomCollectorBuilder {
private final Map<Character, Set<String>> set;
public CustomCollectorBuilder() {
this.set = new HashMap<>();
}
public void add(String str) {
Set<String> subSet = getSubset(str);
if (subSet == null)
return;
subSet.add(str);
}
public CustomCollectorBuilder addAll(Map<Character, Set<String>> collection) {
for (Map.Entry<Character, Set<String>> entry : collection.entrySet()) {
Set<String> subSet = getSubset(entry.getKey());
subSet.addAll(entry.getValue());
}
return this;
}
public Map<Character, Set<String>> build() {
return this.set;
}
private Set<String> getSubset(String str) {
if (str == null || str.length() == 0)
return null;
return getSubset(str.charAt(0));
}
private Set<String> getSubset(Character key) {
Set<String> subSet = set.get(key);
if (subSet == null) {
subSet = new HashSet<>();
set.put(key, subSet);
}
return subSet;
}
}
Думаю, здесь все очевидно и трудностей в понимании кода не возникнет.
Теперь сам коллектор. Пааарааам.
public final class CustomCollector implements Collector<String, CustomCollector.CustomCollectorBuilder, Map<Character, Set<String>>> {
@Override
public Supplier<CustomCollectorBuilder> supplier() {
return CustomCollectorBuilder::new;
}
@Override
public BiConsumer<CustomCollectorBuilder, String> accumulator() {
return CustomCollectorBuilder::add;
}
@Override
public BinaryOperator<CustomCollectorBuilder> combiner() {
return (first, second) -> first.addAll(second.build());
}
@Override
public Function<CustomCollectorBuilder, Map<Character, Set<String>>> finisher() {
return CustomCollectorBuilder::build;
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.CONCURRENT, Characteristics.UNORDERED);
}
}
Как мы видим по характеристикам, коллектор работает в многопоточной среде и результат его работы не сохраняет порядок элементов.
Собственно все. Теперь можно использовать, например, так:
Collection<String> strings = Arrays.asList("apple", "orange", "banana", "pear", "peach");
Map<Character, Set<String>> result = strings
.stream()
.collect(new CustomCollector());
for (Character character : result.keySet())
System.out.println(result.get(character));
Результат выполнения будет следующий:
[pear, peach]
[apple]
[banana]
[orange]