Funkcje rozszerzające (extension functions) — jedna z kluczowych różnic Kotlinu w porównaniu do Javy, po raz pierwszy pojawiła się od pierwszych wersji języka pod wpływem C#. Pozwalają na dodawanie nowych metod do istniejących klas bez konieczności dziedziczenia czy modyfikacji pierwotnej klasy. To znacznie zwiększa ekspresyjność i zwięzłość kodu.
W Javie i wielu innych językach, nie ma możliwości dodawania metod do już istniejącej klasy (np. String czy List) bez użycia dziedziczenia lub wzorca Decorator. Kotlin rozwiązuje ten problem za pomocą mechanizmu funkcji rozszerzających.
Wiele klas biblioteki (np. String, Int, List) nie można zmieniać bezpośrednio. Często wymagane są dodatkowe metody, aby uczynić kod bardziej czytelnym i uniknąć dublowania logiki. Jednak funkcje rozszerzające nie zmieniają rzeczywistej klasy — są tworzone statycznie i nie mają dostępu do prywatnych/chronionych członów.
W Kotlinie, deklaracja funkcji rozszerzającej wygląda tak:
fun String.lastChar(): Char = this[this.length - 1]
Teraz można wywołać tę metodę na dowolnym Stringu:
"Kotlin".lastChar() // zwróci 'n'
Czy funkcję rozszerzającą można nadpisać w klasie pochodnej?
Nie. Funkcje rozszerzające nie są członami klasy, nie można ich nadpisywać za pomocą override: ich statyczność objawia się nawet dla potomków — kompilator wybiera funkcję na podstawie typu wyrażenia, a nie rzeczywistego typu obiektu.
open class Base class Derived : Base() fun Base.foo() = "base" fun Derived.foo() = "derived" fun printFoo(b: Base) { println(b.foo()) } val d = Derived() printFoo(d) // wyświetli "base"
Czy funkcje rozszerzające mogą zmieniać stan obiektu?
Funkcje rozszerzające nie mogą uzyskać dostępu do prywatnego/chronionego stanu, jednak jeśli typ jest mutable (np. List lub własna klasa), mogą zmieniać dostępny zewnętrzny stan przez metody publiczne.
fun MutableList<Int>.addTwice(elem: Int) { add(elem) add(elem) }
Czy funkcje rozszerzające kolidują z członami klasy o tej samej nazwie?
Jeżeli nazwa funkcji rozszerzającej pokrywa się z nazwą rzeczywistej metody, priorytet ma człon klasy. Funkcja rozszerzająca zostanie wywołana tylko wtedy, gdy człony są nieobecne lub niewidoczne.
class Foo { fun bar() = "member" } fun Foo.bar() = "extension" val f = Foo() println(f.bar()) // "member"
Programista rozszerza standardowy String funkcją, która ustawia specyficzny dla projektu format, i używa jej wszędzie — podczas gdy inni programiści nie zauważają, że to nie standardowa operacja klasy String.
Zalety:
Wady:
Rozszerzenie standardowej klasy metodą narzędziową, która jest dokładnie zlokalizowana w pakiecie utils i dobrze udokumentowana. Użycie jest ograniczone do wyraźnie określonych fragmentów kodu.
Zalety:
Wady: