inline function — un meccanismo Kotlin in cui il corpo della funzione viene sostituito direttamente in ogni punto di chiamata in fase di compilazione. Ciò elimina l'overhead derivante dalla creazione di classi anonime e oggetti per i parametri lambda. Secondo Kotlin Documentation, 2025, la parola chiave inline è particolarmente efficace per le funzioni di ordine superiore, dove ogni lambda senza inserimento crea un oggetto FunctionN separato, appesantendo il garbage collector.
Punti chiave
Una funzione inline è una funzione contrassegnata dalla parola chiave inline. Il compilatore Kotlin non crea bytecode separato con una chiamata per essa — invece, copia direttamente il corpo della funzione in ogni punto di chiamata. L'obiettivo principale è ottimizzare le funzioni di ordine superiore che accettano espressioni lambda, poiché ogni lambda in una situazione normale crea un oggetto di classe Function anonimo.
Secondo JetBrains Tech Blog (2024), l'uso di funzioni inline in Kotlin può ridurre il numero di oggetti creati del 40–60% nelle funzioni che utilizzano intensivamente le lambda. Nei cicli e nelle operazioni ad alto carico (ordinamento, filtraggio di collezioni) ciò fornisce un miglioramento delle prestazioni misurabile.
Senza inline, ogni lambda viene compilata in una classe anonima (o un'istanza di un'interfaccia funzionale sintetizzata). Per le lambda che catturano variabili, vengono creati oggetti wrapper aggiuntivi. Le funzioni inline eliminano tutti questi oggetti in fase di compilazione, sostituendoli con codice diretto che accede alle variabili locali senza wrapper.
Utilizza inline solo per funzioni con parametri lambda — il compilatore Kotlin avverte se inline non fornisce benefici.
Basta aggiungere la parola chiave inline prima della dichiarazione della funzione. Il compilatore sostituisce automaticamente il corpo della funzione nei punti di chiamata. La funzione stessa continua ad esistere in bytecode per i casi in cui non viene chiamata direttamente (ad esempio, da codice Java).
inline fun Int.repeatAction(action: (Int) -> Unit) {
for (i in 0 until this) {
action(i)
}
}
// Chiamata — il codice lambda viene inserito nel corpo della funzione
5.repeatAction { index ->
println("Index: $index")
}
Dopo la compilazione, il codice sopra sarà equivalente a:
// Cosa succede dopo l'inserimento (schematizzato):
val $this = 5
for (i in 0 until $this) {
println("Index: $i")
}
Nessun oggetto viene creato per la lambda — il codice action viene eseguito direttamente. Questa è l'essenza dell'ottimizzazione: invece di chiamare Function.invoke() — un inserimento diretto del codice con il corpo della lambda.
Per verificare l'inserimento, apri Tools > Kotlin > Show Kotlin Bytecode in IntelliJ IDEA e fai clic su Decompile. Vedrai che invece di chiamare repeatAction con una lambda, viene generato un inserimento diretto del corpo della funzione con un ciclo for.
Ogni lambda in Kotlin viene compilata in una delle tre varianti. Prima — se la lambda non cattura variabili, diventa un metodo statico della classe in cui è dichiarata. Seconda — se cattura una variabile, viene creata una classe anonima. Terza — se cattura più variabili, viene creata una classe anonima con campi per ogni variabile catturata.
| Tipo di lambda | Senza inline | Con inline |
|---|---|---|
| Senza cattura | Un metodo statico (riutilizzato) | Inserimento completo, senza chiamata |
| Con cattura di 1 variabile | Classe anonima (un oggetto) | Inserimento completo, senza oggetto |
| Con cattura di N variabili | Classe anonima con N campi | Inserimento completo, senza oggetto |
| Ricorsiva | Chiamata normale | inline vietato |
Secondo Android Performance Patterns (Google, 2024), nelle applicazioni con uso intensivo di collezioni (filtraggio, ordinamento, raggruppamento) le funzioni inline riducono le allocazioni del 25–35%. L'effetto è particolarmente evidente in Jetpack Compose, dove ogni cambiamento di stato attiva una ricomposizione con molte lambda.
Una lambda in una funzione normale non può restituire dalla funzione esterna — solo un return locale dalla lambda stessa (tramite return@label). In una funzione inline, la lambda viene inserita nel corpo della funzione chiamante, quindi nonlocal return diventa possibile: un return all'interno della lambda termina la funzione esterna.
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 nella lambda restituirebbe null da processNumbers()
}
Nonlocal return è comodo per la terminazione anticipata, ma può causare errori. Se la lambda viene utilizzata in un contesto non locale (memorizzata in una variabile), un nonlocal return causerà una RuntimeException. Il compilatore Kotlin emette un avviso quando si tenta tale memorizzazione.
Quando una funzione ha più parametri lambda, a volte è necessario inserire solo alcuni di essi. Per questo si usa noinline — impedisce l'inserimento di un parametro lambda specifico, lasciandolo come un normale oggetto Function.
Il modificatore crossinline risolve il problema opposto: la lambda viene inserita, ma il nonlocal return è vietato. Ciò è necessario quando la lambda viene utilizzata all'interno di un'altra lambda o in un contesto in cui return non è consentito (ad esempio, passato a un 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 può essere memorizzato in una variabile o passato altrove
val errorHandler = { e: Exception -> log(e.message) }
processWithCallback("input", { println(it) }, errorHandler)
Nell'esempio, onSuccess è contrassegnata come crossinline — verrà inserita, ma non è possibile utilizzare return al suo interno. onError è contrassegnata come noinline — non viene inserita, quindi può essere passata come oggetto, memorizzata in un campo di classe o utilizzata come listener.
Le funzioni inline hanno limitazioni. Le funzioni inline ricorsive sono vietate — il compilatore restituirà un errore. Le funzioni inline non possono avere visibilità private o internal se dichiarate in un altro modulo, ma questa è una limitazione di visibilità, non correlata al meccanismo di inserimento stesso.
La dimensione del bytecode cresce con ogni chiamata a funzione inline, poiché il corpo viene copiato. Secondo Kotlin Coding Conventions (JetBrains, 2025), si raccomanda di usare inline solo per funzioni fino a 10–15 righe. Per funzioni grandi, il beneficio dell'inserimento delle lambda può essere annullato dall'aumento delle dimensioni dell'APK (critico su Android a causa del limite di 64K metodi).
// Pratica consigliata
inline fun withLock(lock: Lock, action: () -> T): T {
lock.lock()
try {
return action()
} finally {
lock.unlock()
}
}
// Non consigliato per funzioni grandi
inline fun largeComputation(...) { // male — corpo >50 righe
// più di 50 righe — meglio estrarre in una funzione regolare
}
Le funzioni inline pubbliche nelle librerie richiedono cautela: se il corpo di una funzione inline cambia, tutti i client devono essere ricompilati. JetBrains raccomanda di usare @PublishedApi internal per i membri chiamati da funzioni inline per mantenere la compatibilità all'interno di un modulo.
Domande frequenti
Sì, una funzione di estensione inline funziona senza restrizioni. Ad esempio: inline fun String.transform(block: (Char) -> Char): String. L'estensione non influisce sulla capacità di inserimento — il compilatore la tratta come una normale funzione inline.
Se la funzione non accetta parametri lambda — inline non fornisce vantaggi. Il compilatore Kotlin emette un avviso: “Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types.” Inoltre, inline è dannoso per funzioni grandi a causa della crescita del bytecode.
inline — un modificatore di funzione che inserisce il corpo della funzione nel punto di chiamata. @JvmInline (value class) — un meccanismo per classi wrapper che vengono sostituite dal loro valore in fase di compilazione. Concetti diversi: inline ottimizza le chiamate, value class ottimizza la rappresentazione dei dati.
No, le funzioni suspend non possono essere inline perché vengono compilate in una macchina a stati con Continuation. Tuttavia, una funzione inline può accettare una lambda suspend come parametro con crossinline. Questo viene spesso usato nelle coroutine: inline fun launch(block: suspend CoroutineScope.() -> Unit).
Sì, le funzioni inline complicano il debug perché il corpo della funzione non viene chiamato ma inserito nel punto di chiamata. Gli stacktrace diventano più lunghi, i breakpoint funzionano ma possono mostrare posizioni inaspettate. JetBrains raccomanda di eseguire il debug senza inline e di abilitarlo solo nelle build release.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche