by lazy o lateinit: ¿cuál es más rápido en Kotlin?

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: el dilema eterno del desarrollador Kotlin



Si alguna vez has escrito en Kotlin y te has enfrentado a la necesidad de retrasar la inicialización de una propiedad, seguramente te has preguntado: «¿Y qué elijo — by lazy o lateinit?» Esta pregunta es un verdadero éxito en Stack Overflow (miles de visitas, decenas de discusiones). Y no es casualidad: de la elección correcta depende no solo la legibilidad del código, sino también el rendimiento de la aplicación.



En este artículo no solo repetiremos la documentación. Realizaremos una verdadera investigación: analizaremos el funcionamiento interno de ambos mecanismos, compararemos su velocidad, consumo de memoria y daremos recomendaciones claras. Y al final — resumiremos con acciones concretas. ¡Allá vamos!



¿Qué son lateinit y by lazy? Breve introducción



Para empezar, refresquemos los conceptos básicos. Ambos enfoques resuelven un mismo problema: retrasar la inicialización de una propiedad hasta el momento de su primer uso. Pero lo hacen de manera diferente.



lateinit var — una promesa al compilador



La palabra clave lateinit le dice al compilador: «Prometo que inicializaré esta propiedad antes del primer acceso, mientras tanto solo reserva espacio para ella». Se usa solo con var y solo para propiedades mutables. Es ideal para inyección de dependencias (Dagger, Hilt, Koin) o para propiedades que se inicializan en onCreate de Activity/Fragment.



class MyActivity : AppCompatActivity() {    lateinit var repository: UserRepository

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) repository = UserRepository() // inicialización }

fun loadUser() { // Si repository no está inicializado — se lanzará UninitializedPropertyAccessException repository.getUser() }}


by lazy — delegado perezoso



El delegado lazy es un mecanismo más flexible. Crea un objeto contenedor que, en el primer acceso, ejecuta la lambda y almacena el resultado. Todos los accesos posteriores simplemente devuelven el valor guardado. Por defecto es seguro para hilos (sincronizado), pero se puede configurar el modo.



class MyViewModel : ViewModel() {    val heavyData: ExpensiveData by lazy {        // Este bloque se ejecutará solo una vez, en el primer acceso        ExpensiveData()    }}


Ambos enfoques resuelven el problema de la «inicialización perezosa», pero lo hacen con diferentes compromisos. Analicemos estos compromisos bajo el microscopio.



Rendimiento: ¿quién es más rápido?



Aquí llegamos a lo más interesante — a los números. El rendimiento no solo es la velocidad de acceso a la propiedad, sino también los gastos generales de inicialización, memoria y seguridad para hilos.



Velocidad del primer acceso



lateinit — el campeón absoluto. En el primer acceso, el compilador simplemente verifica que el campo no sea null (si es nullable — lanza una excepción). Sin lambdas, sin sincronizaciones. Es prácticamente gratuito.



by lazy — es más lento. En el primer acceso se ejecuta un bloque sincronizado (si el modo por defecto es LazyThreadSafetyMode.SYNCHRONIZED), se ejecuta la lambda y se almacena el resultado en caché. Esto lleva microsegundos, pero en bucles de alta carga o en código crítico puede ser notable.



Velocidad de accesos repetidos



Después de la inicialización, ambos enfoques funcionan igual de rápido. lateinit simplemente lee el campo (como un var normal). by lazy después de la primera inicialización también lee el valor en caché. La diferencia de velocidad en esta etapa tiende a cero.



Consumo de memoria



Aquí lateinit gana por goleada. No ge

Blogs

Book Recommendations