Trabalhando com mapMulti() – Transforme seus streams sem neuras
E aí, dev? Tudo certo?
Se você está no JDK 16 ou superior, a Stream API ganhou um novo brinquedo: o método mapMulti(). Ele parece meio estranho no começo, mas juro que depois que você entende, começa a usar até pra fazer café ☕.
A ideia é simples: ele permite mapear cada elemento do stream para zero, um ou vários elementos novos, tudo dentro de um único BiConsumer. Chega de ficar encadeando filter() e map() que nem louco? Bom, nem sempre, mas em muitos casos sim. Vamos ver na prática.
🔍 Exemplo clássico vs mapMulti()
Imagina que você tem uma lista de notas de alunos e quer filtrar só as notas maiores ou iguais a 7 e aplicar um bônus de 10% em cada uma.
Modo tradicional (filter + map):
List<Double> notas = List.of(5.5, 8.0, 6.0, 9.5, 7.0, 4.0);
List<Double> notasComBonus = notas.stream()
.filter(n -> n >= 7)
.map(n -> n * 1.10)
.collect(Collectors.toList());
// Resultado: [8.8, 10.45, 7.7]
Agora com mapMulti() a gente faz a mesma coisa com uma só operação:
List<Double> notasComBonusMM = notas.stream()
.<Double>mapMulti((nota, consumer) -> {
if (nota >= 7) {
consumer.accept(nota * 1.10);
}
})
.collect(Collectors.toList());
O if faz o papel do filter(), e o accept() aplica o map().
Perceba o .<Double> antes do mapMulti – isso é um type-witness (o compilador chato pediu). Sem ele o código não compila. Um pequeno preço a pagar.
🧮 Trabalhando com tipos primitivos – mapMultiToDouble()
Se a sua ideia é somar essas notas com bônus, melhor usar a versão especializada para double:
double somaComBonus = notas.stream()
.mapMultiToDouble((nota, consumer) -> {
if (nota >= 7) {
consumer.accept(nota * 1.10);
}
})
.sum();
System.out.println(somaComBonus); // 26.95
Sem type-witness, sem firula. E se depois você quiser voltar a ter um Stream<Double>, é só usar boxed() ou mapToObj():
Stream<Double> streamBonus = notas.stream()
.mapMultiToDouble((nota, consumer) -> {
if (nota >= 7) consumer.accept(nota * 1.10);
})
.boxed();
📦 flatMap() vs mapMulti() – o duelo do one‑to‑many
Vamos para um exemplo mais real. Temos categorias de produtos, e cada categoria tem vários produtos. Queremos gerar uma lista simples de ProdutoInfo contendo o nome da categoria + o nome do produto.
class Categoria {
private String nome;
private List<Produto> produtos;
// getters...
}
class Produto {
private String nome;
private double preco;
// getters...
}
class ProdutoInfo {
private String categoria;
private String produto;
// construtor, getters...
}
Com flatMap() (tradicional)
List<ProdutoInfo> listaFlat = categorias.stream()
.flatMap(cat -> cat.getProdutos().stream()
.map(prod -> new ProdutoInfo(cat.getNome(), prod.getNome()))
)
.collect(Collectors.toList());
O problema aqui é que, para cada categoria, criamos um stream intermediário (o cat.getProdutos().stream()). Se forem muitas categorias, isso pesa.
Com mapMulti() (mais enxuto)
List<ProdutoInfo> listaMM = categorias.stream()
.<ProdutoInfo>mapMulti((categoria, consumer) -> {
for (Produto p : categoria.getProdutos()) {
consumer.accept(new ProdutoInfo(categoria.getNome(), p.getNome()));
}
})
.collect(Collectors.toList());
Sem streams intermediários, só um laço for simples. Mais performático e, na minha opinião, mais fácil de ler.
🎯 Com filtro adicional – só produtos acima de R$ 50
Se você quiser só produtos com preço maior que 50, a vantagem do mapMulti() fica ainda mais clara:
List<ProdutoInfo> listaCaros = categorias.stream()
.<ProdutoInfo>mapMulti((categoria, consumer) -> {
for (Produto p : categoria.getProdutos()) {
if (p.getPreco() > 50) {
consumer.accept(new ProdutoInfo(categoria.getNome(), p.getNome()));
}
}
})
.collect(Collectors.toList());
Aqui você evita um filter() extra e mantém tudo no mesmo lugar. E repara: cada categoria tem poucos produtos (geralmente), então se encaixa perfeitamente na recomendação oficial:
Use
mapMulti()quando for substituir cada elemento do stream por um pequeno número (possivelmente zero) de elementos.
🧠 Abordagem imperativa? Também vale!
Sabe aquele método que já faz a lógica toda e recebe um Consumer? Então... você pode passar ele direto pro mapMulti().
Adicione na classe Categoria:
public void produtosCaros(Consumer<ProdutoInfo> consumer, double limite) {
for (Produto p : this.produtos) {
if (p.getPreco() > limite) {
consumer.accept(new ProdutoInfo(this.nome, p.getNome()));
}
}
}
Agora seu stream fica limpo igual água de coco:
List<ProdutoInfo> resultado = categorias.stream()
.<ProdutoInfo>mapMulti(cat -> cat.produtosCaros(consumer, 50))
.collect(Collectors.toList());
Ou usando method reference (ainda mais lindo):
.<ProdutoInfo>mapMulti(cat -> cat.produtosCaros(consumer, 50))
// ou, se o limite for fixo:
.<ProdutoInfo>mapMulti(cat -> cat.produtosCaros(consumer, 50))
(Na real com method reference você teria que adaptar, mas a ideia é que o mapMulti aceita um lambda que chama seu método imperativo.)
✅ Quando usar mapMulti() – resumo da ópera
- Você tem uma relação one‑to‑zero/one‑to‑many e o número de elementos gerados por elemento original é pequeno.
- Quer evitar criar streams intermediários (melhor performance em laços grandes).
- O código fica mais claro com um laço
foreifdo que comflatMap+filter+map. - Você já tem um método que recebe
Consumere quer integrar direto no stream.
O mapMulti() não vai matar o flatMap() – cada um tem seu lugar. Mas quando o cenário se encaixa, é como trocar uma chave de fenda por uma chave Philips: faz o mesmo serviço, mas com muito mais estilo.
Gostou? Testa aí no seu código e me conta se os streams ficaram mais limpos. Até a próxima, e bora codar com menos firula e mais resultado! 🚀
Top comments (0)