Propiedades lazy En Kotlin

En este tutorial verás el uso de propiedades lazy en Kotlin, con el fin de posponer la lógica de los accesores de una propiedad.

Propiedades Lazy

Una propiedad lazy o perezosa, es aquella que su valor es computado por delegación, a través de la función lazy. Esto hará que su accesor get() otorgue el mismo valor luego de la primera ejecución.

La función lambda que recibe lazy para la lógica de get(), será materializado solo cuando sea necesitado, posponiendo la lógica de inicialización al momento en que crees una instancia de su clase contenedora.

Declara una propiedad lazy añadiéndole by lazy al final de su tipo.

val propiedadLazy by lazy{
    /* lógica de accesor
}

Por defecto, las propiedades lazy en Kotlin están seguras en un ambiente multihilo, ya que lazy() mostrará el mismo valor a los hilos que intenten accederlo.

Ejemplo: Propiedad De Nivel Superior Lazy

Declaremos una propiedad de nivel superior que almacene el valor del tiempo actual.

val currentTime: Long by lazy {
    System.currentTimeMillis()
}

fun main() {
    println("Valor en llamada 1: $currentTime")
    println ("Valor en llamada 2: $currentTime")
}

Salida:

Valor en llamada 1: 1611934597326
Valor en llamada 2: 1611934597326

Al delegar el contenido de la propiedad con la función lazy{}, la primer llamada recordará el valor inicial, por lo que en la llamada 2 no habrá una segunda ejecución.

Y ya que solo tomará un valor al ejecutarse la lambda, debes usar val para la declaración.

La Función lazy

Como ves, lazy es una función que recibe como argumento un lambda y retorna una instancia de la interfaz Lazy<T>.

fun <T> lazy(initializer: () -> T): Lazy<T>

La interfaz Lazy<T> contiene un método llamado isInitalized(), el cual actúa como centinela para no permitir las llamadas subsecuentes de la lambda pasada como parámetro:

public interface Lazy<out T> {
    
    public val value: T


    public fun isInitialized(): Boolean
}

Esta interfaz proporcionará el delegado para proveer el método getValue() necesario por by.

Ejemplo: Propiedad Miembro Lazy

Las propiedades miembro funcionan exactamente igual. Cuando usas el operador de acceso punto para obtener el valor de la propiedad, se ejecutará la lambda de inicialización y se establecerá su valor.

Por ejemplo, supongamos que deseamos crear un contendor de dependencias para manejar la inicialización de nuestros componentes.

En este caso: una base de datos con estadísticas de un usuario y el repositorio que la usa.

La idea es inicializar solo una vez a cada elemento, por lo que declararlas como propiedades lazy nos vendría de lujo:


class CoreDatabase(val connection: String)
class StatsRepository(val db: CoreDatabase)

object Dependencies {
    val dbController by lazy {
        println("Inicializando a dbController")
        CoreDatabase("mi_base_de_datos")
    }

    val statsRepository by lazy {
        println("Inicializando a statsRepository")
        StatsRepository(dbController)
    }
}

Como ves, Dependencies actúa como service locator y es una declaración de objeto.

En su interior tendremos a dbController como el punto de acceso a la base de datos y a statsRepository como el repositorio.

statsRepository depende de la instancia de dbController, por lo que este disparará la lambda asignado en el cuerpo de lazy. Si accedes a statsRepository:

fun main() {
    Dependencies.statsRepository
}

Tendrás:

Inicializando a statsRepository
Inicializando a dbController

Y si solo haces la llamada a Dependencies.dbController, solo se verá la inicialización de esta propiedad, ya que el repositorio esperará hasta que tú decidas acceder a su valor.

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!