lateinit, lazy, var, val... ¿cuál debo usar?

¿Estás empezando con Kotlin y tienes dudas para saber cuándo utilizar lateinit o by lazy? ¿Estás intentando integrar Kotlin con Android y te encuentras que no puedes poner medio val en el onCreate()? En esta lección te explico cómo utilizar by lazy y lateinit como alternativas para simplificar la declaración de código donde los inicializadores no se corren a nivel constructor.

Cuando trabajamos con Kotlin, sabemos que las palabras clave val y var son las más comunes para declarar variables. Sin embargo, estas funcionan perfectamente cuando podemos asignar un valor a la variable en el momento en que se crea la instancia de la clase. Por ejemplo, si tenemos un atributo cuyo valor podemos determinar a partir de un parámetro del constructor o de una función, simplemente usamos val o var para asignar ese valor inicial sin complicaciones.

Pero, ¿qué ocurre cuando no podemos asignar ese valor en el constructor? Esto sucede con frecuencia en entornos como Android, donde ciertas propiedades se inicializan en callbacks específicos, como onCreate, y no en el constructor de la clase. Por ejemplo, imaginemos que queremos tener un campo llamado versión en una actividad, pero su valor solo está disponible dentro del objeto properties que recibimos en onCreate. Kotlin no nos permitirá declarar esta propiedad como un val sin valor inicial, porque exige que las propiedades inmutables tengan un valor asignado desde el principio.

Una solución que algunos desarrolladores que vienen de Java podrían intentar es declarar la variable como mutable (var) y nullable, es decir, con un tipo como String?. Esto permite inicializarla con null y luego asignarle el valor correcto cuando esté disponible. Sin embargo, esta práctica tiene sus inconvenientes. Al ser nullable, cada vez que accedamos a esta variable tendremos que usar operadores de navegación segura (?.) o el operador Elvis (?:) para evitar errores por acceso a valores nulos. Esto puede ensuciar el código y hacerlo menos legible, además de que puede tentarnos a usar el operador de aserción no nula (!!), que puede provocar excepciones si no se usa con cuidado.

Aquí es donde entra en juego la palabra clave lateinit. Esta se utiliza exclusivamente con variables mutables (var) y nos permite declarar una variable sin asignarle un valor inicial, pero garantizando que se inicializará antes de su uso. Por ejemplo, podemos declarar:

lateinit var versión: String

Con esto, evitamos que la variable sea nullable y no necesitamos asignarle un valor en el constructor. Luego, cuando tengamos el valor disponible, por ejemplo en onCreate, simplemente hacemos:

versión = properties.get("version") as String

Así, podemos acceder a versión directamente sin preocuparnos por nulls. Eso sí, debemos tener cuidado: si intentamos acceder a versión antes de asignarle un valor, Kotlin lanzará una excepción en tiempo de ejecución indicando que la variable no ha sido inicializada. Por lo tanto, es fundamental asegurarnos de que la inicialización se realiza antes de cualquier acceso.

Además, lateinit tiene limitaciones importantes. No se puede usar con variables inmutables (val), lo que tiene sentido porque si la variable no puede reasignarse, no tendría sentido dejar su inicialización para más tarde.

Si queremos una variable inmutable que se inicialice de forma diferida, la alternativa es usar la delegación by lazy. Esto nos permite declarar una variable val cuyo valor se calcula solo cuando se accede a ella por primera vez. Por ejemplo:

val root: String by lazy {
    "http://example.com/api/version/" + versión
}

Aquí, la lambda que pasamos a lazy contiene el código que calcula el valor de root. Al declarar la variable así, no se ejecuta inmediatamente el código dentro de la lambda, sino que se pospone hasta el primer acceso a root. En ese momento, se evalúa la lambda, se asigna el resultado a root y las siguientes veces que accedamos a root simplemente devolverán ese valor ya calculado, sin volver a ejecutar la lambda.

Esto es especialmente útil cuando la inicialización es costosa, como puede ser una petición de red o un cálculo complejo, y queremos evitar que la creación de la instancia de la clase sea lenta. Sin embargo, debemos tener cuidado con el orden de acceso: si root depende de versión, debemos asegurarnos de que versión ya esté inicializada antes de acceder a root, para evitar errores.

En resumen, lateinit es ideal para variables mutables que no podemos inicializar en el constructor pero que sabemos que se asignarán antes de usarse, mientras que by lazy es perfecto para variables inmutables cuyo valor queremos calcular solo cuando sea necesario, optimizando así el rendimiento y la gestión de recursos. Ambas técnicas nos ayudan a manejar la inicialización tardía en Kotlin de manera segura y eficiente, siempre teniendo en cuenta sus limitaciones y el contexto en el que las usamos.

Lista de reproducción
  1. 1
    let en Kotlin: para qué sirve, cómo se usa y trucos
    10 minutos
  2. 2
    lateinit, lazy, var, val... ¿cuál debo usar?
    10 minutos