ProgrammationDéveloppeur Kotlin

Décrivez le mécanisme de l'initialisation des objets en Kotlin en utilisant des blocs init et le mot-clé 'super'. Quelle est la différence d'ordre d'initialisation par rapport à Java, quels sont les nuances concernant l'héritage et l'appel des constructeurs ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Les blocs init et l'appel de super en Kotlin sont responsables de l'initialisation correcte des objets en tenant compte de la hiérarchie des classes. Ce sont des mécanismes fondamentaux qui influencent le fonctionnement des constructions et de l'héritage.

Histoire de la question

En Java, les constructeurs peuvent être appelés explicitement à l'aide de super(...) ou implicitement par défaut. En Kotlin, tout est différent : il existe une séparation claire entre les constructeurs primaires et secondaires, et le bloc init est utilisé pour l'initialisation.

Problème

La principale difficulté est de comprendre quand les propriétés sont initialisées, quand les blocs init sont appelés et comment l'ordre d'appel des constructeurs est établi, en particulier lors de l'héritage des classes et de la combinaison des constructeurs primaires/secondaires.

Solution

  • En Kotlin, tous les délégations des constructeurs secondaires au constructeur primaire sont effectuées en premier, puis le constructeur de la super-classe est appelé, et enfin les blocs init sont exécutés ;
  • Pour passer des paramètres à super, vous devez les spécifier explicitement dans le constructeur ;
  • Il est important de se rappeler que les propriétés déclarées dans le corps de la classe sont initialisées avant l'exécution du bloc init.

Exemple de code :

open class Base(val name: String) { init { println("Base init: $name") } } class Derived(name: String, val age: Int): Base(name) { init { println("Derived init: $name, $age") } } fun main() { Derived("Ivan", 25) } // Sortie : // Base init: Ivan // Derived init: Ivan, 25

Caractéristiques clés :

  • Toutes les propriétés de la classe externe sont initialisées avant l'exécution du bloc init ;
  • D'abord, le constructeur de la super-classe est exécuté ;
  • Le constructeur secondaire doit appeler le constructeur primaire soit directement, soit via une chaîne.

Questions pièges.

Dans quel ordre les blocs init sont-ils exécutés dans la chaîne d'héritage ?

D'abord, les blocs init de la super-classe sont exécutés après l'initialisation de ses propriétés, puis les blocs init de la sous-classe après ses propres propriétés.

Exemple de code :

open class A { init { println("A") } } class B: A() { init { println("B") } } fun main() { B() } // A -> B

Peut-on appeler super() en dehors d'un constructeur en Kotlin ?

Non, l'appel du super-classe se fait uniquement dans le cadre de la déclaration du constructeur en fournissant des paramètres au super-constructeur.

L'initialisation d'un constructeur secondaire peut-elle ne pas appeler le constructeur primaire en Kotlin ?

Non, tous les constructeurs secondaires doivent déléguer l'appel soit à un autre constructeur secondaire, soit au constructeur primaire. Le primaire, à son tour, appelle le super-constructeur.

Erreurs typiques et anti-modèles

  • Définir une propriété calculée avant d'appeler le super-constructeur entraînera une erreur ;
  • Erreurs d'ordre d'initialisation en tentant d'utiliser des propriétés non initialisées ;
  • Dépendances circulaires dans les constructeurs secondaires.

Exemple de la vie réelle

Cas négatif

Un développeur essaie d'accéder à une propriété, initialisée dans une sous-classe, dans le bloc init de la super-classe :

open class A { init { println(f()) } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s }``` **Avantages :** - Utilisation du polymorphisme. **Inconvénients :** - La propriété s n'est pas encore initialisée lors de l'appel du init de la super-classe - le résultat est null ou une erreur. ## Cas positif Séparation de l'initialisation pour éviter d'appeler des méthodes ou des propriétés redéfinies depuis le constructeur : ```kotlin open class A { init { println("init A") } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s init { println(f()) } }``` **Avantages :** - Pas d'accès à des données non initialisées ; - Ordre d'initialisation garanti. **Inconvénients :** - Augmentation du volume de code pour une initialisation correcte.