Funkcja rozszerzająca w Kotlin: co to jest, składnia i zastosowanie

Autor: IT Sectr Opublikowano: 2026-06-21 Czas czytania: 9 min

funkcja rozszerzająca — mechanizm Kotlin, pozwalający dodawać nowe metody do istniejących klas bez dziedziczenia i bez zmiany kodu źródłowego. Funkcja jest deklarowana z prefiksem w postaci typu-odbiorcy i wywoływana jak zwykła metoda tego typu. Według Kotlin Documentation, 2025, funkcje rozszerzające są kompilowane do statycznych metod z pierwszym parametrem-odbiorcą, co gwarantuje zerowy narzut w czasie wykonania w porównaniu ze zwykłymi wywołaniami.

Najważniejsze

  • Funkcja rozszerzająca — metoda z typem odbiorcy, wywoływana jako członek klasy bez dziedziczenia
  • Typ odbiorcy jest wskazywany przez kropkę przed nazwą funkcji: fun Type.method()
  • Statyczna dyspozytoryzacja — funkcja rozszerzająca nie jest wirtualna, wywoływana według typu zmiennej
  • Import jest wymagany: funkcje rozszerzające nie są członkami klasy i wymagają importu
  • Członkowie klasy mają priorytet: jeśli metoda jest zgodna — wywoływany jest członek klasy, a nie rozszerzenie

Co to jest funkcja rozszerzająca w Kotlin?

Funkcja rozszerzająca to funkcja zadeklarowana poza klasą, ale wywoływana jako jej metoda. Przyjmuje typ-odbiorcę, do którego dodawana jest nowa metoda. Wewnątrz funkcji słowo kluczowe this odnosi się do instancji typu odbiorcy. Takie podejście pozwala rozszerzać klasy finalne, klasy z bibliotek zewnętrznych, a nawet typy prymitywne bez tworzenia opakowań.

W przeciwieństwie do Javy, gdzie dodanie metody do istniejącej klasy wymaga albo dziedziczenia, albo statycznej klasy narzędziowej, Kotlin oferuje eleganckie rozwiązanie bez boilerplate. Według danych badania Kotlin Foundation (2024), funkcje rozszerzające znajdują się w pierwszej piątce najczęściej używanych możliwości języka — stosuje je 78% programistów Kotlin w codziennej pracy.

Kompilator Kotlin przekształca funkcję rozszerzającą w metodę statyczną, gdzie pierwszym argumentem przekazywana jest instancja odbiorcy. Oznacza to, że funkcje rozszerzające nie zmieniają kodu bajtowego klasy docelowej i nie naruszają enkapsulacji — dostępne są tylko publiczne pola i metody odbiorcy.

Używaj funkcji rozszerzających do pisania czystych funkcji narzędziowych, które logicznie są powiązane z określonym typem, ale nie mogą być dodane do jego kodu źródłowego.

Składnia funkcji rozszerzającej: deklaracja i wywołanie

Podstawowa składnia funkcji rozszerzającej: nazwa typu-odbiorcy, kropka, nazwa funkcji, następnie parametry i typ zwracany. Wewnątrz funkcji odbiorca jest dostępny przez this.

kotlin
// Funkcja rozszerzająca dla String
fun String.isEmail(): Boolean {
    return this.contains("@") && this.contains(".")
}

// Wywoływana jako zwykła metoda String
val result = "user@example.com".isEmail() // true

W tym przykładzie isEmail() staje się dostępne dla wszystkich stringów. Wewnątrz funkcji this to sam string, dla którego wywoływana jest metoda. Kotlin pozwala pomijać this w większości przypadków — można po prostu napisać contains("@") zamiast this.contains("@").

Funkcja rozszerzająca z parametrami

Funkcje rozszerzające mogą przyjmować dodatkowe parametry i zwracać wartości dowolnego typu. To czyni je pełnoprawnymi funkcjami, a nie tylko lukrem składniowym.

kotlin
fun List<Int>.defaultIfEmpty(default: Int): List<Int> {
    if (this.isEmpty()) return listOf(default)
    return this
}

val populated = listOf(1, 2).defaultIfEmpty(0)
val empty = listOf<Int>().defaultIfEmpty(0)

Parametr default określa wartość zwracaną dla pustej listy. Funkcja rozszerzająca może być generyczna — typ List jest uogólniony, choć w przykładzie używana jest konkretna List.

Statyczna dyspozytoryzacja i odbiorca

Funkcje rozszerzające w Kotlin używają statycznej dyspozytoryzacji, a nie wirtualnej. To kluczowa różnica w porównaniu ze zwykłymi metodami klas. Która funkcja rozszerzająca zostanie wywołana, jest określane na etapie kompilacji na podstawie statycznego typu zmiennej, a nie jej rzeczywistego typu w czasie wykonania.

kotlin
open class Animal
class Dog : Animal()

fun Animal.speak() = "Dźwięk zwierzęcia"
fun Dog.speak() = "Szczekanie"

fun test() {
    val animal: Animal = Dog()
    println(animal.speak()) // „Dźwięk zwierzęcia” — typ statyczny Animal

    val dog: Dog = Dog()
    println(dog.speak()) // „Szczekanie” — typ statyczny Dog
}

Mimo że zmienna animal wskazuje na instancję Dog, wywoływana jest funkcja rozszerzająca dla Animal, ponieważ statyczny typ zmiennej to Animal. Gdyby speak() był wirtualną metodą klasy, zostałoby wywołane Dog.speak(). Ta właściwość jest ważna przy projektowaniu API opartego na funkcjach rozszerzających.

Import i zakres widoczności funkcji rozszerzających

Funkcje rozszerzające nie są członkami klasy — to zwykłe funkcje, które wymagają importu, aby można było ich używać w innym pliku. Kotlin oferuje dwa warianty importu: po nazwie funkcji lub z zmianą nazwy.

kotlin
// Import po nazwie
import com.example.extensions.isEmail

// Import z aliasem (aby rozwiązać konflikty)
import com.example.extensions.isEmail as isValidEmail

// Wywołanie po imporcie
val valid = "test@test.com".isEmail()

Zmiana nazwy przez as jest przydatna przy konfliktach nazw — na przykład, jeśli dwie biblioteki udostępniają funkcję rozszerzającą o tej samej nazwie dla tego samego typu. W takim przypadku można zaimportować jedną z nich pod inną nazwą i wywoływać przez nową nazwę.

Zakres widoczności

Funkcje rozszerzające można deklarować na różnych poziomach:

PoziomWidocznośćPrzykład
Top-levelCały projekt po imporciefun String.isEmail()
Member extensionWewnątrz klasy właścicielaclass A { fun B.ext() }
LocalWewnątrz funkcjifun test() { fun String.ext() }

Member extension functions — szczególny przypadek, gdy funkcja rozszerzająca jest deklarowana wewnątrz innej klasy. W tym przypadku wewnątrz funkcji rozszerzającej dostępne są zarówno odbiorca (this funkcji), jak i członkowie zewnętrznej klasy.

Priorytet członków klasy nad rozszerzeniami

Jeśli klasa ma metodę o tej samej sygnaturze co funkcja rozszerzająca, zawsze wywoływany jest członek klasy. Funkcja rozszerzająca nigdy nie nadpisuje metody klasy — to decyzja architektoniczna zapobiegająca przypadkowym nadpisywaniom.

kotlin
class User {
    fun greet() = "Witaj z klasy"
}

fun User.greet() = "Hello from extension"

fun main() {
    val user = User()
    println(user.greet()) // „Witaj z klasy”
}

Nawet jeśli funkcja rozszerzająca jest zdefiniowana później i ma tę samą sygnaturę, kompilator wybierze metodę klasy. Kompilator Kotlin wyświetla ostrzeżenie przy wykryciu takiej sytuacji. Jedynym sposobem wywołania funkcji rozszerzającej przy obecności metody klasy jest odwołanie się do niej jak do zwykłej funkcji: greet(user).

Praktyczne przykłady funkcji rozszerzających

W programowaniu na Androida funkcje rozszerzające stały się standardowym narzędziem do pracy z View, Context i fragmentami. Biblioteka Android KTX jest zbudowana na funkcjach rozszerzających, dostarczając wygodnych opakowań dla API Androida.

kotlin
// Funkcja rozszerzająca do pracy z View
fun View.show() {
    visibility = View.VISIBLE
}

fun View.hide() {
    visibility = View.GONE
}

// Funkcja rozszerzająca dla Context — toast
fun Context.toast(message: String) {
    Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}

// Funkcja rozszerzająca jako konstruktor DSL
fun String.colorFormat(color: String): String =
    "$color$this\u001b[0m"

Funkcja rozszerzająca toast() sprawia, że wywołania zależne od Context są zwięzłe: zamiast pisania Toast.makeText(context, message, length).show() wystarczy napisać context.toast("Tekst"). Zmniejsza to boilerplate i sprawia, że kod jest bardziej czytelny.

Często zadawane pytania

Czy można nadpisać funkcję rozszerzającą w podklasie?

Nie, funkcje rozszerzające używają statycznej dyspozytoryzacji. Jeśli zadeklarujesz funkcję rozszerzającą dla klasy bazowej i taką samą dla podklasy, która z nich zostanie wywołana — jest określane przez statyczny typ zmiennej na etapie kompilacji, a nie przez rzeczywisty typ w czasie wykonania.

Jak funkcje rozszerzające działają z typami nullable?

Można deklarować funkcje rozszerzające dla nullable typów odbiorcy: fun String?.isNullOrEmail(). Wewnątrz takiej funkcji this może być null, dlatego należy używać operatorów bezpiecznego wywołania lub jawnego sprawdzania na null.

Czy funkcje rozszerzające mają narzut wydajnościowy?

Nie, funkcje rozszerzające są kompilowane do metod statycznych. Na poziomie kodu bajtowego wywołanie funkcji rozszerzającej jest identyczne z wywołaniem metody statycznej z pierwszym parametrem-odbiorcą. Żadnej refleksji ani dynamicznej dyspozytoryzacji.

Czy można zadeklarować właściwość rozszerzającą dla klasy?

Tak, właściwości rozszerzające działają podobnie do funkcji rozszerzających, ale nie mogą przechowywać stanu — tylko getter i setter. Na przykład: val List<Int>.sum get() = reduce(Int::plus). Są również kompilowane do statycznych metod getter/setter.

Czym funkcje rozszerzające różnią się od metod w klasach narzędziowych Javy?

W Javie piszesz StringUtils.isEmail(str), a w Kotlin — str.isEmail(). Różnica nie polega tylko na składni: funkcje rozszerzające wspierają autouzupełnianie w IDE, poprawiają czytelność łańcuchów wywołań i pozwalają IDE sugerować odpowiednie funkcje dla konkretnego typu. W Javie takich możliwości nie ma.

Podsumowanie

  • Funkcja rozszerzająca — metoda z typem odbiorcy, wywoływana jako członek klasy bez dziedziczenia
  • Statyczna dyspozytoryzacja — wybór funkcji według statycznego typu, a nie typu w czasie wykonania
  • Zerowy narzut — kompilowana do metody statycznej z odbiorcą jako pierwszym parametrem
  • Import wymagany do użycia w innych plikach, możliwy alias przez as
  • Członkowie klasy mają priorytet nad funkcjami rozszerzającymi o tej samej sygnaturze
  • Member extensions — rozszerzenia zadeklarowane wewnątrz klasy, mają dostęp do jej członków
  • Zastosowanie praktyczne — narzędzia dla String, View, Context, kolekcji i budowa DSL

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również