web-dev-qa-db-fra.com

Kotlin - Initialisation de propriété en utilisant "par lazy" vs "Lateinit"

Dans Kotlin, si vous ne souhaitez pas initialiser une propriété de classe dans le constructeur ou dans la partie supérieure du corps de la classe, vous avez essentiellement les deux options suivantes (à partir de la référence du langage):

  1. initialisation lente

lazy () est une fonction qui prend un lambda et renvoie une instance de Lazy qui peut servir de délégué pour l'implémentation d'une propriété lazy: le premier appel à get () exécute le lambda transmis à lazy () et mémorise le résultat, les appels suivants pour obtenir () retourne simplement le résultat mémorisé.

Exemple

public class Hello {

   val myLazyString: String by lazy { "Hello" }

}

Ainsi, le premier appel et les appels subquentiels, où qu’ils se trouvent, à myLazyString renverront "Hello"

  1. Initialisation tardive

Normalement, les propriétés déclarées comme ayant un type non nul doivent être initialisées dans le constructeur. Cependant, assez souvent, ce n'est pas pratique. Par exemple, les propriétés peuvent être initialisées via une injection de dépendance ou dans la méthode de configuration d'un test unitaire. Dans ce cas, vous ne pouvez pas fournir d'initialiseur non null dans le constructeur, mais vous souhaitez tout de même éviter les contrôles NULL lors de la mise en référence de la propriété dans le corps d'une classe.

Pour gérer ce cas, vous pouvez marquer la propriété avec le modificateur lateinit:

public class MyTest {

   lateinit var subject: TestSubject

   @SetUp fun setup() { subject = TestSubject() }

   @Test fun test() { subject.method() }
}

Le modificateur ne peut être utilisé que sur les propriétés var déclarées dans le corps d'une classe (pas dans le constructeur principal), et uniquement lorsque la propriété n'a pas de getter ou de setter personnalisé. Le type de la propriété doit être non nul et il ne doit pas s'agir d'un type primitif.

Alors, comment choisir correctement entre ces deux options, puisque les deux peuvent résoudre le même problème?

207
regmoraes

Voici les différences significatives entre lateinit var et by lazy { ... } propriété déléguée:

  • Le délégué lazy { ... } ne peut être utilisé que pour les propriétés val, alors que lateinit ne peut être appliqué qu'à vars, car il ne peut pas être compilé dans un champ final donc aucune immuabilité ne peut être garantie;

  • lateinit var a un champ de sauvegarde qui stocke la valeur et by lazy { ... } crée un objet délégué dans lequel la valeur est stockée une fois calculée, stocke la référence à l'instance de délégué dans l'objet de classe et génère le getter pour le propriété qui fonctionne avec l'instance de délégué. Donc, si vous avez besoin du champ de sauvegarde présent dans la classe, utilisez lateinit;

  • En plus de vals, lateinit ne peut pas être utilisé pour les propriétés nullables et les types primitifs Java (c'est à cause de null utilisé pour les valeurs non initialisées);

  • lateinit var peut être initialisé à partir de n'importe quel endroit où l'objet est vu, par ex. depuis un code d'infrastructure et plusieurs scénarios d'initialisation sont possibles pour différents objets d'une même classe. by lazy { ... }, à son tour, définit le seul initialiseur de la propriété, qui ne peut être modifié que par substitution de la propriété dans une sous-classe. Si vous souhaitez que votre propriété soit initialisée de l'extérieur d'une manière probablement inconnue auparavant, utilisez lateinit.

  • Initialisation by lazy { ... } est thread-safe par défaut et garantit que l'initialiseur est appelé au plus une fois (mais cela peut être modifié en utilisant ne autre surcharge lazy _ ). Dans le cas de lateinit var, il appartient au code de l'utilisateur d'initialiser la propriété correctement dans des environnements multithreads.

  • Une instance Lazy peut être sauvegardée, transmise et même utilisée pour plusieurs propriétés. Au contraire, lateinit vars ne stocke aucun état d'exécution supplémentaire (uniquement null dans le champ pour la valeur non initialisée).

  • Si vous détenez une référence à une instance de Lazy, isInitialized() vous permet de vérifier si elle a déjà été initialisée (et vous pouvez obtenir une telle instance avec réflexion à partir de une propriété déléguée). Pour vérifier si une propriété lateinit a été initialisée, vous pouvez tiliser property::isInitialized depuis Kotlin 1.2 .

  • Un lambda passé à by lazy { ... } peut capturer les références du contexte où il est utilisé dans son fermeture .. Il stockera ensuite les références et ne les libérera qu'une fois la propriété initialisée. Cela peut entraîner des hiérarchies d’objets, telles que les activités Android, qui ne sont pas publiées trop longtemps (ou jamais, si la propriété reste accessible et n’est jamais utilisée), vous devez donc faire attention à ce que vous utilisez initialiseur lambda.

En outre, il existe un autre moyen non mentionné dans la question: Delegates.notNull() , qui convient à l'initialisation différée de propriétés non null, y compris celles de Java types primitifs.

249
hotkey

En plus de la bonne réponse de hotkey, voici comment je choisis entre les deux en pratique:

lateinit est destiné à l'initialisation externe: lorsque vous avez besoin d'éléments externes pour initialiser votre valeur en appelant une méthode.

par exemple. en appelant:

private lateinit var value: MyClass

fun init(externalProperties: Any) {
   value = somethingThatDependsOn(externalProperties)
}

Alors que lazy est utilisé, il utilise uniquement des dépendances internes à votre objet.

18
Guillaume

Réponse très courte et concise

lateinit: initialise dernièrement les propriétés non nulles

Contrairement à l'initialisation différée, lateinit permet au compilateur de reconnaître que la valeur de la propriété non null n'est pas stockée dans l'étape du constructeur pour être compilée normalement.

Initialisation paresseuse

by paresseux peut être très utile lors de la mise en œuvre de lecture seule (val) propriétés produisant des effets paresseux. -initialisation à Kotlin.

by lazy {...} effectue son initialiseur là où la propriété définie est utilisée pour la première fois, pas sa déclaration.

12
John Wick

En plus de toutes les bonnes réponses, il existe un concept appelé chargement paresseux:

Le chargement différé est un modèle de conception couramment utilisé dans la programmation informatique pour différer l'initialisation d'un objet jusqu'au moment où il est nécessaire.

En l’utilisant correctement, vous pouvez réduire le temps de chargement de votre application. Et la méthode Kotlin de son implémentation est de lazy() qui charge la valeur nécessaire dans votre variable à tout moment.

Mais lateinit est utilisé lorsque vous êtes certain qu'une variable ne sera ni nulle ni vide et qu'elle sera initialisée avant de l'utiliser. dans la méthode onResume() pour Android et vous ne voulez donc pas le déclarer comme un type nullable.

4
Mehrbod Khiabani

Le crédit va à @ Amit Shekhar

lateinit

lateinit est une initialisation tardive.

Normalement, les propriétés déclarées comme ayant un type non nul doivent être initialisées dans le constructeur. Cependant, assez souvent, ce n'est pas pratique. Par exemple, les propriétés peuvent être initialisées via une injection de dépendance ou dans la méthode de configuration d'un test unitaire. Dans ce cas, vous ne pouvez pas fournir d'initialiseur non null dans le constructeur, mais vous souhaitez tout de même éviter les contrôles NULL lors de la référence à la propriété dans le corps d'une classe.

Exemple:

public class Test {

  lateinit var mock: Mock

  @SetUp fun setup() {
     mock = Mock()
  }

  @Test fun test() {
     mock.do()
  }
}

paresseux

paresseux est une initialisation paresseuse.

lazy() est une fonction qui prend un lambda et renvoie une instance de lazy qui peut servir de délégué pour l'implémentation d'une propriété lazy: le premier appel à get() exécute le lambda transmis à lazy() et rappelle le résultat, les appels suivants à get() renverront simplement le résultat mémorisé.

Exemple:

public class Example{
  val name: String by lazy { “Amit Shekhar” }
}
3
Dhaval Jivani

lateinit vs paresseux

  1. lateinit

    i) Utilisez-le avec une variable mutable [var]

    lateinit var name: String       //Allowed
    lateinit val name: String       //Not Allowed
    

    ii) Autorisé avec uniquement les types de données non nullables

    lateinit var name: String       //Allowed
    lateinit var name: String?      //Not Allowed
    

    iii) C'est une promesse faite au compilateur que la valeur soit initialisée à l'avenir.

NOTE: Si vous essayez d'accéder à la variable lateinit sans l'initialiser, une exception UnInitializedPropertyAccessException est renvoyée.

  1. paresseux

    i) L’initialisation différée a été conçue pour éviter toute initialisation inutile d’objets.

    ii) Votre variable ne sera pas initialisée à moins que vous ne l'utilisiez.

    iii) il n'est initialisé qu'une fois. La prochaine fois que vous l'utiliserez, vous obtiendrez la valeur de la mémoire cache.

    iv) il est thread-safe (il est initialisé dans le thread où il est utilisé pour la première fois. Les autres threads utilisent la même valeur stockée dans le cache).

    v) La variable peut être var ou val.

    vi) La variable peut être nullable ou non -nullable.

2
Geeta Gupta

Exemple de lateinit (ce qui est une initialisation tardive):

public class Late {

    lateinit var mercedes: Mercedes

    @SetUp fun setup() {
        mercedes = Mercedes()
    }
    @Test fun testing() {
        mercedes.do()
    }
}

Exemple de lazy (ce qui est une initialisation différée):

public class Lazy {

    val name: String by lazy { "Mercedes-Benz" }
}
0
ARGeo

Si vous utilisez un conteneur Spring et souhaitez initialiser un champ de bean non nullable, lateinit convient mieux.

    @Autowired
    lateinit var myBean: MyBean
0
mpprdev

Si vous utilisez une variable non modifiable, il vaut mieux initialiser avec by lazy { ... } ou val. Dans ce cas, vous pouvez être sûr qu'il sera toujours initialisé en cas de besoin et au plus 1 fois.

Si vous voulez une variable non NULL, cela peut changer sa valeur, utilisez lateinit var. Dans le développement Android, vous pouvez l'initialiser ultérieurement dans des événements tels que onCreate, onResume. Sachez que si vous appelez REST request et accédez à cette variable, une exception UninitializedPropertyAccessException: lateinit property yourVariable has not been initialized risque de se produire, car la demande peut s'exécuter plus rapidement que la variable ne pourrait s'initialiser.

0
CoolMind