ProgrammationDéveloppeur Kotlin

Comment le mot-clé 'inline' est-il implémenté par rapport aux classes (valeur classe/classe inline) en Kotlin ? Quelles sont les limitations, comment ces classes fonctionnent-elles au niveau du bytecode, quand et pourquoi est-ce qu'il faut les utiliser ? Donnez un exemple et expliquez les difficultés typiques.

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

En Kotlin, classe inline (à partir de Kotlin 1.5 — le terme "classe valeur"), permet de créer des wrappers autour des types avec des coûts minimaux. Lors de la compilation, ces classes sont sous le capot remplacées par leur valeur interne (value) pour éviter les frais de création d'objets.

Limitations et caractéristiques :

  • Il ne peut y avoir qu'une seule propriété dans le constructeur principal.
  • Il n'est pas permis de stocker des références sur l'égalité référentielle (=== ne fonctionne pas comme d'habitude).
  • La classe de valeur ne peut pas être une classe dérivée et ne peut pas avoir d'état autre que sa valeur.
  • Tous les types génériques et les API plateforme ne peuvent pas travailler avec des classes inline/de valeur sans boxing.
  • La classe de valeur ne peut pas avoir de bloc init, de champs autres que la valeur et seulement des fonctions minimales.

Exemple :

@JvmInline value class UserId(val value: String) fun getUser(id: UserId) { println("Chargement de l'utilisateur avec l'id : ${id.value}") } val id = UserId("XYZ") getUser(id) // Sous le capot, cela fonctionne simplement avec String !

Quand utiliser :

  • Assurer la sécurité des types pour les identifiants, valeurs spéciales.
  • Améliorer les performances lors du traitement de millions de tels wrappers (aucun objet n'est créé).

Question piège

Peut-on hériter de la classe de valeur ou l'utiliser dans une hiérarchie d'interfaces/classes abstraites ?

Réponse : Non, la classe de valeur ne peut pas hériter d'autres classes (à l'exception des interfaces), ne peut pas être ouverte à l'héritage, ne permet pas de bloc init et d'autres champs non statiques. La seule option disponible est d'implémenter des interfaces.

Exemple :

interface Validatable { fun isValid(): Boolean } @JvmInline value class Email(val raw: String) : Validatable { override fun isValid() = raw.contains("@") }

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet


Histoire

Une application Android a soudainement augmenté son temps de démarrage après avoir ajouté des classes de valeur aux paramètres Parcelable : il s'est avéré qu'un @Parcelize incorrect avec une classe de valeur conduisait à un boxing/unboxing à chaque étape de la sérialisation, annulant les avantages de l'inline.


Histoire

Un microservice a commencé à utiliser activement la classe de valeur pour UserId et ProductId pour des raisons de sécurité des types, mais à de nombreux endroits, des fonctions génériques nécessitaient une réflexion qui ne fonctionnait pas avec "l'enveloppe". Les tests unitaires ont commencé à échouer de manière inattendue, des ClassCastException sont apparues.


Histoire

Le code migré de Java a commencé à remplacer des classes de domaine internes par des classes de valeur pour l'optimisation, mais leur utilisation en tant que champs nullable a conduit à des exceptions null pointer inattendues, car la classe de valeur ne peut être null que si la valeur extérieure est également null, ce qui brisait les anciens invariants.