Init-Blöcke und der Aufruf von super in Kotlin sind verantwortlich für die korrekte Initialisierung von Objekten unter Berücksichtigung der Klassenhierarchie. Dies sind grundlegende Mechanismen, die das Verhalten von Konstruktionen und Vererbung beeinflussen.
In Java können Konstruktoren explizit mit super(...) oder implizit durch Standardaufrufe aufgerufen werden. In Kotlin ist alles anders: Es gibt eine klare Trennung zwischen primären und sekundären Konstruktoren, und der Init-Block wird zur Initialisierung verwendet.
Die Hauptschwierigkeit besteht darin, richtig zu verstehen, wann Eigenschaften initialisiert werden, wann Init-Blöcke aufgerufen werden und wie die Reihenfolge des Aufrufs von Konstruktoren insbesondere bei der Vererbung von Klassen und der Kombination von primären/sekundären Konstruktoren aufgebaut wird.
Beispielcode:
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) } // Ausgabe: // Base init: Ivan // Derived init: Ivan, 25
Wichtige Merkmale:
In welcher Reihenfolge werden die Init-Blöcke in der Vererbungskette ausgeführt?
Zuerst werden die Init-Blöcke der Superklasse nach der Initialisierung ihrer Eigenschaften ausgeführt, dann die Init-Blöcke der Unterklasse nach ihren eigenen Eigenschaften.
Beispielcode:
open class A { init { println("A") } } class B: A() { init { println("B") } } fun main() { B() } // A -> B
Kann man super() nicht aus einem Konstruktor in Kotlin aufrufen?
Nein, der Aufruf der Superklasse erfolgt nur als Teil der Konstruktorbeschreibung, wobei Parameter an den Superkonstruktor übergeben werden.
Kann die Initialisierung des sekundären Konstruktors den primären in Kotlin nicht aufrufen?
Nein, alle sekundären Konstruktoren müssen den Aufruf entweder an einen anderen sekundären oder den primären Konstruktor delegieren. Der PrimärConstructor wiederum ruft den Superkonstruktor auf.
Ein Entwickler versucht, auf eine Eigenschaft zuzugreifen, die im Unterklasse initialisiert wird, im Init-Block der Superklasse:
open class A { init { println(f()) } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s }``` **Vorteile:** - Verwendung von Polymorphismus. **Nachteile:** - Die Eigenschaft s ist noch nicht initialisiert, zum Zeitpunkt des Aufrufs des Init-Blocks der Superklasse — das Ergebnis ist null oder ein Fehler. ## Positiver Fall Die Initialisierung so zu trennen, dass keine überladbaren Methoden oder Eigenschaften aus dem Konstruktor aufgerufen werden: ```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()) } }``` **Vorteile:** - Kein Zugriff auf nicht initialisierte Daten; - Garantierte Reihenfolge der Initialisierung. **Nachteile:** - Erhöht die Menge an Code für eine korrekte Initialisierung.