1. List e MutableList: cosa sono e in cosa differiscono
Se un array in Kotlin e una “scatola di dimensione fissa” (come un contenitore in magazzino: quante celle ti danno, quelle hai), allora List e una “lista” che puo crescere e ridursi durante l’esecuzione del programma. Ma la cosa principale che rende List davvero una lista e l’ordine: gli elementi sono memorizzati in una sequenza definita e ogni elemento ha una posizione (indice). Kotlin descrive esplicitamente List come una collezione con memorizzazione ordinata e accesso indicizzato.
Allo stesso tempo e importante ricordare una sottigliezza: List e un’interfaccia read-only. Cioe, tramite un riferimento di tipo List puoi leggere, ma non puoi modificare la lista. Se invece ti serve aggiungere, eliminare o sostituire elementi, usi MutableList (che estende le capacita della lista con operazioni di scrittura).
Una piccola tabella di riferimento:
| Tipo | Si puo leggere per indice | Si puo aggiungere/rimuovere | Modo tipico di creare |
|---|---|---|---|
|
si | no | |
|
si | si | |
2. Indici e accesso agli elementi
Ordine e indici: perche il primo elemento e 0
Quando lavori con una lista, quasi sempre dici mentalmente “primo”, “secondo”, “ultimo”. Il computer invece dice “0-esimo”, “1-esimo”, “2-esimo”… e cosi via. Gli indici in Kotlin iniziano da zero e arrivano fino a lastIndex, dove lastIndex = size - 1. Non e una “caratteristica di Kotlin”, ma uno stile comune alla maggior parte dei linguaggi.
E utile tenere a mente un piccolo schema:
size = 4
indici: 0 1 2 3
^ ^
primo lastIndex
Ed e qui che nasce il classico errore da principiante: provare ad accedere a list[size] come se fosse “l’ultimo elemento”. Purtroppo size e il numero di elementi, non l’ultimo indice. L’ultimo indice e size - 1 oppure lastIndex.
Mini-esempio:
fun main() {
val xs = listOf(10, 20, 30, 40)
println(xs.size) // 4
println(xs.lastIndex) // 3
}
Lettura degli elementi: list[i] e get(i)
Quando hai gia accettato la filosofia “gli indici iniziano da zero”, il passo successivo e imparare a prendere un elemento. In Kotlin ci sono due forme che fanno la stessa cosa: list[i] e list.get(i). La prima si legge piu facilmente, la seconda a volte e utile quando vuoi sottolineare che si tratta proprio di una chiamata di metodo.
Esempio:
fun main() {
val words = listOf("one", "two", "three", "four")
println(words[0]) // one
println(words.get(2)) // three
println(words[3]) // four
}
Se l’indice e sbagliato (per esempio, -1 oppure size), il programma andra in errore per fuori limite. E normale: la lista si protegge dal “prendimi un elemento dall’universo parallelo”.
3. Operazioni di base di MutableList
Sostituzione di un elemento per indice
Quando passi da List a MutableList, inizia la magia: possiamo modificare gli elementi. L’operazione piu semplice e sostituire il valore in una posizione esistente. La sintassi sembra quella di un array: list[i] = newValue.
Internamente e l’equivalente di set(i, newValue), ma la notazione con [] e piu leggibile.
Esempio:
fun main() {
val items = mutableListOf("a", "b", "c")
items[1] = "B" // abbiamo sostituito l’elemento con indice 1
println(items) // [a, B, c]
}
Qui c’e un significato importante: abbiamo sostituito l’elemento nella posizione 1. Non abbiamo “inserito” nulla, la dimensione non e cambiata e gli altri elementi non si sono spostati.
Aggiunta di elementi: add(value)
Ora parliamo della crescita della lista. Nei problemi reali quasi sempre inizi con una lista vuota e la riempi gradualmente: l’utente inserisce task, prodotti nel carrello, voci di spesa, nomi dei partecipanti — qualunque cosa.
MutableList.add(value) aggiunge un elemento in fondo alla lista. E una delle operazioni piu frequenti, ed e comodo pensarla come “scrivere in fondo”.
Esempio:
fun main() {
val numbers = mutableListOf(1, 2, 3)
numbers.add(4)
numbers.add(5)
println(numbers) // [1, 2, 3, 4, 5]
println(numbers.size) // 5
}
Se provi una leggera gioia nel vedere che size cresce, congratulazioni: hai appena vissuto la “prima crescita del contenitore”. Un array non sa farlo.
Rimozione per indice: removeAt(index) e l’effetto di spostamento
La rimozione e il punto in cui gli indici iniziano a comportarsi da indici. Cioe, dopo aver rimosso un elemento, tutti gli elementi a destra “scorrono a sinistra” e i loro indici cambiano. E logico: se in una fila di persone una se ne va, tutti quelli dietro si avvicinano all’inizio.
removeAt(index) rimuove l’elemento alla posizione index e restituisce l’elemento rimosso. E comodo: puoi stampare subito cosa hai rimosso oppure riusare il valore piu avanti.
Esempio:
fun main() {
val items = mutableListOf("x", "y", "z")
val removed = items.removeAt(0)
println(removed) // x
println(items) // [y, z]
}
Schema visivo dello spostamento:
prima: [ x, y, z ]
indici 0 1 2
removeAt(0)
dopo: [ y, z ]
indici 0 1
Se conservi dei “numeri importanti” degli elementi (per esempio, l’utente ha scelto “rimuovi l’elemento n. 2”), dopo una rimozione i numeri degli altri cambiano. Non e un bug, e matematica.
Rimozione per valore: remove(value)
MutableList ha anche un altro modo di rimuovere: remove(value). A differenza di removeAt, non lavora con la posizione, ma con il valore stesso: “rimuovi l’elemento "bread"”.
Qui iniziano due idee importanti.
La prima: remove(value) restituisce Boolean. true — se la rimozione e avvenuta, false — se quel valore non esisteva (cioe la lista non e cambiata).
La seconda: se ci sono piu valori uguali, allora remove(value) rimuove la prima occorrenza trovata, non “tutte in una volta”.
Esempio:
fun main() {
val products = mutableListOf("milk", "bread", "tea", "bread")
val ok1 = products.remove("bread")
println(ok1) // true
println(products) // [milk, tea, bread]
val ok2 = products.remove("coffee")
println(ok2) // false
println(products) // [milk, tea, bread]
}
Nota: "bread" compariva due volte, e dopo la rimozione ne e rimasta una. E normale: abbiamo rimosso solo la prima occorrenza.
Verifica di presenza: contains(value) e l’operatore in
Quando lavori con le liste, spesso serve la domanda “c’e gia?”. Per esempio, “questo prodotto e gia nel carrello?”, “questo comando e supportato?”, “questo task e gia stato aggiunto?”.
Puoi scrivere list.contains(value), oppure — piu leggibile — value in list. Il significato e lo stesso, solo che la seconda forma e piu vicina al linguaggio naturale.
Mini-esempio:
fun main() {
val allowed = listOf("add", "list", "remove")
println(allowed.contains("list")) // true
println("add" in allowed) // true
println("exit" in allowed) // false
}
La lista puo contenere duplicati (a differenza di Set), e contains / in rispondono solo “esiste almeno un elemento cosi oppure no”. Kotlin sottolinea che List ammette duplicati e memorizza gli elementi in ordine.
4. Pratica: “Lista della spesa v0”
Ora realizzeremo un piccolo frammento di applicazione che crescera insieme al corso. Per ora senza comandi complessi e senza architettura: mostreremo semplicemente come MutableList aiuta a conservare i dati e come l’input dell’utente permette di aggiungere e rimuovere.
Faremo un modello il piu semplice possibile: una lista di stringhe MutableList<String>, dove ogni stringa e un prodotto.
Stampa della lista “in modo umano”
Iniziamo con la funzione di stampa. Sappiamo gia usare i cicli, quindi iteriamo sui prodotti e li mostriamo con i numeri. Nota: il numero per una persona inizia da 1, mentre l’indice nella lista inizia da 0. Quindi “numero umano = indice + 1”.
fun printShoppingList(items: List<String>) {
println("Lista della spesa:")
for (i in 0..items.lastIndex) {
println("${i + 1}. ${items[i]}")
}
}
Se la lista e vuota, 0..items.lastIndex sembrera strano (per esempio, 0..-1). Per evitare sorprese, aggiungiamo una semplice protezione.
fun printShoppingList(items: List<String>) {
if (items.isEmpty()) {
println("La lista della spesa e vuota.") // La lista della spesa e vuota.
return
}
println("Lista della spesa:")
for (i in 0..items.lastIndex) {
println("${i + 1}. ${items[i]}")
}
}
Aggiunta di un prodotto: add(...) e trim()
Ora scriviamo una funzione che legge una stringa, ripulisce gli spazi e la aggiunge alla lista. Qui usiamo il pattern familiare “letto → preparato → usato”.
fun addItem(items: MutableList<String>) {
print("Inserisci un prodotto: ")
val raw = readln()
val name = raw.trim()
if (name.isEmpty()) {
println("Nome vuoto — non aggiungo.") // Nome vuoto — non aggiungo.
return
}
items.add(name)
println("Aggiunto: $name") // per esempio: Aggiunto: milk
}
Rimozione per numero: removeAt(index) e conversione “numero → indice”
Ora facciamo la rimozione per numero inserito dall’utente (1, 2, 3, …). Dobbiamo trasformare il numero in indice: index = number - 1. E dobbiamo assolutamente controllare i limiti, altrimenti ciao, errore a runtime.
fun removeItemByNumber(items: MutableList<String>) {
print("Inserisci il numero del prodotto da rimuovere: ")
val number = readln().trim().toIntOrNull()
if (number == null) {
println("Non e un numero.") // Non e un numero.
return
}
val index = number - 1
if (index < 0 || index > items.lastIndex) {
println("Non esiste un prodotto con numero $number.") // Non esiste un prodotto con numero 10.
return
}
val removed = items.removeAt(index)
println("Rimosso: $removed") // per esempio: Rimosso: milk
}
Mini-main: solo per sentire che la lista vive
Per ora non costruiamo un vero menu di comandi (si trasforma facilmente in un “piccolo sistema operativo”), ma facciamo semplicemente 3 passi: aggiungere due prodotti, stampare, rimuovere per numero, stampare di nuovo.
fun main() {
val items = mutableListOf<String>()
addItem(items)
addItem(items)
printShoppingList(items)
removeItemByNumber(items)
printShoppingList(items)
}
Se lo esegui e provi a inserire valori, sentirai la cosa principale: MutableList e un contenitore comodo da modificare, e le operazioni add/removeAt fanno esattamente cio che c’e scritto sulla scatola.
5. Sfumature utili e promemoria
val per MutableList — va benissimo
A questo punto molti commettono un errore logico (e molto umano): “Se la lista cambia, allora la variabile deve essere var”. In realta, nella maggior parte dei casi la variabile dovrebbe essere val, perche non hai intenzione di riassegnare il riferimento a un’altra lista — vuoi cambiare il contenuto della lista corrente.
Esempio di “normalita corretta”:
fun main() {
val items = mutableListOf("a", "b")
items.add("c")
items[0] = "A"
println(items) // [A, b, c]
}
val impedisce di fare cosi:
items = mutableListOf("x") // non si puo, ma di solito non ci serve
Pero non impedisce di chiamare add, removeAt, remove e di modificare gli elementi. E come dire: “l’indirizzo di casa e fisso, ma i mobili dentro si possono spostare”.
Tabella-promemoria delle operazioni di MutableList
A volte e utile avere un promemoria compatto per non confondere i metodi (soprattutto remove e removeAt — amano sgattaiolare di notte e scambiarsi il significato).
| Operazione | Esempio | Cosa fa | Cosa restituisce |
|---|---|---|---|
| Lettura | |
Prende l’elemento per indice | |
| Sostituzione | |
Sostituisce l’elemento per indice | |
| Aggiungere | |
Aggiunge in fondo | |
| Rimuovere per indice | |
Rimuove l’elemento alla posizione |
elemento rimosso |
| Rimuovere per valore | |
Rimuove la prima occorrenza |
|
| Verificare presenza | |
C’e almeno un |
|
6. Errori tipici quando si lavora con List e MutableList
Errore n. 1: confondere size e lastIndex.
E molto facile voler scrivere list[list.size] e aspettarsi “l’ultimo elemento”, perche “size e la fine”. Ma size e il numero di elementi e gli indici vanno da 0 a size - 1 (cioe lastIndex). Evitare l’errore e semplice: “l’ultimo elemento” e list[list.lastIndex], non list[list.size].
Errore n. 2: provare a modificare una List creata con listOf.
listOf(...) restituisce una List read-only. E perfetta per una “lista di costanti” o un “insieme temporaneo di valori”, ma se poi vuoi usare add/remove, ti serve mutableListOf(...) e il tipo MutableList.
Errore n. 3: confondere removeAt(index) e remove(value).
E un classico del tipo “ho preso la chiave sbagliata”. removeAt(2) rimuove l’elemento alla posizione 2, mentre remove("2") (se lo scrivessi cosi) cercherebbe di rimuovere il valore "2". Aiuta un’abitudine: se nel nome c’e At, allora si lavora “sul posto”, per indice.
Errore n. 4: dimenticare che dopo removeAt gli elementi si spostano.
I principianti a volte salvano un indice, rimuovono un elemento e poi provano ad accedere al “successivo” usando il vecchio indice, sorprendendosi del comportamento. Dopo la rimozione la lista cambia: tutti gli elementi a destra “si spostano” a un indice in meno, ed e normale. Se scrivi codice che rimuove piu elementi di seguito, devi tenere conto di questo spostamento e agire con attenzione.
Errore n. 5: pensare che remove(value) rimuova tutte le occorrenze.
Quando nella lista ci sono duplicati, remove(value) rimuove solo la prima occorrenza. Questo corrisponde al significato della lista: l’ordine e importante e i duplicati sono ammessi. Se ti aspettavi “rimuovi tutto”, allora ti aspettavi un’altra operazione (ma oggi non la introduciamo — prima impariamo a gestire con sicurezza la meccanica di base).
Errore n. 6: fare var items = mutableListOf(...) “perche la lista cambia”.
Di solito e meglio val: cambi il contenuto, non riassegni la variabile a una nuova lista. var serve se davvero prevedi di sostituire l’intera lista. La disciplina del val di default rende il codice piu tranquillo: meno punti in cui “improvvisamente hanno riassegnato tutto” e meno probabilita di perdere dati.
GO TO FULL VERSION