extension property — mechanizm Kotlin pozwalający dodawać nowe właściwości do istniejących klas bez dziedziczenia i bez zmiany kodu źródłowego. W przeciwieństwie do extension functions, właściwości-rozszerzenia nie mogą przechowywać stanu — są deklarowane tylko z getterem i opcjonalnie setterem, ponieważ nie mają backing field. Według Kotlin Documentation, 2025, extension properties są kompilowane do statycznych metod getter i setter z receiver jako pierwszym parametrem.
Najważniejsze
extension property — to konstrukcja składniowa Kotlin, która dodaje właściwość do istniejącego typu bez zmiany jego deklaracji. Właściwość jest deklarowana z określeniem receiver-typu i obowiązkowo zawiera getter. Kluczowa różnica w porównaniu ze zwykłymi właściwościami — brak backing field: extension property nie może przechowywać danych, a jedynie oblicza je na podstawie receiver-obiektu.
Według Kotlin Foundation Survey (2024), extension properties są mniej popularne niż extension functions — regularnie używa ich około 45% programistów. Wynika to z ograniczenia braku stanu, co zawęża obszar zastosowań. Niemniej jednak, dla obliczanych właściwości logicznie powiązanych z typem, extension properties są najbardziej zwięzłą opcją.
Extension properties są kompilowane do pary statycznych metod getter i setter. Na poziomie kodu bajtowego nie ma różnicy między wywołaniem extension property a wywołaniem extension function — oba stają się statycznymi metodami z parametrem receiver. Według danych JetBrains (Kotlin Docs, 2025), narzut jest całkowicie nieobecny.
Używaj extension properties dla krótkich obliczanych wartości, które powinny wyglądać jak właściwości, a nie jak wywołania metod — poprawia to czytelność kodu i przestrzega zasady jednolitego dostępu.
Do deklaracji extension property używana jest składnia podobna do zwykłej właściwości, ale z prefiksem receiver-typu. val deklaruje read-only extension property z obowiązkowym getterem, var — mutable z getterem i opcjonalnym setterem.
// Tylko do odczytu extension property
val String.isEmail: Boolean
get() = this.contains("@") && this.contains(".")
// Wywołanie
val valid = "test@test.com".isEmail
Zwróć uwagę: extension property jest wywoływana bez nawiasów — str.isEmail, a nie str.isEmail(). To jest kluczowa różnica między extension property a extension function: property wygląda jak pole, choć w rzeczywistości jest obliczane przez getter.
Extension properties mogą być uogólnione — receiver może używać parametrów generic. Pozwala to tworzyć uniwersalne właściwości działające z dowolnym typem kolekcji.
val List<T>.secondOrNull: T?
get() = if (size >= 2) this[1] else null
val items = listOf("a", "b", "c")
val second = items.secondOrNull // "b"
Właściwość secondOrNull działa dla dowolnego typu T, zwracając drugi element listy lub null, jeśli elementów jest mniej niż dwa. To typowy przykład, gdzie extension property jest bardziej odpowiednia niż funkcja — odwołanie wygląda jak odczyt pola.
Extension property nie może mieć backing field, ponieważ nie jest dodawana do metadanych klasy — istnieje tylko jako para statycznych funkcji getter/setter. Backing field (słowo kluczowe field w Kotlin) to wewnętrzne pole klasy, które przechowuje wartość właściwości. Extension property nie ma dostępu do wewnętrznej struktury klasy.
// ❌ BŁĄD: extension property nie może mieć pola zapasowego
var String.cachedValue: String
get() = "computed"
set(value) {
field = value // pole nie jest dostępne!
}
// ✅ POPRAWNIE: użyj zewnętrznego przechowywania
val cache = MutableMap<String, String>()
var String.cachedValue: String
get() = cache[this] ?: ""
set(value) { cache[this] = value }
Zewnętrzna Map w przykładzie rozwiązuje problem przechowywania, ale rodzi inny — wyciek pamięci. Wartości uzyskane przez extension property żyją w Map wiecznie, jeśli nie są czyszczone. To ograniczenie sprawia, że extension properties nie nadają się do buforowania lub przechowywania danych tymczasowych.
Do buforowania zaleca się używanie WeakHashMap lub mechanizmów z automatycznym czyszczeniem. JetBrains zaleca unikanie używania var extension properties z zewnętrznym magazynem w kodzie produkcyjnym bez starannego zarządzania cyklem życia.
Wybór między extension property a extension function zależy od semantyki: właściwość opisuje charakterystykę obiektu, a funkcja — działanie. Zasada jednolitego dostępu (Uniform Access Principle) mówi: klient nie powinien wiedzieć, czy wartość jest obliczana, czy przechowywana. Jeśli wartość można przedstawić jako cechę (długość, rozmiar, status) — użyj property.
| Kryterium | Extension property | Extension function |
|---|---|---|
| Wywołanie | Bez nawiasów: obj.property | Z nawiasami: obj.function() |
| Semantyka | Cecha, atrybut | Działanie, operacja |
| Backing field | Nieobsługiwany | Nie dotyczy |
| Parametry | Tylko getter/setter | Dowolne parametry |
| Wydajność | Taka sama (statyczna metoda) | Taka sama (statyczna metoda) |
| Przykład | text.length | text.isEmail() |
Zasada jest prosta: jeśli operacja przyjmuje parametry — użyj extension function. Jeśli to prosta obliczana wartość bez parametrów — extension property. Według danych Android Architecture Guide (Google, 2025), należy preferować extension properties do dostępu do danych i extension functions dla operacji z efektami ubocznymi.
Extension property ze słowem kluczowym var obsługuje setter, ale bez możliwości przechowywania wartości — setter zwykle wykonuje działanie uboczne lub zapisuje dane w zewnętrznym magazynie. Składnia jest analogiczna do mutable właściwości klas.
// Mutable extension property z setterem
var StringBuilder.lastChar: Char
get() = this[length - 1]
set(value) {
this.setCharAt(length - 1, value)
}
val sb = StringBuilder("Kotlin")
println(sb.lastChar) // n
sb.lastChar = '!'
println(sb) // Kotli!
Właściwość lastChar — klasyczny przykład z dokumentacji Kotlin. Getter zwraca ostatni znak StringBuilder, setter zastępuje go nową wartością. Zwróć uwagę: stan jest przechowywany w samym StringBuilder (przez setCharAt), a nie w osobnym polu — to poprawne użycie extension property.
W rzeczywistych projektach extension properties najczęściej są używane do upraszczania dostępu do danych kolekcji, obliczania rozmiarów lub statusów elementów UI oraz tworzenia wygodnego API na bazie istniejących klas. Biblioteka standardowa Kotlin aktywnie używa tego mechanizmu: size, indices, lastIndex dla kolekcji — to extension properties.
// Extension properties dla kolekcji
val List<Int>.sumFast: Int
get() = fold(0) { acc, i -> acc + i }
val String.half: String
get() = this.substring(0, length / 2)
// Extension property dla Android View
val View.isVisible: Boolean
get() = visibility == View.VISIBLE
// Sprawdzenie null przez bezpieczny odbiornik
val String?.isNullOrBlank: Boolean
get() = this == null || this.isBlank()
Extension property isVisible dla View — przykład, który powinien znać każdy programista Android. Zamiast view.visibility == View.VISIBLE można pisać view.isVisible. To nie tylko krótsze, ale czyta się jak naturalny język: „jeśli widok jest widoczny”. Mimo prostoty, takie właściwości znacząco poprawiają czytelność kodu.
Często zadawane pytania
Nie, extension properties nie mogą być deklarowane dla companion object ani object declaration. Mechanizm extension ma zastosowanie tylko do klas, interfejsów i typów nullable. Dla object używaj zwykłych funkcji najwyższego poziomu.
Inline-właściwość (z modyfikatorem inline) — mechanizm Kotlin do wywoływania gettera/settera bez tworzenia obiektu-właściwości. Extension property zawsze kompiluje się do statycznej metody, a inline-właściwość — do wywołania bez opakowania. Rozwiązują one różne zadania: extension property dodaje właściwość do istniejącego typu, inline — optymalizuje wywołania własnych właściwości.
Tak, extension property może zawierać adnotacje, ale tylko na poziomie deklaracji. Nie można adnotować oddzielnie gettera lub settera extension property — w przeciwieństwie do zwykłych właściwości klasy. Przykład: @JvmName(„getIsValid”) val String.isValid get() = true.
Nie, extension properties nie można zadeklarować z companion object jako receiver. To ograniczenie języka — extension property działa tylko z instancjami typów, a companion object to kontekst statyczny. Używaj extension functions najwyższego poziomu lub stałych.
Minimalnie. Każde extension property dodaje jedną statyczną metodę getter (i opcjonalnie setter) do skompilowanego kodu bajtowego. Dla porównania: utworzenie klasy-opakowującej z taką samą właściwością dodaje całą klasę. Extension properties — lżejsze podejście do rozszerzania funkcjonalności.
Podsumowanie
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.
Przeczytaj również