Scope functions w Kotlin to pięć funkcji standardowej biblioteki (let, run, with, apply, also), które wykonują blok kodu w kontekście obiektu. Różnią się sposobem dostępu do obiektu (przez it lub this) i zwracaną wartością (obiekt kontekstowy lub wynik lambdy). Według dokumentacji Kotlin (2026), odpowiedni wybór scope function redukuje kod boilerplate o 25–40%. Scope functions pomagają pisać inicjalizację, konfigurację i transformacje obiektów w deklaratywnym stylu.
Najważniejsze
Scope functions (funkcje zakresu) — to zestaw pięciu funkcji (let, run, with, apply, also) wbudowanych w standardową bibliotekę Kotlin. Każda z nich przyjmuje lambdę i tymczasowo zmienia zakres widoczności tak, że wewnątrz lambdy kod wykonuje się w kontekście konkretnego obiektu. Pozwala to uniknąć powtarzania nazwy obiektu przy wielokrotnych wywołaniach i grupuje powiązane operacje w jeden blok.
Dwie kluczowe cechy różnią scope functions: sposób dostępu do obiektu kontekstowego (przez this lub przez it) oraz zwracana wartość (sam obiekt kontekstowy lub wynik lambdy). Dostęp przez this czyni obiekt niejawnym odbiorcą — metody i właściwości są wywoływane bez prefiksu. Dostęp przez it przekazuje obiekt jako jawny parametr lambdy. Zwrot obiektu umożliwia budowanie łańcuchów, zwrot wyniku — przypisanie przekształconej wartości.
Historycznie scope functions pojawiły się jako zamiennik klasycznego wzorca Builder i tymczasowych zmiennych do inicjalizacji. Zamiast tworzenia osobnej klasy builder, programista pisze apply { właściwość1 = ...; właściwość2 = ... }. Zmniejsza to ilość kodu i czyni inicjalizację bardziej czytelną. W ekosystemie Kotlin scope functions są używane we wszystkich frameworkach i bibliotekach jako standardowa idioma języka.
Wybór odpowiedniej scope function zależy od dwóch pytań: czy chcesz używać this czy it do dostępu do obiektu, oraz czy chcesz zwrócić sam obiekt czy wynik lambdy. Kombinacja dwóch binarnych cech daje cztery możliwe warianty, a with zajmuje szczególne miejsce jako jedyna funkcja niebędąca rozszerzeniem.
| Funkcja | Dostęp | Zwrot | Rozszerzenie? | Typowy scenariusz |
|---|---|---|---|---|
| let | it | wynik lambdy | tak | Transformacja, sprawdzanie null |
| run | this | wynik lambdy | tak | Obliczenia z kontekstem |
| with | this | wynik lambdy | nie | Grupowanie wywołań |
| apply | this | obiekt kontekstowy | tak | Inicjalizacja właściwości |
| also | it | obiekt kontekstowy | tak | Efekty uboczne, logowanie |
Do konfiguracji obiektu bez zwracania nowej wartości używaj apply (dostęp przez this) lub also (dostęp przez it). Do obliczenia nowej wartości na podstawie obiektu używaj let (dostęp przez it) lub run (dostęp przez this). Do grupowania wywołań bez łańcuchowania używaj with. Przestrzeganie tego schematu czyni kod przewidywalnym dla innych programistów znających Kotlin.
apply — scope function, która zwraca obiekt kontekstowy i zapewnia dostęp do niego przez this. To idealne narzędzie do inicjalizacji właściwości obiektu po utworzeniu. Wewnątrz bloku apply można ustawiać właściwości bez powtarzania nazwy obiektu. Metoda zwraca sam obiekt, co pozwala wbudować apply w łańcuchy lub używać w inicjalizatorach.
data class ServerConfig(
var host: String = "localhost",
var port: Int = 8080,
var useTls: Boolean = false
)
val config = ServerConfig().apply {
host = "api.example.com"
port = 443
useTls = true
}
W listingu apply konfiguruje ServerConfig, odwołując się do właściwości bezpośrednio przez this (this pominięty). Bez apply trzeba by pisać config.host = ..., config.port = ... — trzy powtarzające się odwołania do tej samej zmiennej. Zwrot obiektu pozwala zachować wynik konfiguracji w val config. also, w przeciwieństwie do apply, przekazuje obiekt przez it i nadaje się do efektów ubocznych.
fun saveUser(user: User) {
validate(user).also { result ->
println("Wynik walidacji: $result")
}
val savedUser = user.also {
log("Saving user ${it.id}")
database.save(it)
}
}
W przykładzie also jest używane do logowania — zwracając oryginalny obiekt, wykonuje efekt uboczny (wyświetlenie komunikatu) bez modyfikacji obiektu. To łańcuch: validate(user) zwraca User, also loguje wynik i przekazuje użytkownika dalej. also jest również przydatny do dodawania elementów do kolekcji lub debugowania w środku łańcucha transformacji.
let — scope function, która zwraca wynik lambdy i przekazuje kontekst przez it. To podstawowa funkcja do null-bezpiecznych transformacji: jeśli obiekt jest nullable, kombinacja ?.let { } wykona blok tylko przy wartości niepustej. let jest również używane do ograniczania zakresu widoczności zmiennych tymczasowych i transformacji map-podobnych z jednego typu na inny.
fun findUser(id: Int): User?
val displayName = findUser(42)?.let { user ->
"${user.name} (${user.email})"
} ?: "Unknown user"
// let z transformacją kolekcji
val numbers = listOf("1", "2", "3")
val parsed = numbers.firstOrNull()?.let {
it.toIntOrNull()
} ?: 0
W pierwszym przykładzie let jest wywoływane przez ?. — blok wykonuje się tylko jeśli findUser zwrócił nie null. Lambda przyjmuje user przez it (można przemianować dla jasności) i zwraca sformatowany string. Jeśli użytkownik nie został znaleziony — operator Elvisa ?: daje wartość domyślną. W drugim przykładzie let przekształca string na liczbę z potencjalnym null.
run — odpowiednik let z dostępem przez this, zwracający wynik lambdy. run jest wygodny do obliczeń, gdzie wymagany jest kontekst obiektu, ale zwracana jest nowa wartość. Wywołanie run() bez obiektu (jak run { ... }) tworzy tymczasowy zakres widoczności dla zmiennych. with — nierozszerzenie z dostępem przez this, odpowiednie do grupowania wywołań bez łańcuchowania.
// run jako obliczenia kontekstowe
val transformed = listOf(1, 2, 3).run {
filter { it > 1 }.map { it * 10 }
}
println(transformed) // [20, 30]
// with do grupowania wywołań
val info = with(StringBuilder()) {
append("Name: ")
append("Alice")
toString()
}
W przykładzie run na liście zwraca wynik filtrowania i mapowania — samo przekształcenie, a nie listę. with przyjmuje StringBuilder jako argument (nie przez kropkę) i wewnątrz pracuje z this: append, append, toString. Wynik with — string info. Wybór między run a with jest często subiektywny, ale with jest preferowane, gdy obiekt jest przekazywany z innego miejsca bez łańcucha wywołań.
with różni się od pozostałych scope functions tym, że nie jest funkcją-rozszerzeniem. Przyjmuje obiekt jako pierwszy argument, a lambdę jako drugi. Wewnątrz lambdy obiekt jest dostępny przez this. with jest wygodny do grupowania wywołań metod obiektu bez zapisywania wyniku lub gdy wymagane jest tymczasowe „wejście” w kontekst w celu odczytania kilku właściwości.
val user = User("Alice", "alice@example.com", "Admin")
val summary = with(user) {
"User $name has role $role and email $email"
}
println(summary)
// with do inicjalizacji komponentu UI
with(TextView(context)) {
text = "Hello"
textSize = 18.0f
setTextColor(Color.BLUE)
}
W pierwszym przykładzie with tworzy string summary, odwołując się do właściwości user bezpośrednio przez this. Bez with trzeba by pisać „User ${user.name} has role ${user.role}” — wielokrotne powtarzanie user. W drugim przykładzie with grupuje konfigurację TextView: wszystkie metody i właściwości są wywoływane na jednym obiekcie bez powtarzania. Wada with — brak możliwości użycia w łańcuchu, ponieważ nie jest rozszerzeniem i nie zwraca obiektu kontekstowego.
Często zadawane pytania
Użyj apply — zwraca obiekt i zapewnia dostęp przez this, umożliwiając ustawianie właściwości bez powtarzania nazwy obiektu. also jest odpowiedni, jeśli potrzebny jest jawny parametr it dla odróżnienia od właściwości obiektu.
let przekazuje kontekst przez it (jawny parametr), run — przez this (niejawny odbiorca). let jest wygodny do sprawdzania null i transformacji, run — do obliczeń z kontekstem. Obie zwracają wynik lambdy.
with nie jest rozszerzeniem — przyjmuje obiekt jako argument. Używaj with do grupowania wywołań, gdy łańcuch nie jest potrzebny. apply — rozszerzenie zwracające obiekt, wygodne w inicjalizatorach i wzorcach builder.
Tak, ale należy uważać na this — zagnieżdżone this tworzą zamieszanie. Używaj let lub also z jawnymi nazwami dla zagnieżdżonych kontekstów, aby uniknąć niejednoznaczności przy odwoływaniu się do obiektu zewnętrznego.
Wszystkie scope functions są inline, więc nie tworzą narzutu na klasy anonimowe. Różnica w wydajności między nimi jest pomijalnie mała. Wybór powinien opierać się na czytelności i konwencjach, a nie na szybkoś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ż