Dziedziczenie
Wszystkie klasy w Kotlin
dziedziczą
po superklasie Any
(podobnie jak Object
w Java
), która dostarcza dla klas podtypów metody equals
, hashCode
oraz toString
. Klasa, która może być dziedziczona musi być oznaczona jako open
, a deklaracja dziedziczenia następuje w nagłówku klasy. Jeśli klasa ma konstruktor podstawowy (primary constructor) wówczas inicjalizacja klasy bazowej musi nastąpić w konstruktorze podstawowym klasy dziedziczącej. Gdy klasa nie posiada konstruktora podstawowego to we wszystkich jej konstruktorach dodatkowych (secondary constructor) musi przebiegać bezpośrednio inicjalizacja typu bazowego za pomocą słowa kluczowego super
lub pośrednio poprzez delegacje do innego konstruktora.
W procesie tworzenia obiektu klasy pochodnej wywoływany jest najpierw konstruktor klasy bazowej co ma miejsce przed inicjalizacją nadpisanych i nowych właściwości klasy pochodnej. Jeśli któraś z tych właściwości jest używana w procesie inicjalizacji klasy bazowej może to prowadzić do nieoczkiewanego zachowania lub błędu programu w czasie jego działania. Aby zapobiec takim sytuacją projektując klasę bazową należy unikać używania właściwości open w konstruktorze, inicjalizatorów właściwości oraz bloków init
.
Elementy klasy
Metody oraz właściwości, które zezwalają na nadpisywanie w klasach pochodnych podobnie jak klasy wymagają słowa kluczowego open
, a w miejscu ich nadpisywania adnotacji override
. Nadpisane metody i właściwości stają się jednocześnie otwarte na modyfikacje (oznaczone jako open), chyba że w ich deklaracji zostanie użyty modyfikator final
. Ponadto nadpisywanie właściwości może odbywać się przez inicjalizator lub właściwość z metodą dostepową, a zmienne z modyfikatorem val
mogą być nadpisane jako var
(lecz nie na odwrót).
Wielokrotne dziedziczenie
Podobnie jak w języku C++
(przeciwnie do Java
) w Kotlin
możliwe jest wielokrotne dziedziczenie. Klasa pochodna może wywoływać funkcje i właściwości klasy bazowej poprzez użycie słowa kluczowego super
. W przypadku, gdy klasa dziedziczy wiele implementacji tego samego elementu klasy wówczas musi go nadpisać, a aby odwołać się do implementacji konkretnej klasy bazowej należy użyć instrukcji super<NazwaKlasy>
.
Klasa abstrakcyjna
Klasy oraz jej elementy mogą być w Kotlin abstrakcyjne
poprzez zadeklarowanie ich jako abstract
. Jeśli klasa jest abstrakcyjna wówczas nie może posiadać żadnej instancji, natomiast funkcja abstrakcyjna nie posiada implementacji. Klasy abstrakcyjne są automatycznie oznaczone jako open. W przeciwieństwie do Java klasa abstrakcyjna nie musi posiadać elementu oznaczonego jako abstract. Klasa dziedzicząca musi nadpisać wszystkie abstrakcyjne elementy klasy bazowej.
Interfejsy
Interfejsy
w Kotlin podobnie jak w Java wprowadzają poziom abstrakcji w implementacji oraz sprawiają, że od grupy różnego typu obiektów można oczekiwać wspólnego zachowania. Deklaruje się je za pomocą słowa kluczowego interface
. W przeciwieństwie do klas abstrakcyjnych nie przechowują stanu. Mogą zawierać deklaracje metod abstrakcyjnych i metody z domyślną implementacją, a także właściwości abstrakcyjne lub właściwości z implementacją akcesorów. Klasa implementująca interfejs musi nadpisać wszystkie elementy abstrakcyjne interfejsów, które nie posiadają implementacji.
Interfejs może również dziedziczyć po innych interfejsach i tak samo jak w przypadku dziedziczenia klas, klasa implementująca interfejsy o tych samych elementach musi je nadpisać, a aby odwołać się do implementacji konkretnego interfejsu należy użyć instrukcji super<NazwaInterfejsu>
.
Rozszerzenia
Kotlin dostarcza mechanizmu deklaracji rozszerzenia
(extensions
), które umożliwiają rozszerzenie funkcjonalności klas (funkcje i właściwości) bez ich dziedziczenia czy stosowania wzorca typu Dekorator. Nierzadko są one wykorzystywane jako alternatywa dla klas typu Utils. Aby zadeklarować funkcję lub właściwość należy poprzedzić jej nazwę typem odbiorcy rozszerzenia. W ten sam sposób można deklarować i wywołać rozszerzenia dla companion object
. Słowo kluczowe this
odnosi się do bieżącego obiektu odbiorcy. Typ odbiorcy rozszerzenia może także nullable
.
Rozszerzenia nie modyfikują ani nie rozszerzają klasy, są wywoływane statycznie
i związane są z typem wyrażenia dla którego wywoływana jest funkcja, a nie typem argumentu. Rozszerzenie o tej samej sygnaturze co członek klasy nie powoduje jego nadpisania.
Rozszerzenia dla jednej klasy mogą być definiowane w ciele innej klasy co sprawia, że stają się członkiem klasy. Co więcej mogą być one zadeklarowane jako open
i nadpisane w klasach pochodnych. Instancja klasy w której jest definiowane rozszerzenie nazywa się dispatch receiver
, a obiekt typu odbiornika extension receiver
.