Inicialización de propiedad usando "by lazy" frente a "lateinit"
En Kotlin, si no desea inicializar una propiedad de clase dentro del constructor o en la parte superior del cuerpo de la clase, tiene básicamente estas dos opciones (de la referencia del lenguaje):
- Inicialización diferida
lazy()
es una función que toma una lambda y devuelve una instancia deLazy<T>
la cual puede servir como delegado para implementar una propiedad diferida: la primera llamada ejecutaget()
la lambda pasadalazy()
y recuerda el resultado, las llamadas posterioresget()
simplemente devuelven el resultado recordado.Ejemplo
public class Hello { val myLazyString: String by lazy { "Hello" } }
Entonces, la primera llamada y las siguientes, dondequiera que sea, myLazyString
volveránHello
- Inicialización tardía
Normalmente, las propiedades declaradas como de tipo no nulo deben inicializarse en el constructor. Sin embargo, con bastante frecuencia esto no resulta conveniente. Por ejemplo, las propiedades se pueden inicializar mediante inyección de dependencia o en el método de configuración de una prueba unitaria. En este caso, no puede proporcionar un inicializador no nulo en el constructor, pero aun así desea evitar comprobaciones nulas al hacer referencia a la propiedad dentro del cuerpo de una clase.
Para manejar este caso, puedes marcar la propiedad con el modificador lateinit:
public class MyTest { lateinit var subject: TestSubject @SetUp fun setup() { subject = TestSubject() } @Test fun test() { subject.method() } }
El modificador solo se puede usar en propiedades var declaradas dentro del cuerpo de una clase (no en el constructor principal), y solo cuando la propiedad no tiene un captador o definidor personalizado. El tipo de propiedad no debe ser nulo y no debe ser un tipo primitivo.
Entonces, ¿cómo elegir correctamente entre estas dos opciones, ya que ambas pueden solucionar el mismo problema?
Estas son las diferencias significativas entre lateinit var
la by lazy { ... }
propiedad delegada:
lazy { ... }
delegado sólo se puede utilizar paraval
propiedades, mientras quelateinit
sólo se puede aplicar avar
s, porque no se puede compilar en unfinal
campo, por lo que no se puede garantizar la inmutabilidad;lateinit var
tiene un campo de respaldo que almacena el valor yby lazy { ... }
crea un objeto delegado en el que se almacena el valor una vez calculado, almacena la referencia a la instancia delegada en el objeto de clase y genera el captador para la propiedad que funciona con la instancia delegada. Entonces, si necesita que el campo de respaldo esté presente en la clase, uselateinit
;Además de
val
s,lateinit
no se puede utilizar para propiedades que aceptan valores NULL o tipos primitivos de Java (esto se debe a quenull
se utiliza para valores no inicializados);lateinit var
se puede inicializar desde cualquier lugar desde donde se vea el objeto, por ejemplo, desde dentro de un código marco, y son posibles múltiples escenarios de inicialización para diferentes objetos de una sola clase.by lazy { ... }
, a su vez, define el único inicializador de la propiedad, que sólo puede modificarse anulando la propiedad en una subclase. Si desea que su propiedad se inicialice desde afuera de una manera probablemente desconocida de antemano, uselateinit
.La inicialización
by lazy { ... }
es segura para subprocesos de forma predeterminada y garantiza que el inicializador se invoque como máximo una vez (pero esto se puede modificar usando otralazy
sobrecarga ). En el caso delateinit var
, depende del código del usuario inicializar la propiedad correctamente en entornos multiproceso.Una
Lazy
instancia se puede guardar, transmitir e incluso utilizar para múltiples propiedades. Por el contrario,lateinit var
los correos electrónicos no almacenan ningún estado de tiempo de ejecución adicional (solonull
en el campo para el valor no inicializado).Si tiene una referencia a una instancia de
Lazy
,isInitialized()
le permite verificar si ya se ha inicializado (y puede obtener dicha instancia con la reflexión de una propiedad delegada). Para comprobar si se ha inicializado una propiedad lateinit, puede utilizarlaproperty::isInitialized
desde Kotlin 1.2 .Una lambda pasada
by lazy { ... }
puede capturar referencias del contexto donde se usa en su cierre . Luego almacenará las referencias y las liberará solo una vez que se haya inicializado la propiedad. Esto puede provocar que las jerarquías de objetos, como las actividades de Android, no se publiquen durante demasiado tiempo (o nunca, si la propiedad permanece accesible y nunca se accede a ella), por lo que debe tener cuidado con lo que usa dentro del inicializador lambda.
Además, hay otra forma que no se menciona en la pregunta: Delegates.notNull()
, que es adecuada para la inicialización diferida de propiedades no nulas, incluidas las de tipos primitivos de Java.
lateinit vs perezoso
lateinita
i) Úselo con variable mutable [var]
lateinit var name: String //Allowed lateinit val name: String //Not Allowed
ii) Permitido solo con tipos de datos que no admiten valores NULL
lateinit var name: String //Allowed
lateinit var name: String? //Not Allowed
iii) Es una promesa al compilador de que el valor se inicializará en el futuro.
NOTA : Si intenta acceder a la variable lateinit sin inicializarla, arrojará UnInitializedPropertyAccessException.
perezoso
i) La inicialización diferida se diseñó para evitar la inicialización innecesaria de objetos.
ii) Su propiedad no se inicializará a menos que la use.
iii) Se inicializa sólo una vez. La próxima vez que lo use, obtendrá el valor de la memoria caché.
iv) Es seguro para subprocesos (se inicializa en el subproceso donde se usa por primera vez. Otros subprocesos usan el mismo valor almacenado en el caché).
v) La propiedad sólo puede ser val .
vi) La propiedad puede ser de cualquier tipo (incluidas primitivas y anulables, que no están permitidas con lateinit
).
Respuesta muy corta y concisa.
lateinit: Inicializa propiedades no nulas últimamente
A diferencia de la inicialización diferida, lateinit permite al compilador reconocer que el valor de la propiedad no nula no se almacena en la etapa del constructor para compilar normalmente.
Inicialización perezosa
by lazy puede ser muy útil al implementar propiedades de solo lectura (val) que realizan una inicialización diferida en Kotlin.
by lazy { ... } realiza su inicializador donde se usa por primera vez la propiedad definida, no su declaración.
Además de hotkey
la buena respuesta, así es como elijo entre los dos en la práctica:
lateinit
es para inicialización externa: cuando necesita elementos externos para inicializar su valor llamando a un método.
por ejemplo llamando a:
private lateinit var value: MyClass
fun init(externalProperties: Any) {
value = somethingThatDependsOn(externalProperties)
}
Mientras que lazy
es cuando solo usa dependencias internas de su objeto.
Además de todas las excelentes respuestas, existe un concepto llamado carga diferida:
La carga diferida es un patrón de diseño comúnmente utilizado en programación de computadoras para diferir la inicialización de un objeto hasta el punto en el que se necesita.
Utilizándolo correctamente, puedes reducir el tiempo de carga de tu aplicación. Y la forma de implementación de Kotlin es lazy()
cargar el valor necesario en su variable cuando sea necesario.
Pero lateinit se usa cuando está seguro de que una variable no será nula o vacía y se inicializará antes de usarla (por ejemplo, en el onResume()
método para Android) y, por lo tanto, no desea declararla como un tipo que acepta valores NULL.