Le scope functions in Kotlin sono cinque funzioni della libreria standard (let, run, with, apply, also) che eseguono un blocco di codice nel contesto di un oggetto. Differiscono nel modo di accedere all'oggetto (tramite it o this) e nel valore restituito (oggetto di contesto o risultato della lambda). Secondo la documentazione Kotlin (2026), la scelta corretta della scope function riduce il codice boilerplate del 25–40%. Le scope functions aiutano a scrivere inizializzazione, configurazione e trasformazioni di oggetti in uno stile dichiarativo.
Punti chiave
Le scope functions sono un insieme di cinque funzioni (let, run, with, apply, also) integrate nella libreria standard di Kotlin. Ognuna di esse prende una lambda e modifica temporaneamente l'ambito in modo che il codice all'interno della lambda venga eseguito nel contesto di un oggetto specifico. Questo elimina la ripetizione del nome dell'oggetto per chiamate multiple e raggruppa le operazioni correlate in un unico blocco.
Due caratteristiche chiave distinguono le scope functions: il modo di accedere all'oggetto di contesto (tramite this o tramite it) e il valore restituito (l'oggetto di contesto stesso o il risultato della lambda). L'accesso tramite this rende l'oggetto un ricevitore implicito — metodi e proprietà vengono chiamati senza prefisso. L'accesso tramite it passa l'oggetto come parametro esplicito della lambda. Restituire l'oggetto consente il concatenamento, restituire il risultato consente di assegnare il valore trasformato.
Storicamente, le scope functions sono emerse come sostituzione del classico pattern Builder e delle variabili temporanee per l'inizializzazione. Invece di creare una classe builder separata, lo sviluppatore scrive apply { proprietà1 = ...; proprietà2 = ... }. Questo riduce la quantità di codice e rende l'inizializzazione più leggibile. Nell'ecosistema Kotlin, le scope functions sono utilizzate in tutti i framework e le librerie come idioma linguistico standard.
La scelta della scope function corretta è determinata da due domande: se si desidera utilizzare this o it per accedere all'oggetto, e se si desidera restituire l'oggetto stesso o il risultato della lambda. La combinazione di due caratteristiche binarie dà quattro possibili opzioni, e with occupa un posto speciale come unica funzione che non è un'estensione.
| Funzione | Accesso | Restituzione | Estensione? | Scenario tipico |
|---|---|---|---|---|
| let | it | risultato lambda | sì | Trasformazione, controllo null |
| run | this | risultato lambda | sì | Calcolo con contesto |
| with | this | risultato lambda | no | Raggruppamento chiamate |
| apply | this | oggetto di contesto | sì | Inizializzazione proprietà |
| also | it | oggetto di contesto | sì | Effetti collaterali, logging |
Per configurare un oggetto senza restituire un nuovo valore, utilizzare apply (accesso tramite this) o also (accesso tramite it). Per calcolare un nuovo valore basato su un oggetto, utilizzare let (accesso tramite it) o run (accesso tramite this). Per raggruppare chiamate senza concatenamento, utilizzare with. Seguire questo schema rende il codice prevedibile per altri sviluppatori familiari con Kotlin.
apply è una scope function che restituisce l'oggetto di contesto e fornisce l'accesso ad esso tramite this. È lo strumento ideale per inizializzare le proprietà di un oggetto dopo la sua creazione. All'interno del blocco apply, le proprietà possono essere impostate senza ripetere il nome dell'oggetto. Il metodo restituisce l'oggetto stesso, consentendo di incorporare apply in catene o utilizzarlo negli inizializzatori.
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
}
Nell'esempio, apply configura ServerConfig accedendo direttamente alle proprietà tramite this (this è omesso). Senza apply, si dovrebbe scrivere config.host = ..., config.port = ... — tre riferimenti ripetitivi alla stessa variabile. La restituzione dell'oggetto consente di salvare il risultato della configurazione in val config. also, a differenza di apply, passa l'oggetto tramite it ed è adatto per effetti collaterali.
fun saveUser(user: User) {
validate(user).also { result ->
println("Risultato della validazione: $result")
}
val savedUser = user.also {
log("Saving user ${it.id}")
database.save(it)
}
}
Nell'esempio, also viene utilizzato per il logging — mentre restituisce l'oggetto originale, esegue un effetto collaterale (output di un messaggio) senza modificare l'oggetto. Questa è una catena: validate(user) restituisce User, also registra il risultato e passa l'utente oltre. also è utile anche per aggiungere elementi a collezioni o per eseguire debug nel mezzo di una catena di trasformazioni.
let è una scope function che restituisce il risultato della lambda e passa il contesto tramite it. È la funzione principale per le trasformazioni null-safe: se l'oggetto è nullable, la combinazione ?.let { } esegue il blocco solo per valori non nulli. let viene anche utilizzato per limitare l'ambito di variabili temporanee e per trasformazioni simili a map da un tipo a un altro.
fun findUser(id: Int): User?
val displayName = findUser(42)?.let { user ->
"${user.name} (${user.email})"
} ?: "Unknown user"
// let con trasformazione di collezione
val numbers = listOf("1", "2", "3")
val parsed = numbers.firstOrNull()?.let {
it.toIntOrNull()
} ?: 0
Nel primo esempio, let viene chiamato tramite ?. — il blocco viene eseguito solo se findUser ha restituito un valore non nullo. La lambda prende user tramite it (può essere rinominato per chiarezza) e restituisce una stringa formattata. Se l'utente non viene trovato, l'operatore Elvis ?: fornisce un valore predefinito. Nel secondo esempio, let converte una stringa in numero con un possibile null.
run è un analogo di let con accesso tramite this, che restituisce il risultato della lambda. run è comodo per calcoli che richiedono il contesto dell'oggetto ma restituiscono un nuovo valore. Chiamare run() senza oggetto (come run { ... }) crea un ambito temporaneo per le variabili. with è una non-estensione con accesso tramite this, adatta per raggruppare chiamate senza concatenamento.
// run come calcolo basato sul contesto
val transformed = listOf(1, 2, 3).run {
filter { it > 1 }.map { it * 10 }
}
println(transformed) // [20, 30]
// with per raggruppare chiamate
val info = with(StringBuilder()) {
append("Name: ")
append("Alice")
toString()
}
Nell'esempio, run su una lista restituisce il risultato del filtraggio e del mapping — la trasformazione stessa, non la lista. with prende StringBuilder come argomento (non tramite punto) e internamente lavora con this: append, append, toString. Il risultato di with è la stringa info. La scelta tra run e with è spesso soggettiva, ma with è preferibile quando l'oggetto viene passato da un altro punto senza concatenamento.
with si differenzia dalle altre scope functions in quanto non è una funzione di estensione. Prende l'oggetto come primo argomento e la lambda come secondo. All'interno della lambda, l'oggetto è accessibile tramite this. with è comodo per raggruppare chiamate di metodi dell'oggetto senza salvare il risultato o quando è necessario “entrare” temporaneamente in un contesto per leggere più proprietà.
val user = User("Alice", "alice@example.com", "Admin")
val summary = with(user) {
"User $name has role $role and email $email"
}
println(summary)
// with per inizializzazione componenti UI
with(TextView(context)) {
text = "Hello"
textSize = 18.0f
setTextColor(Color.BLUE)
}
Nel primo esempio, with forma la stringa summary accedendo direttamente alle proprietà di user tramite this. Senza with, si dovrebbe scrivere "User ${user.name} has role ${user.role}" — ripetizione di user. Nel secondo esempio, with raggruppa la configurazione di TextView: tutti i metodi e le proprietà vengono chiamati su un singolo oggetto senza ripetizione. Svantaggio di with — impossibilità di usarlo in una catena, poiché non è un'estensione e non restituisce l'oggetto di contesto.
Domande frequenti
Utilizzare apply — restituisce l'oggetto e fornisce l'accesso tramite this, consentendo di impostare le proprietà senza ripetere il nome dell'oggetto. also è adatto se è necessario un parametro it esplicito per distinguerlo dalle proprietà dell'oggetto.
let passa il contesto tramite it (parametro esplicito), run — tramite this (ricevitore implicito). let è comodo per controlli null e trasformazioni, run — per calcoli con contesto. Entrambi restituiscono il risultato della lambda.
with non è un'estensione — prende l'oggetto come argomento. Utilizzare with per raggruppare chiamate quando non è necessario il concatenamento. apply è un'estensione che restituisce l'oggetto, comodo in inizializzatori e pattern builder.
Sì, ma fare attenzione a this — this annidato crea confusione. Utilizzare let o also con nomi espliciti per contesti annidati per evitare ambiguità quando si fa riferimento all'oggetto esterno.
Tutte le scope functions sono inline, quindi non creano overhead da classi anonime. La differenza di prestazioni tra di loro è trascurabile. La scelta dovrebbe basarsi sulla leggibilità e le convenzioni, non sulla velocità.
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