Lambdas
Una lambda es una función anónima que podés tratar como un valor — guardarla en una variable, pasarla como argumento, retornarla desde otra función:
// Sintaxis completa: { parámetros -> cuerpo }
val saludar = { nombre: String -> "Hola, $nombre!" }
println(saludar("Carlos")) // Hola, Carlos!
// Lambda sin parámetros
val imprimirHola = { println("Hola!") }
imprimirHola()
// Lambda con múltiples parámetros
val sumar = { a: Int, b: Int -> a + b }
println(sumar(3, 4)) // 7
// Lambda de varias líneas — el último valor es el resultado
val procesarTexto = { texto: String ->
val limpio = texto.trim().lowercase()
val palabras = limpio.split(" ")
palabras.joinToString("-") // este es el valor de retorno
}
println(procesarTexto(" Hola Mundo ")) // hola-mundo
Tipos de función
Las funciones tienen tipos, igual que Int o String. La sintaxis es (TipoParam1, TipoParam2) -> TipoRetorno:
// Tipo de función: recibe String, retorna String
val transformar: (String) -> String = { it.uppercase() }
// Recibe dos Int, retorna Int
val operar: (Int, Int) -> Int = { a, b -> a + b }
// Sin parámetros, sin retorno útil
val accion: () -> Unit = { println("ejecutado") }
// Nullable — la función puede ser null
var callback: (() -> Unit)? = null
callback?.invoke() // safe call para invocar
// Tipos de función con receiver — la lambda tiene acceso a 'this'
// Se usan internamente en las scope functions
val configurar: StringBuilder.() -> Unit = {
append("Hola ")
append("Kotlin")
}
val sb = StringBuilder()
sb.configurar()
println(sb) // Hola Kotlin
Funciones de orden superior
Una función de orden superior es una función que recibe otra función como parámetro o la retorna:
// Recibe una función como parámetro
fun operar(a: Int, b: Int, operacion: (Int, Int) -> Int): Int {
return operacion(a, b)
}
operar(10, 3, { a, b -> a + b }) // 13
operar(10, 3, { a, b -> a * b }) // 30
operar(10, 3, { a, b -> a - b }) // 7
// Retorna una función
fun crearMultiplicador(factor: Int): (Int) -> Int {
return { numero -> numero * factor }
}
val duplicar = crearMultiplicador(2)
val triplicar = crearMultiplicador(3)
println(duplicar(5)) // 10
println(triplicar(5)) // 15
// Caso real: ejecutar código con manejo de errores centralizado
fun ejecutarSeguro(accion: () -> T): Result {
return try {
Result.success(accion())
} catch (e: Exception) {
Result.failure(e)
}
}
val resultado = ejecutarSeguro { apiService.getProductos() }
resultado.onSuccess { productos -> mostrar(productos) }
resultado.onFailure { error -> mostrarError(error.message) }
Trailing lambda — la sintaxis que usás todo el tiempo
Si el último parámetro de una función es una lambda, podés sacarla de los paréntesis. Si es el único parámetro, los paréntesis desaparecen:
// Sin trailing lambda
listOf(1, 2, 3).filter({ it > 1 })
// Con trailing lambda — la lambda sale de los paréntesis
listOf(1, 2, 3).filter { it > 1 }
// Si hay más parámetros, solo la lambda sale
listOf(1, 2, 3).fold(0, { acc, i -> acc + i })
listOf(1, 2, 3).fold(0) { acc, i -> acc + i } // más limpio
// Esto explica la sintaxis que ya conocés de Android:
viewModelScope.launch { // launch recibe una lambda como último param
val datos = repository.getDatos()
}
binding.btnGuardar.setOnClickListener { // setOnClickListener igual
viewModel.guardar()
}
Column { // los composables de Compose también
Text("Hola")
Button(onClick = { }) {
Text("Click")
}
}
Esto es lo que ya usabas sin saberloCada vez que escribís launch { }, setOnClickListener { } o un composable con contenido, estás usando trailing lambda. No es magia — es una función que recibe otra función como último parámetro.
it y destructuring en lambdas
// Cuando la lambda tiene un solo parámetro, se puede omitir y usar 'it'
listOf(1, 2, 3).filter { it > 1 } // it es el Int
listOf("a", "b").map { it.uppercase() } // it es el String
// Pero si la lambda es compleja, nombrá el parámetro explícitamente
listOf(usuarios).map { usuario -> // más legible que 'it' para objetos
"${usuario.nombre} (${usuario.email})"
}
// Ignorar parámetros con _
mapOf("a" to 1).forEach { (_, valor) -> println(valor) }
// Destructuring en lambdas
listOf(Pair("Ana", 25), Pair("Carlos", 30)).forEach { (nombre, edad) ->
println("$nombre tiene $edad años")
}
Referencias a funciones
Podés pasar una función existente como valor usando :::
fun esPar(n: Int) = n % 2 == 0
fun imprimirMayus(texto: String) = println(texto.uppercase())
// En lugar de lambda
listOf(1, 2, 3, 4).filter { esPar(it) }
listOf("a", "b").forEach { imprimirMayus(it) }
// Con referencia a función — más limpio
listOf(1, 2, 3, 4).filter(::esPar)
listOf("a", "b").forEach(::imprimirMayus)
listOf("a", "b", "a").filter(::println) // referencia a función del stdlib
// Referencia a método de instancia
data class Usuario(val nombre: String, val activo: Boolean)
val usuarios = listOf(Usuario("Ana", true), Usuario("Bob", false))
// En lugar de: usuarios.filter { it.activo }
val activos = usuarios.filter(Usuario::activo)
val nombres = usuarios.map(Usuario::nombre)
inline functions
Las lambdas generan objetos en el heap. Para funciones de orden superior que se llaman frecuentemente, inline copia el cuerpo de la función en el punto de llamada — sin overhead de objeto:
// Sin inline — crea un objeto lambda en cada llamada
fun medirTiempo(bloque: () -> Unit): Long {
val inicio = System.currentTimeMillis()
bloque()
return System.currentTimeMillis() - inicio
}
// Con inline — el compilador copia el código, sin objeto lambda
inline fun medirTiempo(bloque: () -> Unit): Long {
val inicio = System.currentTimeMillis()
bloque()
return System.currentTimeMillis() - inicio
}
// Las funciones del stdlib como filter, map, let, run son todas inline
// Por eso no tienen overhead aunque las uses millones de veces