Construção de um Collector Personalizado
Implemente a interface Collector para criar uma agregação reutilizável voltada a um caso de uso específico do domínio.
Construção de um Collector Personalizado é uma aula grátis de Java Academy no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Java Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Java Academy inclui 4 aulas no total.
Por que criar um coletor personalizado?
Quando os coletores integrados não atendem ao seu domínio, implemente a interface Collector<T, A, R> para criar uma lógica de agregação reutilizável e combinável. T=entrada, A=acumulador mutável, R=resultado.
A interface Collector
Um coletor tem cinco componentes:
supplier()— cria o acumulador inicialaccumulator()— incorpora um elemento ao acumuladorcombiner()— combina dois acumuladores para fluxos paralelosfinisher()— transforma o acumulador no resultado finalcharacteristics()— fornece indicações para otimização
Fábrica Collector.of()
A maneira mais simples de criar um coletor personalizado é usar Collector.of() sem implementar a interface diretamente:
import java.util.stream.*;
// Collector that builds a comma-separated String
Collector<String, StringBuilder, String> csvCollector = Collector.of(
StringBuilder::new, // supplier
(sb, s) -> {
if (sb.length() > 0) sb.append(',');
sb.append(s);
}, // accumulator
(a, b) -> {
if (a.length() > 0 && b.length() > 0) a.append(',');
a.append(b); return a;
}, // combiner
StringBuilder::toString // finisher
);
String csv = Stream.of("Alice","Bob","Carol").collect(csvCollector);
System.out.println(csv); // Alice,Bob,CarolColetor personalizado: TopN
Crie um coletor que mantenha apenas os N elementos principais de acordo com um comparador:
import java.util.*;
import java.util.stream.*;
static <T> Collector<T, ?, List<T>> topN(int n, Comparator<T> comp) {
return Collector.of(
() -> new ArrayList<T>(),
(list, e) -> {
list.add(e);
list.sort(comp.reversed());
if (list.size() > n) list.remove(list.size() - 1);
},
(a, b) -> {
a.addAll(b);
a.sort(comp.reversed());
while (a.size() > n) a.remove(a.size() - 1);
return a;
},
Collector.Characteristics.UNORDERED
);
}Usando o coletor TopN
Aplique o coletor TopN personalizado a um fluxo:
List<Integer> top3 = Stream.of(5,2,9,1,7,3,8)
.collect(topN(3, Comparator.naturalOrder()));
System.out.println(top3); // [9, 8, 7]Sinalizadores de características
Três características opcionais ajustam a otimização do fluxo:
IDENTITY_FINISH— o finalizador é a identidade; ignore-oUNORDERED— a ordem do resultado não importaCONCURRENT— o acumulador pode ser chamado simultaneamente
Definir o sinalizador errado, como CONCURRENT sem segurança para uso simultâneo, causa erros.
Coletor personalizado: média acumulada
Mantenha uma média acumulada sem fazer uma segunda passagem:
record RunningAvg(long count, double sum) {
RunningAvg add(double v) { return new RunningAvg(count+1, sum+v); }
RunningAvg merge(RunningAvg o) { return new RunningAvg(count+o.count, sum+o.sum); }
double avg() { return count == 0 ? 0 : sum / count; }
}
Collector<Double, RunningAvg[], Double> avgCollector = Collector.of(
() -> new RunningAvg[]{new RunningAvg(0, 0)},
(a, v) -> a[0] = a[0].add(v),
(a, b) -> { a[0] = a[0].merge(b[0]); return a; },
a -> a[0].avg()
);
double avg = Stream.of(10.0, 20.0, 30.0).collect(avgCollector);
System.out.println(avg); // 20.0Implementando diretamente a interface Collector
Para ter mais controle, implemente Collector<T,A,R> como uma classe:
import java.util.function.*;
import java.util.stream.*;
class StringJoiningCollector implements Collector<String, List<String>, String> {
private final String delimiter;
StringJoiningCollector(String delimiter) { this.delimiter = delimiter; }
public Supplier<List<String>> supplier() { return ArrayList::new; }
public BiConsumer<List<String>, String> accumulator() { return List::add; }
public BinaryOperator<List<String>> combiner() {
return (a,b) -> { a.addAll(b); return a; };
}
public Function<List<String>, String> finisher() {
return list -> String.join(delimiter, list);
}
public Set<Characteristics> characteristics() { return Set.of(); }
}Comparando coletores personalizados e integrados
Antes de escrever um coletor personalizado, verifique se a composição de coletores integrados produz o mesmo resultado — eles já foram testados e otimizados:
// Instead of custom, compose:
Map<String, IntSummaryStatistics> stats = orders.stream().collect(
Collectors.groupingBy(Order::status,
Collectors.summarizingInt(Order::amount))
);
// This replaces many custom collector implementationsSegurança de uso simultâneo em coletores personalizados
Se a característica CONCURRENT estiver definida, o acumulador será chamado simultaneamente por várias linhas de execução. Nesse caso, o acumulador deverá ser seguro para uso simultâneo, por exemplo, usando ConcurrentHashMap ou AtomicLong. Sem CONCURRENT, o combiner trata o paralelismo com segurança.
Caso real: coletor de histograma
Agrupe valores numéricos em faixas dentro de um mapa de histograma:
Collector<Integer, Map<Integer, Long>, Map<Integer, Long>> histogram =
Collector.of(
LinkedHashMap::new,
(map, val) -> map.merge(val / 10 * 10, 1L, Long::sum),
(a, b) -> { b.forEach((k,v) -> a.merge(k,v,Long::sum)); return a; },
Collector.Characteristics.IDENTITY_FINISH
);
Map<Integer,Long> hist = Stream.of(5,15,25,12,8,22,18,3)
.collect(histogram);
hist.forEach((bucket, cnt) -> System.out.println(bucket+"-"+(bucket+9)+": "+cnt));Verificação rápida
Qual sinalizador de características do coletor indica que a função de finalização é a transformação identidade e pode ser ignorada?
Recapitulação: coletores personalizados
Principais pontos:
- A interface do coletor usa T como entrada, A como acumulador e R como resultado
- Use Collector.of() para uma implementação rápida sem criar uma classe
- supplier, accumulator, combiner, finisher e characteristics são as cinco partes
- As características de finalização por identidade, ausência de ordem e concorrência são sinalizadores de otimização
- Dê preferência à composição de coletores integrados; crie um personalizado apenas quando necessário
Perguntas Frequentes
A aula “Construção de um Collector Personalizado” é grátis?
Sim — o texto completo de “Construção de um Collector Personalizado” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Java Academy, atualize para CoddyKit PRO. O curso de Java Academy inclui 4 aulas no total.
O que vou aprender em “Construção de um Collector Personalizado”?
Implemente a interface Collector para criar uma agregação reutilizável voltada a um caso de uso específico do domínio. Você pratica Java Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Java Academy?
Nenhuma experiência prévia é necessária. Java Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.
Quanto tempo leva a aula “Construção de um Collector Personalizado”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Java Academy?
Sim. Cada aula de Java Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- groupingBy: Classificação de Elementos
- partitioningBy e Contagem
- toMap, joining e summarizing
- Construção de um Collector Personalizado