DEV Community

Fabio Rocha
Fabio Rocha

Posted on

Trabalhando com mapMulti() – Transforme seus streams sem neuras

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]
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

📦 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...
}
Enter fullscreen mode Exit fullscreen mode

Com flatMap() (tradicional)

List<ProdutoInfo> listaFlat = categorias.stream()
    .flatMap(cat -> cat.getProdutos().stream()
        .map(prod -> new ProdutoInfo(cat.getNome(), prod.getNome()))
    )
    .collect(Collectors.toList());
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

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()));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora seu stream fica limpo igual água de coco:

List<ProdutoInfo> resultado = categorias.stream()
    .<ProdutoInfo>mapMulti(cat -> cat.produtosCaros(consumer, 50))
    .collect(Collectors.toList());
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

(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 for e if do que com flatMap + filter + map.
  • Você já tem um método que recebe Consumer e 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)