In Kotlin, ogni classe può avere un solo primary constructor (principale) e diversi secondary constructors (secondari). La delegazione dei costruttori è un meccanismo in cui un constructor secondario deve necessariamente chiamare direttamente un primary constructor oppure un altro secondary constructor della stessa classe (e, in definitiva, il primary). Se una classe eredita da un'altra, ogni secondary constructor della classe figlia deve esplicitamente delegare la chiamata al costruttore della superclasse, se necessario.
In Java i costruttori possono chiamarsi direttamente l'un l'altro usando this() o super(), sovraccaricando i costruttori in varie combinazioni. In Kotlin questo concetto è stato formalizzato: una classe può avere solo un primary constructor, i costruttori secondari possono utilizzare la logica di delegazione che deve essere esplicitamente indicata.
Un'implementazione errata della delegazione può portare a errori di compilazione: non è possibile omettere la chiamata al primary constructor se è dichiarato, né chiamare il costruttore super in una classe derivata se la classe base non ha un costruttore predefinito. È importante comprendere in quale momento vengono chiamati i blocchi init, come vengono passato i parametri e come la delegazione influisce sull'ordine di inizializzazione.
Esempio di delegazione di base:
class Person(val name: String) { constructor(name: String, age: Int) : this(name) { println("Constructor secondario: $name, $age") } }
Se viene utilizzata l'ereditarietà:
open class Parent(val name: String) class Child : Parent { constructor(name: String) : super(name) { println("Child secondario: $name") } }
Il costruttore secondario può non delegare affatto?
No, il compilatore richiederà esplicitamente di chiamare this(...) o super(...), in caso contrario ci sarà un errore.
In quale ordine vengono eseguite le inizializzazioni e i blocchi init se viene utilizzato un costruttore secondario?
Per primo viene sempre chiamato il primary constructor e il (i) blocchi init, quindi il codice del costruttore secondario di inizializzazione.
class Demo(val value: String) { init { println("blocco init") } constructor(value: String, code: Int) : this(value) { println("secondario: $code") } } Demo("kotlin", 7) // output: // blocco init // secondario: 7
Il discendente può chiamare solo il costruttore primario del genitore, se esiste il suo costruttore secondario?
Sì, ma solo esplicitamente, tramite super(...); i secondari non sono visibili direttamente ai discendenti.
Nel progetto il costruttore secondario non chiama il primario, l'inizializzazione vitale (ad esempio, impostazione di campi obbligatori) non avviene, causando bug e crash a runtime.
Vantaggi:
Svantaggi:
Tutti i costruttori secondari delegano rigorosamente tramite this(...), la corretta inizializzazione è centralizzata in primary/init, la struttura è trasparente per la manutenzione.
Vantaggi:
Svantaggi: