ProgrammationDéveloppeur Backend

Décrivez les particularités des déclarations de constantes et du travail avec des objets compagnons (companion objects) en Kotlin, y compris les restrictions et les nuances.

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

La déclaration de constantes et l'utilisation des companion objects sont des concepts importants en Kotlin, qui ont remplacé les membres statiques habituels en Java et les difficultés associées à la traversée des paradigmes de la programmation orientée objet et de la programmation fonctionnelle.

Historique de la question : En Java, les constantes sont généralement des champs static final, et les méthodes statiques sont utilisées pour des fonctions utilitaires ou de fabrication. En Kotlin, à la place de static, on a introduit object et companion object, et pour les constantes de compilation, le mot clé const.

Problème : Il est nécessaire de déclarer des valeurs qui ne dépendent pas d'une instance de classe, ainsi que d'organiser des méthodes de fabrication et un état statique, sans compromettre l'intégrité de la programmation orientée objet.

Solution : Les objets compagnons (companion object) sont déclarés à l'intérieur d'une classe et permettent de placer des membres communs à toutes les instances :

Exemple de code :

class MyClass { companion object { const val DEFAULT_LIMIT = 10 fun create(): MyClass = MyClass() } } val limit = MyClass.DEFAULT_LIMIT val instance = MyClass.create()

Particularités clés :

  • Tout ce qui est à l'intérieur du companion object se comporte comme des membres statiques en Java, mais conserve l'intégration OO et la possibilité d'héritage/d'interfaces
  • Pour les constantes de compilation à l'intérieur du companion object, l'étiquette const est obligatoire
  • L'objet compagnon lui-même est accessible comme un objet (la référence peut être conservée) et peut implémenter des interfaces

Questions pièges.

Un companion object peut-il avoir plusieurs instances dans une classe ?

Non, une classe ne peut avoir qu'un seul companion object. Tenter d'en déclarer un deuxième entraînera une erreur de compilation. Mais à l'intérieur du companion object, tout nombre de méthodes/propriétés est autorisé.

Peut-on initialiser des variables lateinit à l'intérieur du companion object ?

Non, car les propriétés avec const ou les variables à l'intérieur du companion object doivent être initialisées immédiatement ou être val/var avec une initialisation explicite. lateinit n'est pas autorisé pour les propriétés à l'intérieur du companion object.

Un companion object peut-il avoir son propre nom et quand cela est-il nécessaire ?

Oui, le nom du companion object est donné explicitement si l'on souhaite y accéder par nom ou, par exemple, implémenter des interfaces. Dans d'autres cas, il est optionnel. Exemple :

class Foo { companion object Factory { fun create(): Foo = Foo() } } val instance = Foo.Factory.create()

Erreurs typiques et anti-patrons

  • Utiliser des variables statiques mutables dans le companion object – peut entraîner des conditions de course dans le code multithread
  • Mélanger les constantes de compilation (const) et les valeurs d'exécution dans un même objet
  • Des companion objects inutiles, lorsque des fonctions/propriétés au niveau du fichier suffisent

Exemple de la vie réelle

Cas négatif

Toute la fonctionnalité auxiliaire et les variables globales du programme sont placées dans le companion object, des var sont utilisées au lieu de val/const :

Avantages :

  • Toutes les fonctions et variables "statiques" au même endroit, faciles à trouver.

Inconvénients :

  • Difficultés de test, état global et peut être accidentellement modifié ailleurs dans le code.

Cas positif

Seules des constantes de compilation (const val) et des fonctions pures sont utilisées à l'intérieur du companion object, tout ce qui est mutable est soit localisé, soit passé par DI :

Avantages :

  • Code prévisible et sécurisé, architecture claire, le niveau d'encapsulation augmente.

Inconvénients :

  • Parfois, il est nécessaire de créer des objets au niveau du fichier pour des utilitaires globaux, ce qui nécessite un peu plus de boilerplate.