I blocchi init e la chiamata a super in Kotlin sono responsabili della corretta inizializzazione degli oggetti tenendo conto della gerarchia delle classi. Questi sono meccanismi fondamentali che influenzano il funzionamento delle strutture e dell'ereditarietà.
In Java i costruttori possono essere chiamati esplicitamente con super(...) o implicitamente per impostazione predefinita. In Kotlin è tutto diverso: c'è una netta separazione tra i costruttori primari e secondari, e il blocco init è usato per l'inizializzazione.
La difficoltà principale è capire correttamente quando vengono inizializzati le proprietà, quando vengono chiamati i blocchi init e come è strutturato l'ordine di chiamata dei costruttori, specialmente quando si ereditarie classi e si combinano costruttori primari/secondari.
Esempio di codice:
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) } // Output: // Base init: Ivan // Derived init: Ivan, 25
Caratteristiche principali:
In che ordine vengono eseguiti i blocchi init nella catena di ereditarietà?
Prima vengono eseguiti i blocchi init della superclasse dopo l'inizializzazione delle sue proprietà, quindi i blocchi init della sottoclasse dopo le proprie proprietà.
Esempio di codice:
open class A { init { println("A") } } class B: A() { init { println("B") } } fun main() { B() } // A -> B
È possibile chiamare super() non da un costruttore in Kotlin?
No, la chiamata alla superclasse avviene solo come parte della dichiarazione del costruttore fornendo i parametri al supercostruttore.
La inizializzazione del costruttore secondario può non chiamare il primario in Kotlin?
No, tutti i costruttori secondari devono delegare la chiamata a un altro costruttore secondario o al costruttore primario. Il primario, a sua volta, chiama il supercostruttore.
Un sviluppatore tenta di accedere a una proprietà inizializzata nella sottoclasse nel blocco init della superclasse:
open class A { init { println(f()) } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s }``` **Pro:** - Uso del polimorfismo. **Contro:** - La proprietà s non è ancora stata inizializzata durante la chiamata init della superclasse — il risultato è null o un errore. ## Caso positivo Separare l'inizializzazione in modo da non chiamare metodi o proprietà sovrascrivibili dal costruttore: ```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()) } }``` **Pro:** - Nessun accesso a dati non inizializzati; - Garanzia dell'ordine di inizializzazione. **Contro:** - Aumenta la quantità di codice per una corretta inizializzazione.