Kotlin: Funções e Expressões Lambda do Kotlin
Última atualização: 2026-08-26
Funções são cidadãos de primeira classe no Kotlin — funções de ordem superior + lambdas + inline permitem que Charlie construa os pipelines de processamento de dados do OrderProcessor em estilo funcional, reduzindo o código em 60%.
1. O que Você Aprenderá
- Declarações de funções: parâmetros padrão, argumentos nomeados, corpos de expressão
- Funções de ordem superior: funções como parâmetros e valores de retorno
- Sintaxe lambda:
{ it -> ... }e abreviaçãoit - Convenção de lambda final:
orders.filter { ... } - Funções inline
inline: eliminar overhead de tempo de execução de lambdas
2. A História Real de um Desenvolvedor
(1) Problema: Lógica Duplicada de Processamento de Pedidos
Na equipe do Charlie, cada desenvolvedor escrevia sua própria lógica de filtragem/transformação/agregação de pedidos — 100+ linhas de código duplicado espalhadas por 5 serviços. Mudar uma condição de filtro significava alterar em 5 lugares.
(2) Funções de Ordem Superior Eliminam Duplicação
// Processador de pedidos genérico - uma função substitui 5 cópias
fun <T> processOrders(
orders: List<Order>,
filter: (Order) -> Boolean,
transform: (Order) -> T
): List<T> = orders.filter(filter).map(transform)
// Uso: cada chamada são 2 linhas em vez de 20
val highValueIds = processOrders(orders, { it.total > 1_000 }, { it.id })
val vipCustomers = processOrders(orders, { it.total > 10_000 }, { it.customer })
Funções de ordem superior parametrizam "o que muda" para que "o que permanece" seja escrito apenas uma vez.
3. Declarações de Funções
(1) Funções Básicas
// Corpo de bloco
fun calculateTax(total: Double, rate: Double = 0.08): Double {
return total * rate
}
// Corpo de expressão
fun calculateTax(total: Double, rate: Double = 0.08) = total * rate
// Retorno Unit (void)
fun logOrder(order: Order) {
println("Processando ${order.id}")
}
(2) Parâmetros Padrão e Nomeados
// Parâmetros padrão - elimina sobrecarga de métodos
fun createOrder(
id: String,
total: Double,
status: String = "PENDING",
currency: String = "USD",
taxRate: Double = 0.08
): Order = Order(id, total, status, currency)
// Argumentos nomeados - chame qualquer parâmetro pelo nome
val order1 = createOrder("ORD-001", 299.99)
val order2 = createOrder("ORD-002", 1_500.00, taxRate = 0.10)
val order3 = createOrder("ORD-003", 45.50, currency = "EUR", status = "CONFIRMED")
(3) Parâmetros Padrão vs Sobrecarga de Métodos Java
| Dimensão | Sobrecarga Java | Parâmetros Padrão Kotlin |
|---|---|---|
| Tamanho do código | N métodos | 1 método |
| Manutenibilidade | Mudar N lugares para um parâmetro | Mudar um lugar |
| Flexibilidade de chamada | Apenas ordem posicional | Argumentos nomeados, qualquer combinação |
| Interoperabilidade | Java chama diretamente | Precisa de @JvmOverloads |
4. Funções de Ordem Superior
Funções de ordem superior recebem funções como parâmetros ou retornam funções — a pedra angular da programação funcional do Kotlin.
(1) Tipos de Função
// Sintaxe de tipo de função
val transform: (Order) -> String = { order -> order.id }
val predicate: (Order) -> Boolean = { it.total > 1_000 }
val aggregator: (Double, Order) -> Double = { acc, order -> acc + order.total }
val supplier: () -> Order = { Order("DEFAULT", 0.0, "PENDING") }
// Função como parâmetro
fun processOrders(orders: List<Order>, transform: (Order) -> String): List<String> {
return orders.map(transform)
}
(2) Função como Valor de Retorno
// Retornar uma função
fun getDiscountStrategy(tier: String): (Double) -> Double = when (tier) {
"VIP" -> { total -> total * 0.80 } // 20% de desconto
"GOLD" -> { total -> total * 0.90 } // 10% de desconto
else -> { total -> total } // Sem desconto
}
val discount = getDiscountStrategy("VIP")
val finalPrice = discount(1_000.00) // 800.0
(3) Função de Ordem Superior + Cadeia de Chamadas Lambda
flowchart LR
A[Pedidos] --> B[filter]
B --> C[map]
C --> D[sortedBy]
D --> E[fold]
E --> F[Resultado]
5. Expressões Lambda
(1) Sintaxe Lambda
// Sintaxe completa
val total = orders.map({ order: Order -> order.total })
// Inferência de tipo
val total = orders.map({ order -> order.total })
// Palavra-chave it para parâmetro único
val total = orders.map({ it.total })
// Convenção de lambda final
val total = orders.map { it.total }
// Lambda com múltiplos parâmetros
val result = orders.fold(0.0) { acc, order -> acc + order.total }
(2) Níveis de Simplificação da Sintaxe Lambda
| Nível | Sintaxe | Descrição |
|---|---|---|
| Completa | { order: Order -> order.total } |
Tipo explícito |
| Simplificada 1 | { order -> order.total } |
Inferência de tipo |
| Simplificada 2 | { it.total } |
Parâmetro único com it |
| Lambda final | orders.map { it.total } |
Lambda fora dos parênteses |
(3) Padrões Lambda Comuns
// let: transformar e consumir
val formatted = order.total.let { "\$$it USD" }
// also: efeito colateral sem alterar o valor
val order = createOrder("ORD-001", 299.99).also {
println("Pedido criado: ${it.id}")
}
// apply: configurar objeto
val order = Order("ORD-001", 0.0, "PENDING").apply {
// Só pode ser usado com propriedades var
}
// run: executar bloco e retornar resultado
val summary = order.run {
"Pedido $id: \$${total} USD ($status)"
}
6. Convenção de Lambda Final
Quando o último parâmetro de uma função é um lambda, ele pode ser escrito fora dos parênteses — este é o açúcar sintático mais importante do Kotlin.
(1) Uso Básico
// Definição
fun processOrders(orders: List<Order>, callback: (Order) -> Unit) { ... }
// Chamada: lambda final
processOrders(orders) { order ->
println(order.id)
}
// Se o lambda é o ÚNICO parâmetro, omita os parênteses
orders.forEach { println(it.id) }
(2) Lambda Final como Base de DSLs
// É assim que DSLs funcionam
fun order(id: String, init: OrderBuilder.() -> Unit): Order {
val builder = OrderBuilder(id)
builder.init()
return builder.build()
}
// Uso: lambda final faz ler como configuração
val myOrder = order("ORD-001") {
total(299.99)
status("CONFIRMED")
customer("Alice")
}
7. Funções Inline
Lambdas compilam para objetos de classe anônima na JVM, criando novas instâncias a cada chamada. inline elimina esse overhead.
(1) Básico do inline
// Sem inline: Lambda cria um objeto a cada chamada
fun process(order: Order, block: (Order) -> String): String {
return block(order)
}
// Com inline: código do lambda é inserido no local da chamada
inline fun process(order: Order, block: (Order) -> String): String {
return block(order)
}
(2) Comparação do Efeito do inline
// Após a inlining, isso:
process(order) { it.id }
// Torna-se (conceitualmente):
run { order.id }
(3) inline vs noinline vs crossinline
| Modificador | Efeito | Caso de Uso |
|---|---|---|
inline |
Código do lambda inserido no local da chamada | Lambdas chamados frequentemente |
noinline |
Impede inlining (permanece como objeto) | Lambda precisa ser armazenado/passado |
crossinline |
Inserido, mas proíbe retorno não local | Lambda em um contexto de execução diferente |
(4) Parâmetros de Tipo reified
// Normal: tipo é apagado em tempo de execução
fun <T> isType(value: Any): Boolean = value is T // ERRO
// reified: inline preserva informação de tipo
inline fun <reified T> isType(value: Any): Boolean = value is T // OK
isType<String>("Hello") // true
isType<Int>(42) // true
isType<Int>("42") // false
8. Exemplo Completo: Pipeline Funcional do OrderProcessor
// ============================================
// OrderProcessor - Pipeline Funcional
// Funcionalidade: Processar pedidos com funções de ordem superior
// ============================================
data class Order(val id: String, val total: Double, val status: String, val customer: String)
// Processador de pipeline genérico
inline fun <T, R> List<T>.pipeline(
filter: (T) -> Boolean,
transform: (T) -> R,
crossinline postProcess: (R) -> Unit
): List<R> {
return this.filter(filter).map(transform).also { results ->
results.forEach(postProcess)
}
}
// Fábrica de estratégia de desconto
fun getDiscountStrategy(tier: String): (Double) -> Double = when (tier) {
"VIP" -> { total -> total * 0.80 }
"GOLD" -> { total -> total * 0.90 }
"SILVER" -> { total -> total * 0.95 }
else -> { total -> total }
}
fun main() {
val orders = listOf(
Order("ORD-001", 299.99, "CONFIRMED", "Alice"),
Order("ORD-002", 15_000.00, "CONFIRMED", "Bob"),
Order("ORD-003", 2_500.00, "PENDING", "Charlie"),
Order("ORD-004", 45.50, "CANCELLED", "Alice"),
Order("ORD-005", 8_000.00, "CONFIRMED", "Bob")
)
// Pipeline: filtrar confirmados -> aplicar desconto VIP -> registrar
val discount = getDiscountStrategy("VIP")
val processed = orders.pipeline(
filter = { it.status == "CONFIRMED" },
transform = { it.copy(total = discount(it.total)) },
postProcess = { println(" Processado: ${it.id} -> \$${it.total} USD") }
)
// Agregar resultados
val totalRevenue = processed.sumOf { it.total }
val avgOrder = processed.map { it.total }.average()
println("\nReceita: \$$totalRevenue USD | Média: \$${"%.2f".format(avgOrder)} USD")
// Composição funcional com let/also/run
val report = orders
.filter { it.status != "CANCELLED" }
.groupBy { it.customer }
.mapValues { (_, list) ->
list.sumOf { it.total }
}
.also { println("\nReceita por cliente:") }
.map { (customer, total) -> "$customer: \$$total USD" }
.joinToString("\n ")
println(" $report")
}
Saída:
Processado: ORD-001 -> $239.992 USD
Processado: ORD-002 -> $12000.0 USD
Processado: ORD-005 -> $6400.0 USD
Receita: $18639.992 USD | Média: $6213.330666666667 USD
Receita por cliente:
Alice: $299.99 USD
Bob: $23000.0 USD
Charlie: $2500.0 USD
9. Exemplos práticos rápidos
▶ Exemplo: Parâmetros padrão e nomeados
// Parâmetros com valor padrão - reduz sobrecargas
fun createOrder(
id: String,
total: Double,
currency: String = "USD",
status: String = "PENDING",
express: Boolean = false
): String {
val shipping = if (express) "EXPRESS" else "STANDARD"
return "$id | $total $currency | $status | $shipping"
}
// Chamada posicional
println(createOrder("ORD-001", 299.99))
// Chamada com argumentos nomeados - legibilidade máxima
println(createOrder(
id = "ORD-002",
total = 1500.0,
express = true,
status = "CONFIRMED"
))
// Argumentos nomeados em qualquer ordem
println(createOrder(
total = 50.0,
id = "ORD-003",
currency = "EUR"
))
Saída:
ORD-001 | 299.99 USD | PENDING | STANDARD
ORD-002 | 1500.0 USD | CONFIRMED | EXPRESS
ORD-003 | 50.0 EUR | PENDING | STANDARD
▶ Exemplo: Funções de ordem superior
// Função que recebe outra função como parâmetro
fun calculateTotal(
items: List<Double>,
discount: (Double) -> Double
): Double {
val subtotal = items.sum()
return discount(subtotal)
}
// Lambdas para diferentes políticas
val vipDiscount = { total: Double -> if (total > 10_000) total * 0.8 else total }
val seasonalDiscount = { total: Double -> total * 0.9 }
val noDiscount = { total: Double -> total }
val order1 = calculateTotal(listOf(5000.0, 4000.0, 3000.0), vipDiscount)
val order2 = calculateTotal(listOf(500.0, 300.0), seasonalDiscount)
val order3 = calculateTotal(listOf(100.0), noDiscount)
println("VIP: \$$order1") // 9600.0 (20% off)
println("Sazonal: \$$order2") // 720.0 (10% off)
println("Normal: \$$order3") // 100.0
Saída:
VIP: $9600.0
Sazonal: $720.0
Normal: $100.0
▶ Exemplo: Lambdas com it e closures
// Lambda simples com `it`
val names = listOf("Alice", "Bob", "Charlie")
val greetings = names.map { "Olá, $it!" }
println(greetings)
// Lambda com captura (closure)
fun makeCounter(start: Int = 0): () -> Int {
var count = start
return { count++ }
}
val counter1 = makeCounter()
val counter2 = makeCounter(100)
println("counter1: ${counter1()}, ${counter1()}, ${counter1()}")
println("counter2: ${counter2()}, ${counter2()}")
// Modificando variáveis externas
var total = 0
val orders = listOf(100.0, 200.0, 300.0)
orders.forEach { total += it.toInt() }
println("Total: $total")
Saída:
[Olá, Alice!, Olá, Bob!, Olá, Charlie!]
counter1: 0, 1, 2
counter2: 100, 101
Total: 600
▶ Exemplo: Funções inline e reified
// inline: evitar overhead de lambda em loops
inline fun <T> List<T>.customEach(action: (T) -> Unit) {
for (item in this) action(item)
}
// reified: tipo disponível em tempo de execução (apenas em funções inline)
inline fun <reified T> List<*>.filterByType(): List<T> = filterIsInstance<T>()
val mixed = listOf("Alice", 42, "Bob", 3.14, "Charlie")
val strings = mixed.filterByType<String>()
val ints = mixed.filterByType<Int>()
println("Strings: $strings")
println("Ints: $ints")
// Uso de customEach
mixed.customEach { println("Item: $it") }
Saída:
Strings: [Alice, Bob, Charlie]
Ints: [42]
Item: Alice
Item: 42
Item: Bob
Item: 3.14
Item: Charlie
▶ Exemplo: Operador infix
// Operador infix: método de um parâmetro que pode ser chamado sem ponto/parênteses
infix fun String.times(count: Int): String = this.repeat(count)
infix fun Int.to(range: IntRange): List<Int> = (this..range).toList()
// Uso sem pontos
val shout = "Olá " times 3
println(shout)
val range = 1 to 10
println("Intervalo: $range")
// Composição com chamadas normais
val frames = 60.times(2)
println("Quadros: $frames")
Saída:
Olá Olá Olá
Intervalo: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Quadros: 120
▶ Exemplo: Funções de escopo
// let: transformação segura, execução de lambda no objeto
val order: String? = "ORD-001"
val length = order?.let {
println("Pedido: $it")
it.length
}
println("Comprimento: $length")
// also: efeitos colaterais, retorna o próprio objeto
val processed = "ORD-002".also {
println("Log: processando $it")
}.also {
println("Notificando $it")
}
println("Resultado: $processed")
// with: contexto múltiplo para configuração
val config = with(mutableMapOf<String, Any>()) {
put("env", "produção")
put("debug", false)
put("timeout", 30)
this
}
println("Config: $config")
Saída:
Pedido: ORD-001
Comprimento: 7
Log: processando ORD-002
Notificando ORD-002
Resultado: ORD-002
Config: {env=produção, debug=false, timeout=30}
▶ Exemplo: Recursão e tailrec
// Função recursiva padrão - pode causar StackOverflow
fun factorial(n: Int): Long {
return if (n <= 1) 1 else n * factorial(n - 1)
}
println("5! = ${factorial(5)}")
// tailrec: otimiza recursão em cauda para loop
tailrec fun factorialTailrec(n: Int, acc: Long = 1): Long {
return if (n <= 1) acc else factorialTailrec(n - 1, n * acc)
}
println("10! = ${factorialTailrec(10)}")
// Fibonacci otimizado
tailrec fun fib(n: Int, a: Long = 0, b: Long = 1): Long {
return if (n == 0) a else fib(n - 1, b, a + b)
}
println("Fib(20) = ${fib(20)}")
Saída:
5! = 120
10! = 3628800
Fib(20) = 6765
❓ Perguntas Frequentes
P: Qual a diferença entre um lambda e uma função anônima? R: Lambdas usam
itcomo parâmetro implícito ereturnse comporta de forma diferente (lambdareturné não local, função anônimareturné local). Use lambdas na maioria dos casos.
P: Quando devo usar
itvs nome explícito? R: Useitquando o corpo do lambda tem 1-2 linhas e o contexto é claro; use nomes explícitos para corpos mais longos ou lambdas aninhados para melhorar a legibilidade.
P: Todas as funções de ordem superior devem ser
inline? R: Não.inlineé melhor para corpos de função pequenos onde o lambda é chamado diretamente. Inlining funções grandes aumenta o tamanho do código. A maioria das funções de ordem superior da biblioteca padrão sãoinline.
P:
reifiedpode ser usado em classes regulares? R: Não.reifiedsó funciona em parâmetros de tipo de funçõesinline, porque inlining permite ao compilador preservar informação de tipo no local da chamada. Parâmetros de tipo de funções regulares são apagados em tempo de execução.
P: A convenção de lambda final é apenas açúcar sintático? R: É açúcar sintático, mas criticamente importante — é a base da construção de DSLs Kotlin. Gradle Kotlin DSL, Compose UI, Anko, etc. todos dependem desta convenção.
P: Como escolher entre
let/also/apply/run? R:letpara transformar valores,alsopara efeitos colaterais,applypara configurar objetos (retorna this),runpara executar um bloco (retorna o resultado). Lembre-se:applyretornathis,runretorna o resultado do lambda.
📖 Resumo
- Parâmetros padrão + argumentos nomeados eliminam a sobrecarga de métodos do Java — uma função trata todas as combinações de chamada
- Funções de ordem superior parametrizam "o que muda", eliminando lógica duplicada
- Sintaxe lambda vai da completa à concisa:
{ x:Type -> ... }→{ x -> ... }→{ it -> ... }→ lambda final - A convenção de lambda final é a base sintática das DSLs Kotlin
inlineelimina o overhead de alocação de objetos lambda;reifiedpreserva informação de tipo genéricolet/also/apply/runsão funções de escopo comumente usadas, cada uma com propósitos distintos
📝 Exercícios
- Iniciante (⭐): Escreva uma função
calculateTotalcom parâmetros padrão e chame-a com argumentos nomeados. Dica:fun calc(price: Double, tax: Double = 0.08) - Intermediário (⭐⭐): Implemente
processAndTransformcom funções de ordem superior, aceitando uma condição de filtro e uma função de transformação, retornando uma lista de resultados. Dica:<T> fun List<T>.process... - Desafio (⭐⭐⭐): Implemente uma função
filterByType<Any>que filtra elementos de um tipo especificado de uma lista de tipos mistos. Dica:inline fun <reified T> List<Any>.filterByType(): List<T>