W Kotlinie każda klasa może mieć jeden konstruktor podstawowy (primary) i kilka konstruktorów drugorzędnych (secondary). Delegacja konstruktorów to mechanizm, w którym konstruktor drugorzędny musi bezwzględnie wywołać либо konstruktor podstawowy bezpośrednio, либо inny konstruktor drugorzędny tej samej klasy (a ostatecznie — konstruktor podstawowy). Jeśli klasa dziedziczy z innej, każdy konstruktor drugorzędny klasy potomnej musi jawnie delegować wywołanie do konstruktora klasy bazowej, jeśli jest to konieczne.
W Javie konstruktory mogą bezpośrednio wywoływać się nawzajem za pomocą this() lub super(), przeciążając konstruktory w różnych kombinacjach. W Kotlinie ta koncepcja została sformalizowana: klasa może mieć tylko jeden konstruktor podstawowy, a konstruktory drugorzędne mogą korzystać z logiki delegacji, którą należy dokładnie określić.
Niewłaściwa implementacja delegacji może prowadzić do błędów kompilacji: nie można nie wywołać konstruktora podstawowego, jeśli jest on zadeklarowany, ani nie wywołać konstruktora super w klasie dziedziczonej, jeśli klasa bazowa nie ma konstruktora domyślnego. Ważne jest, aby zrozumieć, w którym momencie wywoływane są bloki init, jak przekazywane są parametry i jak delegacja wpływa na kolejność inicjalizacji.
Przykład podstawowej delegacji:
class Person(val name: String) { constructor(name: String, age: Int) : this(name) { println("Secondary constructor: $name, $age") } }
Jeśli używane jest dziedziczenie:
open class Parent(val name: String) class Child : Parent { constructor(name: String) : super(name) { println("Child secondary: $name") } }
Czy konstruktor drugorzędny może nie delegować do niczego?
Nie, kompilator wymusi jawne wywołanie this(...) lub super(...), w przeciwnym razie wystąpi błąd.
W jakiej kolejności wykonywane są inicjalizacje i bloki init, jeśli używany jest konstruktor drugorzędny?
Pierwszy zawsze wywoływany jest konstruktor podstawowy i blok(i) init, następnie kod inicjalizacji konstruktora drugorzędnego.
class Demo(val value: String) { init { println("init block") } constructor(value: String, code: Int) : this(value) { println("secondary: $code") } } Demo("kotlin", 7) // Wynik: // init block // secondary: 7
Czy dziedzic może wywołać tylko konstruktor podstawowy rodzica, jeśli istnieje jego konstruktor drugorzędny?
Tak, ale tylko jawnie, przez super(...); konstruktory drugorzędne nie są bezpośrednio widoczne dla dziedziczących klas.
W projekcie konstruktor drugorzędny nie wywołuje konstruktora podstawowego; nie następuje kluczowa inicjalizacja (np. ustawienie wymaganych pól), co staje się przyczyną błędów i awarii w czasie wykonywania.
Zalety:
Wady:
Wszystkie konstruktory drugorzędne ściśle delegują przez this(...), potrzebna inicjalizacja jest centralizowana w konstruktorze podstawowym/init, struktura jest przejrzysta dla konserwacji.
Zalety:
Wady: