Funciones estándar (Scope Functions) III: apply y also

Kotlin: Scope Functions
Tercera entrega sobre las funciones estándar (revisar las entradas Funciones estándar I: let y Funciones estándar II: with y run), que se centra en las funciones apply() y also().

apply

En esta función el objeto está disponible como un receptor (this) y su valor de retorno es el objeto.

Es común usar apply para bloques de código que no devuelven un valor y operan con los miembros del objeto receptor, habitualmente para inicializar o configurar el objeto. Así, cuando se utiliza apply se puede interpretar como "aplicar las siguientes asignaciones al objeto", por ejemplo:
data class Persona(var nombre: String, var edad: Int = 0, var ciudad: String = "")

fun main() {
    val persona1 = Persona("Juan").apply {
        edad = 32
        ciudad = "Valencia"
    }
}

Otra manera de escribir lo mismo:
data class Persona(var nombre: String, var edad: Int, var ciudad: String) {
    constructor() : this("", 0, "")
}

fun main() {
    val persona1 = Persona()
    val persona1Data = persona1.apply {
        nombre = "Juan"  // this.nombre o persona1.nombre
        edad = 32
        ciudad = "Valencia"
    }.toString()
}

Otro ejemplo:
enum class Color { ROJO, AZUL }

data class PaletaColores(
    var color1: Color = Color.AZUL,
    var color2: String = "default"
)

fun main() {
    val paleta = PaletaColores().apply {
        color1 = Color.ROJO
        color2 = "Blanco"
    }
    println(paleta)
}

Teniendo el receptor como valor de retorno, podemos incluir apply en llamadas encadenadas para un procesamiento más complejo.
fun main() {
    val numeroTelefono = "8899665544"
    numeroTelefono.apply {
        println(contains("8")) // true
        println(length) // 10
    }
}

also

En la función also() el objeto está disponible como un argumento (it) y el valor de retorno es el objeto en sí. Cuando vemos la función also() en el código, podemos interpretarla como "y también hacer lo siguiente...".
fun main() {
    val numeros = mutableListOf("uno", "dos", "tres")
    numeros
        .also { println("Antes: $it") }
        .add("cuatro")
    println("Después: $numeros")
}

En ocasiones es útil para acciones que no alteran el objeto, como registrar o imprimir información de depuración.
data class Persona(var nombre: String, var edad: Int, var ciudad: String) {
    constructor() : this("", 0, "")
}

fun personaLog(p: Persona) {
    println("Nuevo contacto creado: ${p.nombre}.")
}

fun main() {
    val persona1 = Persona("Juan", 32, "Valencia")
        .also {
            personaLog(it)
        }
}

Como hemos visto en esta serie dedicada a las funciones estándar, a la hora de elegir una función estándar puede resultar confuso decidirnos por una u otra. A modo de resumen, en la siguiente tabla se ven rápidamente las diferencias entre ellas:

Scope Functions
Función Referencia al Objeto Valor de Retorno
let it lambda
run this lambda
with* this lambda
apply this objeto
also it objeto
* A diferencia de run, with toma el objeto como un argumento de la función.

Comentarios

Entradas populares

I/O: entrada y salida de datos en consola

Recursos gratis para aprender Kotlin

Lectura y escritura de archivos