惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
The GitHub Blog
The GitHub Blog
Y
Y Combinator Blog
博客园 - 三生石上(FineUI控件)
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
Hugging Face - Blog
Hugging Face - Blog
WordPress大学
WordPress大学
V
Visual Studio Blog
小众软件
小众软件
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
MongoDB | Blog
MongoDB | Blog
V
V2EX
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Engineering at Meta
Engineering at Meta
L
LangChain Blog
Martin Fowler
Martin Fowler
GbyAI
GbyAI
博客园 - 司徒正美

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Trabalhando com mapMulti() – Transforme seus streams sem ...
Fabio Rocha · 2026-05-21 · via DEV Community

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! 🚀