Kotlin è stato progettato, in molti casi, in modo da conferire agli oggetti un elevato grado di mutabilità. Al fine di scrivere codice chiaro e
sicuro, è comunque conveniente sfruttare la mutabilità solo nei casi in cui ciò sia strettamente necessario. Con le strutture dati vale lo stesso principio, ed
infatti al momento di utilizzare liste, insiemi e mappe (con interfacce e classi che richiamano in pieno quelle di Java) si può
optare per una tra tre opzioni diverse: mutabili, non mutabili, e proiezioni read-only di strutture mutabili.
Al di là di quante siano le strutture dati effettivamente disponibili in un linguaggio, i tre meccanismi di fondo sono la lista (oggetti
memorizzati per posizione, con ammissione di duplicati), il set (memorizzazione di elementi senza indicazione della posizione né duplicazione) e
la mappa (collezione di coppie chiave/valore): in Kotlin, come si può facilmente immaginare, sono disponibili tutti.
List e Set
Una lista può essere creata con alcuni metodi di utilità:
// lista mutabile
val listaMutabile= mutableListOf<String>("Alessio", "Andrea", "Annalisa",
"Simona", "Francesca")
// lista non mutabile
val lista=listOf<String>("Alessio", "Andrea", "Annalisa",
"Simona", "Fracesca")
Nel primo caso abbiamo creato una lista che può essere modificata, nel secondo una immutabile, entrambe contenenti gli stessi elementi.
Sia l'una sia l'altra lista potranno essere consultate in ogni loro aspetto (verifica della dimensione, lettura di singoli elementi o
di sottoliste, iterazione, etc.) ma solo l'oggetto listaMutabile potrà essere modificato. Ecco alcune operazioni read-only che possono
essere eseguite:
// dimensione della lista
// Risultato: 5
val dimensione=lista.size
// primo elemento
// Risultato: "Alessio"
val primo=lista.first()
// ultimo elemento
// Risultato: "Francesca"
val ultimo=lista.last()
// lettura del terzo elemento
// Risultato: "Annalisa"
val terzoElemento=lista[2]
// sottolista dal secondo al quarto elemento (dall'indice 1 incluso al 4 escluso)
// Risultato: ["Andrea", "Annalisa", "Simona"]
val sottolista=lista.subList(1,4)
// indice di un elemento
// Risultato: 2
val posizioneAnnalisa=lista.indexOf("Annalisa")
// produzione di una lista invertita
// Risultato: [Francesca, Simona, Annalisa, Andrea, Alessio]
val listaInversa=lista.asReversed()
// produzione di una lista disordinata
// Risultato: [Alessio, Andrea, Simona, Francesca, Annalisa]
val listaDisordinata=lista.shuffled()
Sull'oggetto listaMutabile, oltre a quelle viste, possiamo invocare una serie di operazioni
che producono modifiche al contenuto della struttura come inserimenti, cancellazioni e via dicendo. Vediamone
alcuni:
// aggiungiamo la stringa "Ivano"
listaMutabile.add("Ivano")
// aggiungiamo una Collection di nuove stringhe
listaMutabile.addAll(listOf("Sonia", "Loredana", "Filippo"))
// rimuoviamo l'elemento in seconda posizione (la stringa "Andrea")
listaMutabile.removeAt(1)
// rimuoviamo, indipendentemente dalla posizione, la stringa "Fracesca"
listaMutabile.remove("Francesca")
Come terza possibilità, si può definire un riferimento di tipo List che fornisca una versione
read-only di una struttura dati seppur mutabile. Dopo la seguente dichiarazione:
val mutabileReadOnly:List<String> = listaMutabile
su mutabileReadOnly si potranno invocare solo metodi in lettura: nessun add o remove per intenderci.
Sulle altre strutture dati ci si può comportare in maniera simile. Quello che segue è un Set, dichiarato non
mutabile:
// creazione di un Set
// il numero 8 duplicato non verrà accettato
val setNumeri=setOf<Int>(8, 6, 7, 2, 4, 8, 10)
// otteniamo la dimensione: 6, in questo caso
val dimensione=setNumeri.size
// restituisce true perchè il numero 4 è incluso nel Set
val esiste4 = setNumeri.contains(4)
// visto che è un set di interi si può eseguire la somma
val totale=setNumeri.sum()
// stampa di ogni elemento
setNumeri.forEach { println(it) }
Gli elementi di questo Set di interi possono essere letti e sfruttati in una serie di operazioni
(il metodo sum esiste perché la struttura dati contiene numeri). Si noti che abbiamo volutamente
proposto un valore duplicato in fase di inizializzazione (abbiamo fornito due volte il numero 8) ma ne è stato accettato
solo uno in quanto la struttura dati Set non ammette duplicati.
Creando un Set mutabile si potranno svolgere diverse operazioni di modifica ma si ricordi che non essendo contemplata la posizione degli elementi
in questa struttura dati non si potrà impartire nessuna direttiva che si basi su questo tipo di informazione.
// creiamo un set mutabile di tre elementi
val setMutabile=mutableSetOf<Int>(1,2,3)
// aggiungiamo il valore 5
setMutabile.add(5)
// aggiungiamo altri tre valori: 7, 8, 9
setMutabile.addAll(listOf(7,9,8))
// rimuoviamo il valore 9
setMutabile.remove(9)
Si possono eseguire delle conversioni tra tipologie diverse di Set, nonché tra Set e liste. Ad esempio,
il Set non mutabile che risponde al riferimento setNumeri può essere convertito nei seguenti modi:
// conversione del set numerico contenente [8, 6, 7, 2, 4, 10]
val setNumeriMutabile=setNumeri.toMutableSet()
// ora accetta modifiche e con l'aggiunta del numero 55 diventa
// [8, 6, 7, 2, 4, 10, 55]
setNumeriMutabile.add(55)
// convertito in un Set ordinato
// da [8, 6, 7, 2, 4, 10] diventa [2, 4, 6, 7, 8, 10]
val setNumeriOrdinato=setNumeri.toSortedSet()
// è anche mutabile e con aggiunta di 1 diventa
// [1, 2, 4, 6, 7, 8, 10]
setNumeriOrdinato.add(1)
Con la seconda conversione, lo si è trasformato in un Set ordinato che non solo presenta i valori
al suo interno in ordine crescente ma anche i nuovi elementi inseriti verranno presentati nella giusta posizione.
Ad esempio, abbiamo aggiunto il valore 1, minore degli altri già inseriti, ma al momento della stampa viene
mostrato per primo.
Si possono eseguire analoghe conversioni tra liste e set ed in questo caso l'utilità può consistere
nell'eliminazione di duplicati. Immaginiamo di voler ottenere da una lista di numeri solo i valori distinti
senza ripetizioni. Possiamo eseguire velocemente il tutto passandola in un Set che al momento della sua
inizializzazione provvederà direttamente ad eliminare i duplicati.
// la lista contiene: [3, 4, 6, 4, 3, 3, 4, 6, 4, 3, 3, 4, 6, 4, 3]
val numeri= listOf<Int>(3,4,6,4,3,3,4,6,4,3,3,4,6,4,3)
// trasformazione in Set: [3, 4, 6]
val setDaLista=numeri.toSet()
Il Set finale ottenuto conterrà solo tre valori (3,4,6) che corrispondono al contenuto della lista
iniziale epurata di tutti i duplicati.
Mappe
Altra struttura dati fondamentale in ogni ambito di programmazione è quella che nel mondo Java/Kotlin
prende il nome di mappa, con la sua implementazione principale HashMap: una struttura dati che raccoglie delle
coppie chiave/valore, dove la prima serve da indice per recuperare in maniera efficiente il secondo.
In Kotlin, possiamo recuperare un riferimento ad una HashMap usando il metodo hashMapOf che sarà parametrizzato
in base al tipo di oggetti che vorremo usare come chiave e valore. Creiamo una pagella scolastica mediante una HashMap, la
chiave costituirà la materia ed il valore il voto in essa conseguito:
// creazione della mappa
val pagella=hashMapOf<String, Int>()
// operazioni di put per inserire accoppiate chiave/valore
// struttura finale di pagella {Storia=7, Geografia=8, Matematica=6, Italiano=8}
pagella.put("Italiano", 8)
pagella.put("Storia", 7)
pagella.put("Geografia", 8)
pagella.put("Matematica", 6)
// set di chiavi
val setMutabileDiChiavi = pagella.keys
// collection di valori
val collectionValori = pagella.values
// calcolo della media
val mediaVoti= collectionValori.average()
Dopo aver creato la struttura dati, con il metodo put inseriamo una serie di accoppiate
chiave/valore. Si potrebbero leggere con l'operazione complementare get: ad esempio,
pagella.get("Italiano")
restituirebbe il voto di Italiano. Visto che le chiavi sono tutte
univoche se le estraiamo in blocco vengono fornite come un Set (collection senza duplicati) e ciò potrebbe
darci l'indicazione delle materie studiate nella classe. Tale Set non fornirebbe però la risposta in ordine
di inserimento (l'ordine delle put). I valori possono, al contrario, essere duplicati pertanto
possono essere restituiti come una Collection tipizzata: in questo caso, ne otteniamo una di interi che
offre il metodo average per il calcolo della media.