Neste tutorial, você vai aprender tudo sobre lambdas em Kotlin: desde a sintaxe básica até conceitos avançados como closures, function references e funções inline. Lambdas são um dos recursos mais poderosos da linguagem e estão presentes em praticamente todo código Kotlin moderno. Ao final deste guia, você vai dominar programação funcional em Kotlin e saberá usar as principais funções da standard library como let, run, with, apply e also.

O que São Lambdas?

Uma lambda é, essencialmente, uma função anônima — uma função sem nome que pode ser tratada como um valor. Em Kotlin, lambdas são cidadãs de primeira classe, o que significa que você pode armazená-las em variáveis, passá-las como argumento para outras funções e retorná-las de funções.

Se você já trabalhou com JavaScript, Python ou qualquer linguagem que suporte programação funcional, o conceito será familiar. A diferença é que Kotlin oferece uma sintaxe especialmente elegante para trabalhar com lambdas.

Sintaxe Básica de Lambdas

A sintaxe de uma lambda em Kotlin segue este formato:

val minhaLambda: (Int, Int) -> Int = { a, b -> a + b }

// Chamando a lambda
val resultado = minhaLambda(3, 5) // resultado = 8
println(resultado)

Observe os elementos: as chaves {} delimitam o corpo da lambda, os parâmetros ficam antes da seta ->, e o último valor da expressão é o retorno implícito. Não é necessário usar a palavra return dentro de uma lambda.

Quando a lambda tem apenas um parâmetro, Kotlin permite usar a palavra reservada it no lugar do nome do parâmetro:

val dobrar: (Int) -> Int = { it * 2 }

println(dobrar(7)) // 14

O uso de it é extremamente comum no dia a dia e torna o código mais conciso. No entanto, quando a lambda é complexa ou aninhada, é recomendável nomear o parâmetro explicitamente para manter a legibilidade.

Higher-Order Functions

Uma higher-order function é uma função que recebe outra função como parâmetro ou que retorna uma função. Em Kotlin, isso é natural graças ao suporte a lambdas.

fun operacao(a: Int, b: Int, op: (Int, Int) -> Int): Int {
    return op(a, b)
}

// Usando com lambdas diferentes
val soma = operacao(10, 5) { a, b -> a + b }      // 15
val subtracao = operacao(10, 5) { a, b -> a - b }  // 5
val produto = operacao(10, 5) { a, b -> a * b }    // 50

println("Soma: $soma, Subtração: $subtracao, Produto: $produto")

Note que quando a lambda é o último parâmetro de uma função, podemos movê-la para fora dos parênteses. Essa é a chamada trailing lambda syntax e é uma das convenções mais usadas em Kotlin.

Closures

Lambdas em Kotlin podem acessar e modificar variáveis do escopo externo — isso é o que chamamos de closure. Diferente de Java (onde variáveis capturadas precisam ser efetivamente final), em Kotlin a lambda pode alterar variáveis mutáveis do escopo:

fun contadorDeCliques(): () -> Int {
    var cliques = 0
    return {
        cliques++
        cliques
    }
}

val contador = contadorDeCliques()
println(contador()) // 1
println(contador()) // 2
println(contador()) // 3

A lambda “captura” a variável cliques e mantém uma referência a ela mesmo após a função contadorDeCliques ter retornado. Closures são muito úteis para criar funções com estado interno.

Function References (::)

Além de lambdas, Kotlin permite passar referências a funções já existentes usando o operador ::. Isso é chamado de function reference:

fun ehPar(numero: Int): Boolean = numero % 2 == 0

val numeros = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// Usando function reference em vez de lambda
val pares = numeros.filter(::ehPar)
println(pares) // [2, 4, 6, 8, 10]

// Equivalente com lambda:
val paresLambda = numeros.filter { it % 2 == 0 }

Você também pode referenciar métodos de uma classe ou construtores:

data class Usuario(val nome: String, val idade: Int)

val nomes = listOf("Ana", "Bruno", "Carla")

// Referência ao construtor
val usuarios = nomes.map { Usuario(it, 25) }

// Referência a método de instância
val tamanhos = nomes.map(String::length)
println(tamanhos) // [3, 5, 5]

Funções Inline

Quando usamos higher-order functions com lambdas, cada lambda cria um objeto em memória. Para evitar esse overhead, Kotlin oferece a palavra-chave inline:

inline fun medirTempo(bloco: () -> Unit) {
    val inicio = System.currentTimeMillis()
    bloco()
    val fim = System.currentTimeMillis()
    println("Executado em ${fim - inicio}ms")
}

medirTempo {
    // Código a ser medido
    val lista = (1..1_000_000).toList().shuffled().sorted()
    println("Lista ordenada com ${lista.size} elementos")
}

Funções inline substituem a chamada da lambda pelo seu corpo diretamente no local de chamada durante a compilação. Isso elimina a criação de objetos e melhora a performance, especialmente em loops e código crítico.

Lambdas da Standard Library

Kotlin oferece um conjunto de funções de escopo extremamente úteis que usam lambdas. Vamos explorar cada uma delas:

let

Usada para executar um bloco de código com um objeto como argumento. Muito comum para null checks:

val nome: String? = "Kotlin Brasil"

nome?.let {
    println("O nome tem ${it.length} caracteres")
    println("Em maiúsculas: ${it.uppercase()}")
}

// Se nome for null, o bloco não é executado

run

Similar ao let, mas o objeto é acessado via this em vez de it:

val resultado = "Kotlin".run {
    println("O texto é: $this")
    length // retorno implícito
}
println("Tamanho: $resultado") // 6

with

Igual ao run, mas o objeto é passado como argumento em vez de ser o receiver:

val builder = StringBuilder()

with(builder) {
    append("Kotlin ")
    append("é ")
    append("incrível!")
}

println(builder.toString()) // Kotlin é incrível!

apply

Configura um objeto e retorna o próprio objeto. Ideal para inicialização:

val usuario = Usuario("Ana", 28).apply {
    // Imagine que Usuario é uma classe com propriedades mutáveis
    println("Criando usuário: $nome")
}

also

Executa uma ação adicional e retorna o objeto original. Útil para logging e debugging:

val numeros = mutableListOf(1, 2, 3)
    .also { println("Lista original: $it") }
    .apply { add(4) }
    .also { println("Após adicionar: $it") }

Exemplo Prático Completo

Vamos combinar tudo em um exemplo realista — um mini sistema de filtro de produtos:

data class Produto(val nome: String, val preco: Double, val categoria: String)

fun List<Produto>.filtrarE(
    vararg filtros: (Produto) -> Boolean
): List<Produto> {
    return this.filter { produto ->
        filtros.all { filtro -> filtro(produto) }
    }
}

fun main() {
    val produtos = listOf(
        Produto("Notebook", 3500.0, "Eletrônicos"),
        Produto("Camiseta", 79.90, "Roupas"),
        Produto("Smartphone", 2200.0, "Eletrônicos"),
        Produto("Calça Jeans", 150.0, "Roupas"),
        Produto("Fone Bluetooth", 199.90, "Eletrônicos")
    )

    val resultado = produtos
        .filtrarE(
            { it.categoria == "Eletrônicos" },
            { it.preco < 3000.0 }
        )
        .sortedBy { it.preco }
        .also { println("Encontrados ${it.size} produtos") }

    resultado.forEach { produto ->
        println("${produto.nome} — R$ ${"%.2f".format(produto.preco)}")
    }
}

Erros Comuns

  1. Confundir it em lambdas aninhadas: quando você tem lambdas dentro de lambdas, cada uma tem seu próprio it. Nomeie os parâmetros explicitamente para evitar ambiguidade.

  2. Esquecer que o último valor é o retorno: em Kotlin, a última expressão de uma lambda é o retorno implícito. Não use return dentro de lambdas a menos que queira fazer um non-local return (que retorna da função externa).

  3. Usar inline desnecessariamente: marcar funções como inline é útil apenas quando elas recebem lambdas como parâmetro. Usar inline em funções comuns pode aumentar o tamanho do bytecode sem benefício.

  4. Não entender a diferença entre scope functions: let usa it, run/with/apply usam this, e apply/also retornam o objeto original enquanto let/run/with retornam o resultado da lambda.

  5. Capturar variáveis mutáveis em closures sem cuidado: embora Kotlin permita, alterar variáveis externas dentro de lambdas pode gerar bugs difíceis de rastrear, especialmente em código assíncrono com coroutines.

Conclusão e Próximos Passos

Lambdas são o coração da programação funcional em Kotlin. Neste tutorial, você aprendeu a sintaxe básica, como usar it, closures, function references com ::, funções inline e as cinco scope functions da standard library. Com esse conhecimento, você está preparado para escrever código Kotlin idiomático e conciso.

Para continuar sua jornada, recomendamos explorar os seguintes tópicos:

  • Extension Functions para criar funções mais expressivas
  • Generics para trabalhar com tipos parametrizados em lambdas
  • Collections e suas operações funcionais avançadas como fold, reduce e groupBy
  • Coroutines para usar lambdas em programação assíncrona

Pratique criando suas próprias higher-order functions e experimentando as scope functions no seu código diário. Quanto mais você usar lambdas, mais natural a sintaxe vai se tornar.