funkcja inline — mechanizm Kotlin, w którym ciało funkcji jest wstawiane bezpośrednio w każde miejsce wywołania na etapie kompilacji. Eliminuje to narzut związany z tworzeniem anonimowych klas i obiektów dla parametrów lambda. Według Kotlin Documentation, 2025, słowo kluczowe inline jest szczególnie efektywne w przypadku funkcji wyższego rzędu, gdzie każda lambda bez wstawiania tworzy osobny obiekt FunctionN, obciążając garbage collector.
Najważniejsze
Funkcja inline to funkcja oznaczona słowem kluczowym inline. Kompilator Kotlin nie tworzy dla niej osobnego kodu bajtowego z wywołaniem, ale kopiuje ciało funkcji bezpośrednio do każdego miejsca wywołania. Głównym celem jest optymalizacja funkcji wyższego rzędu, które przyjmują wyrażenia lambda, ponieważ każda lambda w normalnej sytuacji tworzy anonimowy obiekt klasy Function.
Według JetBrains Tech Blog (2024), użycie funkcji inline w Kotlin może zmniejszyć liczbę tworzonych obiektów o 40–60% w funkcjach intensywnie wykorzystujących lambdy. W pętlach i operacjach o wysokim obciążeniu (sortowanie, filtrowanie kolekcji) daje to wymierny wzrost wydajności.
Bez inline każda lambda jest kompilowana do anonimowej klasy (lub instancji syntetyzowanego interfejsu funkcyjnego). Dla lambd przechwytujących zmienne tworzone są dodatkowe obiekty-opakowania. Funkcje inline eliminują wszystkie te obiekty na etapie kompilacji, zastępując je bezpośrednim kodem z dostępem do zmiennych lokalnych bez opakowań.
Używaj inline tylko dla funkcji z parametrami lambda — kompilator Kotlin sam ostrzega, jeśli inline nie daje korzyści.
Wystarczy dodać słowo kluczowe inline przed deklaracją funkcji. Kompilator automatycznie wstawia ciało funkcji w miejsca wywołania. Sama funkcja nadal istnieje w kodzie bajtowym na wypadek, gdyby była wywoływana niebezpośrednio (np. z kodu Java).
inline fun Int.repeatAction(action: (Int) -> Unit) {
for (i in 0 until this) {
action(i)
}
}
// Wywołanie — kod lambdy jest wstawiany do ciała funkcji
5.repeatAction { index ->
println("Index: $index")
}
Po kompilacji powyższy kod będzie równoważny:
// Co dzieje się po wstawieniu (schematycznie):
val $this = 5
for (i in 0 until $this) {
println("Index: $i")
}
Nie tworzy się żadnych obiektów dla lambdy — kod action jest wykonywany bezpośrednio. To jest istota optymalizacji: zamiast wywołania Function.invoke() — bezpośrednie wstawienie kodu z ciałem lambdy.
Aby sprawdzić wstawianie, otwórz Tools > Kotlin > Show Kotlin Bytecode w IntelliJ IDEA i kliknij Decompile. Zobaczysz, że zamiast wywołania repeatAction z lambdą generowane jest bezpośrednie wstawienie ciała funkcji z pętlą for.
Każda lambda w Kotlin jest kompilowana do jednego z trzech wariantów. Pierwszy — jeśli lambda nie przechwytuje zmiennych, staje się statyczną metodą klasy, w której jest zadeklarowana. Drugi — jeśli przechwytuje jedną zmienną, tworzona jest anonimowa klasa. Trzeci — jeśli przechwytuje wiele zmiennych, tworzona jest anonimowa klasa z polami dla każdej przechwyconej zmiennej.
| Typ lambdy | Bez inline | Z inline |
|---|---|---|
| Bez przechwytywania | Jedna metoda statyczna (współdzielona) | Pełne wstawienie, bez wywołania |
| Z przechwyceniem 1 zmiennej | Anonimowa klasa (jeden obiekt) | Pełne wstawienie, bez obiektu |
| Z przechwyceniem N zmiennych | Anonimowa klasa z N polami | Pełne wstawienie, bez obiektu |
| Rekurencyjna | Zwykłe wywołanie | inline zabroniony |
Według Android Performance Patterns (Google, 2024), w aplikacjach intensywnie wykorzystujących kolekcje (filtrowanie, sortowanie, grupowanie) funkcje inline zmniejszają liczbę alokacji o 25–35%. Efekt jest szczególnie widoczny w Jetpack Compose, gdzie każda zmiana stanu powoduje ponowną kompozycję z wieloma lambdami.
Lambda w zwykłej funkcji nie może wykonać return z funkcji zewnętrznej — tylko lokalny return z samej lambdy (przez return@label). W funkcji inline lambda jest wstawiana do ciała funkcji wywołującej, dlatego nonlocal return staje się możliwy: return wewnątrz lambdy kończy funkcję zewnętrzną.
inline fun findFirst(
items: List<Int>,
predicate: (Int) -> Boolean
): Int {
for (item in items) {
if (predicate(item)) {
return item
}
}
return -1
}
fun processNumbers() {
val numbers = listOf(1, 2, 3)
val firstEven = findFirst(numbers) { it % 2 == 0 }
// return w lambdzie zwróciłby null z processNumbers()
}
Nonlocal return jest wygodny do wczesnego zakończenia, ale może prowadzić do błędów. Jeśli lambda jest używana w kontekście nielokalnym (zapisywana do zmiennej), nonlocal return spowoduje RuntimeException. Kompilator Kotlin wyświetla ostrzeżenie przy próbie takiego zapisu.
Gdy funkcja ma wiele parametrów lambda, czasami trzeba wstawić tylko część z nich. Służy do tego noinline — zabrania wstawiania konkretnego parametru lambda, pozostawiając go jako zwykły obiekt Function.
Modyfikator crossinline rozwiązuje przeciwne zadanie: lambda jest wstawiana, ale nonlocal return jest zabroniony. Jest to potrzebne, gdy lambda jest używana wewnątrz innej lambdy lub w kontekście, gdzie return jest niedozwolony (np. przekazywana do Runnable).
inline fun processWithCallback(
data: String,
crossinline onSuccess: (String) -> Unit,
noinline onError: (Exception) -> Unit
) {
try {
val result = process(data)
onSuccess(result)
} catch (e: Exception) {
onError(e)
}
}
// noinline: onError może być przechowywany w zmiennej lub przekazany gdzie indziej
val errorHandler = { e: Exception -> log(e.message) }
processWithCallback("input", { println(it) }, errorHandler)
W przykładzie onSuccess jest oznaczona jako crossinline — zostanie wstawiona, ale wewnątrz niej nie można użyć return. onError jest oznaczona jako noinline — nie jest wstawiana, można ją przekazać jako obiekt, zapisać w polu klasy lub użyć jako listener.
Funkcje inline mają ograniczenia. Rekurencyjne funkcje inline są zabronione — kompilator zgłosi błąd. Funkcje inline nie mogą mieć widoczności private lub internal, jeśli są zadeklarowane w innym module, ale to ograniczenie dotyczy dostępu, a nie samego mechanizmu wstawiania.
Rozmiar kodu bajtowego rośnie z każdym wywołaniem funkcji inline, ponieważ ciało jest kopiowane. Według Kotlin Coding Conventions (JetBrains, 2025), zaleca się używanie inline tylko dla funkcji o rozmiarze do 10–15 linii. Dla większych funkcji korzyść z wstawiania lambd może być zniwelowana przez zwiększenie rozmiaru APK (w Android jest to krytyczne — istnieje limit 64K metod).
// Zalecana praktyka
inline fun withLock(lock: Lock, action: () -> T): T {
lock.lock()
try {
return action()
} finally {
lock.unlock()
}
}
// Nie zalecane dla dużych funkcji
inline fun largeComputation(...) { // źle — ciało >50 linii
// ponad 50 linii — lepiej wydzielić do zwykłej funkcji
}
Publiczne funkcje inline w bibliotekach wymagają ostrożności: jeśli ciało funkcji inline ulegnie zmianie, wszyscy klienci muszą skompilować się ponownie. JetBrains zaleca używanie @PublishedApi internal dla elementów wywoływanych z funkcji inline, aby zachować kompatybilność w obrębie modułu.
Często zadawane pytania
Tak, inline extension function działa bez ograniczeń. Na przykład: inline fun String.transform(block: (Char) -> Char): String. Rozszerzenie nie wpływa na możliwość wstawiania — kompilator traktuje ją tak samo jak zwykłą funkcję inline.
Jeśli funkcja nie przyjmuje parametrów lambda — inline nie daje przewagi. Kompilator Kotlin wyświetla ostrzeżenie: "Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types." Ponadto inline jest szkodliwy dla dużych funkcji ze względu na wzrost kodu bajtowego.
inline — modyfikator funkcji wstawiający jej ciało w miejsce wywołania. @JvmInline (value class) — mechanizm dla klas-opakowań, które są zastępowane wartością podczas kompilacji. Różne koncepcje: inline optymalizuje wywołania, value class optymalizuje reprezentację danych.
Nie, suspend-funkcje nie mogą być inline, ponieważ są kompilowane do maszyny stanów z Continuation. Jednak funkcja inline może przyjmować suspend-lambdę jako parametr z crossinline. Jest to często używane w korutynach: inline fun launch(block: suspend CoroutineScope.() -> Unit).
Tak, funkcje inline utrudniają debugowanie, ponieważ ciało funkcji nie jest wywoływane, ale wstawiane w miejsce wywołania. Stacktrace staje się dłuższy, punkty przerwania działają, ale mogą pokazywać nieoczekiwane pozycje. JetBrains zaleca debugowanie bez inline, a włączanie go tylko w wersjach release.
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ż