
Constructeurs et initialisation (`init`)
Apprenez à initialiser vos objets en Kotlin avec les constructeurs primaires et secondaires, et à exécuter du code d'initialisation avec les blocs `init`.
Introduction : Donner un état initial aux objets
Lorsque nous créons une instance (un objet) d'une classe, il est très fréquent de vouloir lui donner un état initial spécifique dès sa création. Par exemple, quand on crée un objet `User`, on veut probablement lui assigner un nom et un âge immédiatement, plutôt que d'avoir des valeurs par défaut ou `null` à remplir plus tard. C'est le rôle principal des constructeurs.
Un constructeur est un bloc de code spécial au sein d'une classe, exécuté lors de l'instanciation (`NomDeLaClasse()`). Il est responsable de la préparation du nouvel objet, notamment en initialisant ses propriétés avec les valeurs fournies lors de l'appel.
Kotlin propose une approche flexible et concise pour définir les constructeurs, avec une distinction importante entre le constructeur primaire (le principal, souvent déclaré de manière très compacte) et les constructeurs secondaires (optionnels, pour offrir d'autres façons de créer l'objet). De plus, Kotlin fournit les blocs `init` pour exécuter du code d'initialisation plus complexe qui pourrait être nécessaire lors de la création de l'objet.
Le constructeur primaire : concision et déclaration de propriétés
Chaque classe Kotlin peut avoir au maximum un constructeur primaire. Il est déclaré directement après le nom de la classe, dans les parenthèses `()`. Ses paramètres définissent les données qui peuvent (ou doivent) être fournies lors de la création de l'objet.
La grande force du constructeur primaire en Kotlin est qu'il permet de déclarer et d'initialiser des propriétés de manière extrêmement concise. Si vous préfixez un paramètre du constructeur primaire avec `val` ou `var`, Kotlin fait automatiquement deux choses : il crée une propriété du même nom dans la classe et il initialise cette propriété avec la valeur passée à ce paramètre lors de l'instanciation.
// Classe User avec un constructeur primaire
// 'name' et 'age' sont des paramètres du constructeur primaire
// 'val name: String' déclare une propriété immutable 'name' initialisée par le paramètre
// 'var age: Int' déclare une propriété mutable 'age' initialisée par le paramètre
class User(val name: String, var age: Int) {
// Le corps de la classe peut être vide si seules des propriétés sont déclarées
// via le constructeur primaire
fun printInfo() {
println("Utilisateur: $name, Age: $age")
}
}
// Classe alternative où les paramètres ne deviennent pas automatiquement propriétés
class Point(xCoord: Int, yCoord: Int) { // xCoord et yCoord sont juste des paramètres
// Il faut déclarer les propriétés séparément et les initialiser
val x: Int = xCoord
val y: Int = yCoord
// Note: La première syntaxe (User) est beaucoup plus idiomatique en Kotlin !
}
fun main() {
// Instanciation en utilisant le constructeur primaire
val user1 = User("Alice", 30)
val user2 = User("Bob", 25)
user1.printInfo() // Affiche: Utilisateur: Alice, Age: 30
user2.printInfo() // Affiche: Utilisateur: Bob, Age: 25
user1.age += 1 // Modification de la propriété 'var' OK
user1.printInfo() // Affiche: Utilisateur: Alice, Age: 31
// user1.name = "Alicia" // Erreur: Val cannot be reassigned
val p1 = Point(10, 20)
println("Point: (${p1.x}, ${p1.y})") // Affiche: Point: (10, 20)
}La syntaxe consistant à déclarer les propriétés directement dans le constructeur primaire avec `val`/`var` est la méthode préférée et la plus courante en Kotlin pour définir des classes simples avec un état initial.
Le bloc d'initialisation `init` : exécuter du code à la création
Parfois, l'initialisation d'un objet nécessite plus qu'une simple assignation de valeurs aux propriétés. Vous pourriez avoir besoin d'effectuer des validations, de calculer des valeurs dérivées, d'afficher un message de log, ou d'exécuter d'autres logiques lors de la création de l'objet. C'est là qu'intervient le bloc `init`.
Vous pouvez placer un ou plusieurs blocs `init` dans le corps de votre classe. Le code contenu dans ces blocs est exécuté lors de l'instanciation de l'objet, juste après l'appel au constructeur primaire (et l'initialisation des propriétés déclarées dans celui-ci). Les blocs `init` sont exécutés dans l'ordre où ils apparaissent dans la classe.
Les paramètres du constructeur primaire (même ceux qui ne sont pas déclarés comme propriétés avec `val`/`var`) sont accessibles depuis les blocs `init`.
class ValidatedUser(val username: String, initialAge: Int) {
var age: Int = initialAge // Propriété initialisée par un paramètre non-val/var
// Bloc d'initialisation pour valider l'âge
init {
println("Bloc init 1: Validation de l'âge...")
// 'initialAge' (paramètre du constructeur) est accessible ici
require(initialAge >= 0) { "L'âge ne peut pas être négatif. Reçu: $initialAge" }
println("Age validé: $age")
}
// Autre propriété calculée lors de l'initialisation
val normalizedUsername = username.trim().lowercase()
init {
println("Bloc init 2: Normalisation du nom d'utilisateur...")
println("Nom d'utilisateur normalisé: $normalizedUsername")
}
fun describe() {
println("User: $normalizedUsername, Age: $age")
}
}
fun main() {
println("Création de user1...")
val user1 = ValidatedUser(" Alice ", 30)
user1.describe()
println("\nCréation de user2...")
try {
val user2 = ValidatedUser("Bob", -5)
} catch (e: IllegalArgumentException) {
println("Erreur lors de la création de user2: ${e.message}")
}
}Les blocs `init` sont donc parfaits pour encapsuler toute logique qui doit s'exécuter lors de la création d'un objet, au-delà de la simple initialisation des propriétés.
Constructeurs secondaires : alternatives pour l'instanciation
En plus du constructeur primaire (unique ou absent), une classe peut déclarer un ou plusieurs constructeurs secondaires en utilisant le mot-clé `constructor` à l'intérieur du corps de la classe.
Les constructeurs secondaires sont utiles lorsque vous voulez offrir plusieurs manières de créer un objet, par exemple avec différents ensembles de paramètres, ou pour fournir des valeurs par défaut si le constructeur primaire n'en a pas (bien que les arguments par défaut dans le constructeur primaire soient souvent une meilleure solution en Kotlin).
Une règle cruciale s'applique aux constructeurs secondaires : chaque constructeur secondaire doit déléguer (directement ou indirectement) à un autre constructeur de la même classe (soit le primaire, soit un autre secondaire qui finira par appeler le primaire) en utilisant la syntaxe `: this(...)`. Cette délégation assure que le constructeur primaire (et ses blocs `init` associés) est toujours exécuté, garantissant une initialisation cohérente de l'objet.
class Rectangle(val width: Double, val height: Double) { // Constructeur primaire
val area: Double = width * height
init {
println("Rectangle créé: L=$width, H=$height, Aire=$area")
}
// Constructeur secondaire pour créer un carré
constructor(side: Double) : this(side, side) { // Délègue au constructeur primaire
println("Constructeur secondaire (carré) appelé.")
// Le code ici est exécuté APRES le constructeur primaire et les blocs init
}
// Autre constructeur secondaire (exemple)
constructor(widthInt: Int, heightInt: Int) : this(widthInt.toDouble(), heightInt.toDouble()) {
println("Constructeur secondaire (depuis Int) appelé.")
}
}
fun main() {
println("-- Création via primaire --")
val rect1 = Rectangle(10.0, 5.0)
println("\n-- Création via secondaire (carré) --")
val square = Rectangle(7.0)
println("\n-- Création via secondaire (Int) --")
val rect2 = Rectangle(8, 4)
}Bien que possibles, les constructeurs secondaires sont moins fréquents en Kotlin idiomatique que dans d'autres langages comme Java. On leur préfère souvent l'utilisation d'arguments par défaut dans le constructeur primaire ou des fonctions "factory" statiques (souvent définies dans un objet compagnon) pour créer des instances de manières différentes.
Récapitulatif : initialisation contrôlée
Pour contrôler comment les objets sont créés et initialisés en Kotlin :
- Le constructeur primaire (après le nom de la classe) est la méthode principale. Il peut déclarer et initialiser directement des propriétés (`val`/`var`).
- Les blocs `init` permettent d'exécuter du code (validation, calculs, logs) lors de l'initialisation via le constructeur primaire.
- Les constructeurs secondaires (`constructor(...)`) offrent des moyens alternatifs d'instanciation mais doivent obligatoirement déléguer (`: this(...)`) à un autre constructeur (typiquement le primaire).
Privilégier le constructeur primaire avec des arguments par défaut est souvent la solution la plus concise et la plus lisible en Kotlin.