Inicialización de propiedad usando "by lazy" frente a "lateinit"

Resuelto regmoraes asked hace 8 años • 8 respuestas

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):

  1. Inicialización diferida

lazy()es una función que toma una lambda y devuelve una instancia de Lazy<T>la cual puede servir como delegado para implementar una propiedad diferida: la primera llamada ejecuta get()la lambda pasada lazy()y recuerda el resultado, las llamadas posteriores get()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, myLazyStringvolveránHello

  1. 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?

regmoraes avatar Apr 14 '16 19:04 regmoraes
Aceptado

Estas son las diferencias significativas entre lateinit varla by lazy { ... }propiedad delegada:

  • lazy { ... }delegado sólo se puede utilizar para valpropiedades, mientras que lateinitsólo se puede aplicar a vars, porque no se puede compilar en un finalcampo, por lo que no se puede garantizar la inmutabilidad;

  • lateinit vartiene un campo de respaldo que almacena el valor y by 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, use lateinit;

  • Además de vals, lateinitno se puede utilizar para propiedades que aceptan valores NULL o tipos primitivos de Java (esto se debe a que nullse utiliza para valores no inicializados);

  • lateinit varse 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, use lateinit.

  • 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 otra lazysobrecarga ). En el caso de lateinit var, depende del código del usuario inicializar la propiedad correctamente en entornos multiproceso.

  • Una Lazyinstancia se puede guardar, transmitir e incluso utilizar para múltiples propiedades. Por el contrario, lateinit varlos correos electrónicos no almacenan ningún estado de tiempo de ejecución adicional (solo nullen 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 utilizarla property::isInitializeddesde 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.

hotkey avatar Apr 14 '2016 12:04 hotkey

lateinit vs perezoso

  1. 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.

  1. 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).

Shankar Kumar avatar Jul 18 '2019 14:07 Shankar Kumar

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.

Yogesh kataria avatar Jan 05 '2018 05:01 Yogesh kataria

Además de hotkeyla buena respuesta, así es como elijo entre los dos en la práctica:

lateinites 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 lazyes cuando solo usa dependencias internas de su objeto.

Guillaume avatar Apr 14 '2016 13:04 Guillaume

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.

Mehrbod Khiabani avatar Jan 23 '2019 09:01 Mehrbod Khiabani