Capitolo 11

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.

In questo capitolo impari

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).
Un errore: 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.
Un errore: manca l'istanzaRettangolo(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.

  1. prezzo è var, quindi magazzino[1].prezzo = 1.5 funziona: la lista contiene riferimenti agli oggetti, e l'oggetto viene modificato davvero.
  2. La somma ora è 2.5 + 1.5 = 4.0. Attenzione al .0: sono Double, e nella console i decimali si vedono con il punto.
  3. filter { it.prezzo > 2 } tiene solo il Pane, quindi count() è 1.
  4. map { it.nome } trasforma ogni oggetto nel suo nome: si ottiene la lista [Pane, Latte].
Leggere una proprietà dentro una lista

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à.

Array o lista?

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?

Lancia un erroreNo: find non dà errore, ti dice semplicemente che non ha trovato niente.
Restituisce null: va gestito con ?., ?: o un controllo ifEsatto: per questo il risultato di find è un valore che può mancare.
Restituisce il primo elemento della listaQuello confonderebbe "non trovato" con "trovato il primo": Kotlin usa 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.

I metodi delle liste funzionano anche con gli oggetti

map, filter, sortedBy, count funzionano su qualsiasi lista: dentro le graffe { } scrivi it.proprieta per indicare la proprietà dell'oggetto che ti interessa.

max() su una lista vuota

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.

  1. Il primo find trova Anna (voto 8) e bravo?.nome stampa Anna.
  2. Il secondo find non trova nessuno con voto 10, quindi restituisce null: println(assente) stampa null.
  3. L'ultima riga gestisce il caso mancante con ?. e ?:: invece di null stampa nessuno.
  4. Se avessi scritto assente.nome senza protezione, il programma si sarebbe fermato con un errore.
find può restituire null

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?

Un intArrayOf(...) fissato a 100 posizioniFunziona a forza di "posti vuoti" e numeri magici: la dimensione deve essere decisa alla creazione.
Una mutableListOf<Int>() con dentro add a ogni giroEsatto: la lista cresce da sola, quindi non devi indovinare in anticipo quanti elementi saranno.
Un array di 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.

Le proprietà val non si possono cambiare

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.

In che ordine escono i gruppi

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

ScriviCosa 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].proprietalegge una proprietà dell'elemento in posizione i
lista[i].proprieta = xcambia una proprietà var dell'elemento
for (x in lista) somma += x.valoretotale 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 }.

  1. La lista contiene oggetti Libro: per stampare usiamo l.titolo e l.pagine.
  2. biblioteca.map { it.pagine } costruisce la lista dei soli numeri di pagine; .sum() li somma tutti.
  3. La media divide il totale per biblioteca.size, cioè il numero di libri; toDouble() evita la divisione intera.
  4. maxByOrNull { it.pagine } restituisce il libro con più pagine: il null è possibile (lista vuota), quindi lo controlliamo con if (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().

  1. Il ciclo for (i in 1..3) legge i dati a coppie e riempie la lista con add: la lista è modificabile proprio perché deve crescere.
  2. filter { it.prezzo < 2.0 } crea una nuova lista con i soli prodotti economici: il magazzino originale resta completo.
  3. maxByOrNull trova l'oggetto con il prezzo più alto; il controllo if (x != null) è prudente quando la lista potrebbe essere vuota.
  4. map { it.prezzo }.sum() estrae i prezzi e li somma in una sola espressione.
  5. L'ultimo add funziona solo su mutableListOf: su una lista creata con listOf darebbe 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).

  1. La lettura dei dati riempie la lista di squadre con un ciclo, come negli altri esercizi.
  2. La stampa usa lo schema plurale/singolare: for (s in ...) scorre la classifica e il contatore posizione scrive il numero in testa alla riga.
  3. sortedByDescending { it.punti } restituisce una nuova lista ordinata dal punteggio più alto: non cambia l'ordine di squadre.
  4. squadre[0].punti += 3 modifica l'oggetto dentro la lista: funziona perché punti è dichiarato con var.
  5. 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.

  1. find non si ferma quando non trova niente: restituisce null. Qui cerca Burro, che non è nel magazzino.
  2. Leggere cercato.prezzo su 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'è".
  3. La forma corretta è cercato?.prezzo ?: "nessuno": "il prezzo se il prodotto c'è, altrimenti nessuno".
  4. Con Pane al posto di Burro lo stesso codice stampa Prezzo: 2.5: la protezione non cambia il risultato quando il prodotto c'è.
  • so creare una lista di oggetti e un array di oggetti
  • so scorrere l'elenco e calcolare somma, media e massimo
  • so filtrare con filter, contare con count e cercare con find
  • so gestire il null che find può restituire
  • so aggiornare gli oggetti dell'elenco distinguendo le proprietà var dalle val
  • so mettere tutto insieme in un piccolo report ordinato
  • so leggere un messaggio di errore e correggere il programma da solo