Oggetti e array di oggetti
Nel capitolo precedente hai imparato a descrivere una cosa con una data class. Adesso metti insieme i pezzi: un elenco di oggetti è esattamente come una lista di numeri, solo che ogni elemento porta con sé più informazioni. È il modo in cui funzionano davvero i programmi: elenchi di prodotti, di studenti, di prenotazioni.
- creare liste di oggetti con
mutableListOfelistOf; - creare array di oggetti con
arrayOf(dimensione fissa); - scorrere l'elenco e fare calcoli: somma, media, massimo;
- cercare e filtrare con
filter,find,count,sortedBy; - modificare gli oggetti dell'elenco usando le proprietà
var; - mettere tutto insieme in un piccolo report.
Ripasso lampo: classi e oggetti
Un elenco di oggetti è fatto di tanti oggetti come quelli del capitolo 10, e i calcoli che gli fai sopra sono le funzioni del capitolo 9 applicate a un elenco.
Che cosa stampa questo programma?
data class Punto(val x: Int, val y: Int)
fun main() {
val p = Punto(1, 2)
val q = p.copy(x = 5)
println(p)
println(q)
}Punto(x=1, y=2) e Punto(x=5, y=2)Esatto: copy crea un nuovo oggetto copiando i valori e cambiando solo quelli indicati.Punto(x=5, y=2) due voltecopy non tocca l'oggetto di partenza: p resta Punto(x=1, y=2).x è una proprietà valcopy non modifica una proprietà: crea un oggetto nuovo con quel valore diverso, quindi funziona anche con val.copy restituisce sempre un oggetto nuovo e lascia intatto quello di partenza: per questo funziona anche quando le proprietà sono val.
Che cosa stampa questo programma?
data class Rettangolo(val base: Int, val altezza: Int) {
fun area(): Int = base * altezza
}
fun main() {
println(Rettangolo(3, 4).area())
}12Esatto: il metodo area() usa le proprietà dell'oggetto su cui lo chiami.Rettangolo(3, 4) è l'istanza su cui viene chiamato area().areaIl metodo restituisce un numero, non il suo nome.Il metodo lavora sull'oggetto su cui lo chiami: dentro vede le proprietà di quell'oggetto, senza che tu debba passargliele.
11.1 Un elenco di oggetti
Una lista di oggetti si costruisce come qualsiasi altra lista: la differenza è che dentro
ci sono oggetti, quindi per leggere un dato scrivi elemento.proprieta.
Con listOf(...) l'elenco non cambia più; con mutableListOf(...) puoi
aggiungere e togliere elementi con add e remove.
prezzoèvar, quindimagazzino[1].prezzo = 1.5funziona: la lista contiene riferimenti agli oggetti, e l'oggetto viene modificato davvero.- La somma ora è
2.5 + 1.5 = 4.0. Attenzione al.0: sonoDouble, e nella console i decimali si vedono con il punto. filter { it.prezzo > 2 }tiene solo il Pane, quindicount()è1.map { it.nome }trasforma ogni oggetto nel suo nome: si ottiene la lista[Pane, Latte].
magazzino[0] è il primo oggetto; magazzino[0].nome è il
nome di quel prodotto. L'ordine delle due parti conta: prima prendi l'elemento,
poi la sua proprietà.
L'array (arrayOf, intArrayOf, ...) ha una
dimensione fissa decisa alla creazione: puoi cambiare gli elementi
(pagella[0] = ...) ma non aggiungerne di nuovi.
La lista modificabile (mutableListOf) cresce e si accorcia con
add e remove. Quando l'elenco può cambiare dimensione usa la lista;
quando le posizioni sono sempre quelle usa l'array.
11.2 Calcoli su un elenco di oggetti
Gli algoritmi sono gli stessi dei cicli e delle liste: cambia solo che i numeri si prendono dalle proprietà. Somma, media e ricerca del massimo sono i tre più usati.
find non trova nessun elemento. Che cosa restituisce, e come lo gestisci?
find non dà errore, ti dice semplicemente che non ha trovato niente.null: va gestito con ?., ?: o un controllo ifEsatto: per questo il risultato di find è un valore che può mancare.null proprio per distinguerli.Se sai che l'elemento ci deve essere e non vuoi gestire il caso mancante, first { ... } è la scelta giusta; find è per quando può non esserci.
map, filter, sortedBy, count funzionano su
qualsiasi lista: dentro le graffe { } scrivi it.proprieta per indicare
la proprietà dell'oggetto che ti interessa.
Qui prezzi ha sempre tre elementi, quindi max() è sicuro. Su una
lista che può essere vuota servono maxOrNull() e un controllo di
null: è spiegato nel capitolo 6, sezione 6.3.
11.3 Cercare e filtrare
filter tiene solo gli elementi che rispettano una condizione,
count li conta, find restituisce il primo che trova
(o null se non c'è), sortedBy li mette in ordine.
- Il primo
findtrova Anna (voto 8) ebravo?.nomestampaAnna. - Il secondo
findnon trova nessuno con voto 10, quindi restituiscenull:println(assente)stampanull. - L'ultima riga gestisce il caso mancante con
?.e?:: invece dinullstampanessuno. - Se avessi scritto
assente.nomesenza protezione, il programma si sarebbe fermato con un errore.
find restituisce null quando non trova niente: prima di usare il
risultato controlla con if (x != null). Se invece sei sicuro che l'elemento esista,
puoi usare first { ... } e chiedere direttamente .proprieta, ma se
manca il programma si ferma con un errore.
11.4 Modificare gli oggetti dell'elenco
Se la proprietà è dichiarata con var, puoi cambiarla anche dopo aver messo
l'oggetto nella lista: basta arrivare all'elemento e assegnare il nuovo valore.
Devi leggere i voti finché l'utente non scrive -1: non sai in anticipo quanti saranno. Che cosa usi?
intArrayOf(...) fissato a 100 posizioniFunziona a forza di "posti vuoti" e numeri magici: la dimensione deve essere decisa alla creazione.mutableListOf<Int>() con dentro add a ogni giroEsatto: la lista cresce da sola, quindi non devi indovinare in anticipo quanti elementi saranno.StringIl problema non è il tipo dei dati, ma la dimensione: con un array resti comunque legato a un numero fisso di posizioni.Regola pratica: dimensione nota → array; dimensione che dipende dai dati → mutableListOf.
Se una proprietà è val, per "aggiornarla" devi creare un nuovo oggetto con
copy e sostituirlo nella lista: lista[i] = lista[i].copy(voto = 10).
Con var invece basta lista[i].voto = 10.
11.5 Mettere tutto insieme: un report
Un report è un programma che scorre un elenco di oggetti, calcola qualche totale e stampa il risultato in modo ordinato. Qui sotto c'è l'esempio completo di un piccolo scontrino.
Le chiavi della mappa costruita da groupBy compaiono nell'ordine in cui
compaiono per la prima volta scorrendo l'elenco: qui Cibo prima di
Casa, e Casa prima di Scuola, perché è l'ordine dei
prodotti. Dentro ogni gruppo, invece, gli elementi conservano esattamente l'ordine che avevano
nella lista di partenza. Se ti serve l'elenco dei gruppi in ordine alfabetico, ordina le
chiavi: for (categoria in perCategoria.keys.sorted()) { ... }.
11.6 Riepilogo
| Scrivi | Cosa fa |
|---|---|
listOf(Prodotto(...), ...) | elenco di oggetti non modificabile |
mutableListOf<Prodotto>() | elenco modificabile (cresce e si accorcia) |
arrayOf(Prodotto(...), ...) | array di oggetti, dimensione fissa |
lista.add(oggetto), lista.remove(oggetto) | aggiunge / toglie un elemento (solo liste modificabili) |
lista[i].proprieta | legge una proprietà dell'elemento in posizione i |
lista[i].proprieta = x | cambia una proprietà var dell'elemento |
for (x in lista) somma += x.valore | totale di una proprietà su tutti gli elementi |
lista.map { it.proprieta } | lista con una sola proprietà di ogni oggetto |
lista.filter { it.proprieta > n } | solo gli oggetti che rispettano la condizione |
lista.count { it.proprieta == x } | quanti oggetti rispettano la condizione |
lista.find { it.nome == "..." } | il primo oggetto che corrisponde (o null) |
lista.first { ... } | come find ma errore se non c'è |
lista.sortedBy { it.proprieta } | elenco ordinato per quella proprietà |
lista.maxByOrNull { it.proprieta } | l'elemento con il valore più alto |
lista.map { it.proprieta }.sum() | somma di una proprietà su tutti gli elementi |
lista.groupBy { it.categoria } | mappa categoria → elenco di oggetti |
11.7 Esercizi guidati
Crea una data class Libro(val titolo: String, val pagine: Int) e una lista con
tre libri: "Kotlin base" 300 pagine, "Storia" 150, "Fisica" 450.
Stampa ogni libro, il totale delle pagine, la media (due decimali) e
il libro più lungo con maxByOrNull.
Il totale si trova con un ciclo (somma += l.pagine) oppure con
biblioteca.map { it.pagine }.sum(). Il più lungo è
biblioteca.maxByOrNull { it.pagine }.
- La lista contiene oggetti
Libro: per stampare usiamol.titoloel.pagine. biblioteca.map { it.pagine }costruisce la lista dei soli numeri di pagine;.sum()li somma tutti.- La media divide il totale per
biblioteca.size, cioè il numero di libri;toDouble()evita la divisione intera. maxByOrNull { it.pagine }restituisce il libro con più pagine: ilnullè possibile (lista vuota), quindi lo controlliamo conif (piuLungo != null).
Leggi tre prodotti (nome e prezzo). Mettili in una
data class Prodotto(val nome: String, val prezzo: Double) e in una lista.
Stampa: quanti prodotti costano meno di 2 euro, i loro nomi, il prodotto più caro e
il totale di tutti i prezzi. Infine aggiungi in fondo il prodotto "Sconosciuto" da 1.0 euro.
Per i prodotti economici: magazzino.filter { it.prezzo < 2.0 }.
Il più caro: magazzino.maxByOrNull { it.prezzo }.
Il totale: magazzino.map { it.prezzo }.sum().
- Il ciclo
for (i in 1..3)legge i dati a coppie e riempie la lista conadd: la lista è modificabile proprio perché deve crescere. filter { it.prezzo < 2.0 }crea una nuova lista con i soli prodotti economici: il magazzino originale resta completo.maxByOrNulltrova l'oggetto con il prezzo più alto; il controlloif (x != null)è prudente quando la lista potrebbe essere vuota.map { it.prezzo }.sum()estrae i prezzi e li somma in una sola espressione.- L'ultimo
addfunziona solo sumutableListOf: su una lista creata conlistOfdarebbe errore.
Leggi tre squadre (nome e punti). Crea una data class Squadra(val nome: String, var punti: Int),
mettile in una lista e stampa la classifica ordinata dai punti più alti ai più bassi.
Poi aggiungi 3 punti di bonus alla prima squadra della lista (non importa quale sia) e
stampa di nuovo la classifica. Infine stampa il totale dei punti di tutte le squadre.
Per la classifica usa squadre.sortedByDescending { it.punti }.
Il bonus si dà così: squadre[0].punti += 3 (la proprietà è var).
- La lettura dei dati riempie la lista di squadre con un ciclo, come negli altri esercizi.
- La stampa usa lo schema plurale/singolare:
for (s in ...)scorre la classifica e il contatoreposizionescrive il numero in testa alla riga. sortedByDescending { it.punti }restituisce una nuova lista ordinata dal punteggio più alto: non cambia l'ordine disquadre.squadre[0].punti += 3modifica l'oggetto dentro la lista: funziona perchépuntiè dichiarato convar.- Dopo il bonus la classifica va ricalcolata: le posizioni possono essere cambiate, per questo il ciclo di stampa viene ripetuto dopo la modifica.
Il programma deve stampare il totale del magazzino e il prezzo del prodotto cercato per nome: il prodotto cercato è Burro, che non c'è, quindi deve scrivere Totale: 3.7 e Prezzo: nessuno (mettendo Pane al posto di Burro stamperebbe 2.5). Adesso non funziona: correggi.
find non dà errore quando non trova niente: restituisce un valore che può mancare. Per leggerne una proprietà serve ?., e ?: copre il caso in cui manchi.
findnon si ferma quando non trova niente: restituiscenull. Qui cercaBurro, che non è nel magazzino.- Leggere
cercato.prezzosu un valore che può mancare fa fermare il programma: il messaggio dice "Non posso leggere la proprietà da un valore di tipo Any?", cioè "qui il valore non c'è". - La forma corretta è
cercato?.prezzo ?: "nessuno": "il prezzo se il prodotto c'è, altrimenti nessuno". - Con
Paneal posto diBurrolo stesso codice stampaPrezzo: 2.5: la protezione non cambia il risultato quando il prodotto c'è.
filter, contare con count e cercare con findnull che find può restituirevar dalle val