Capitolo 3

Leggere i dati: l'input

I programmi diventano interessanti quando chiedono dei dati all'utente. In questo capitolo impari a leggere quello che viene scritto con readln(), a trasformarlo in numeri e a gestire i casi difficili: quando il dato non è un numero e quando i dati finiscono (la String? con null).

In questo capitolo impari

Ripasso lampo: le variabili e i conti

Serve tutto il capitolo 2: se sbagli, torna a rileggere quella sezione prima di andare avanti. Il secondo quiz, invece, torna al capitolo 1: i comandi di stampa e il numero di righe dell'output non si dimenticano.

Che cosa stampa questo programma?

fun main() {
    var vite = 3
    vite -= 1
    vite -= 1
    println("Vite rimaste: $vite")
}
Vite rimaste: 3No: -= cambia davvero il valore della variabile.
Vite rimaste: 1Esatto: da 3 si toglie 1 due volte, quindi si arriva a 1.
Un errore: vite è una costanteIn questo programma vite è dichiarata con var, quindi si può modificare.

Il valore di una variabile cambia solo quando il programma esegue un'assegnazione. Per sapere quanto vale alla fine bisogna leggere le istruzioni nell'ordine in cui verranno eseguite.

Che cosa stampa questo programma?

fun main() {
    println("Inizio")
    print("Totale: ")
    print("5")
    println(" euro")
}
Due righe: Inizio e Totale: 5 euroEsatto: println("Inizio") va a capo, poi due print restano sulla stessa riga e il println(" euro") finale la chiude.
Tre righe: Inizio, Totale: 5, euroNo: print non va mai a capo, quindi Totale: 5 euro resta su una riga sola.
Una riga sola: Inizio Totale: 5 euroNo: println("Inizio") chiude subito la prima riga, quindi l'output non può stare tutto su una riga.

Regola del capitolo 1: println stampa e va a capo, print stampa e resta sulla riga. Il numero di righe dell'output dipende da quanti println ci sono e da quanti print si usano per costruire ogni riga.

Dove si scrivono i dati

Nella console sotto il codice, come nella finestra Run di IntelliJ: quando il programma arriva alla prima readln() l'output si ferma lì e il cursore di scrittura compare in coda al testo (lo stato diventa ⏸ in attesa di dati…). Scrivi il valore e premi Invio: la prima readln() prende il primo valore, la seconda il secondo, e così via.

I dati li scrivi sempre tu: il programma non parte mai con dei valori suoi. Le lettere che digiti compaiono nella console una dopo l'altra, nello stesso punto in cui il programma sta leggendo, ed è quella la riga che readln() riceve. Un clic in un punto qualsiasi della console riporta il cursore in fondo all'output. Se ti serve solo vedere in fretta il risultato previsto, c'è il pulsante ⏎ prova con i dati dell'esempio sulla barra sotto la console: scrive lui i valori preparati per l'esempio, ma sei tu a deciderlo.

Quando i dati finiscono si dice che si è arrivati alla fine dell'input (in una console vera si preme Ctrl+D). Da quel momento: readln() si ferma con un errore, mentre readlnOrNull() restituisce null. Qui nella pagina puoi premere Ctrl+D mentre scrivi nella console (su Mac anche Cmd+D) oppure il pulsante ⏹ fine dei dati: serve per vedere con i tuoi occhi la differenza.

3.1 readln: leggere una riga di testo

readln() legge una riga di quello che l'utente scrive e la restituisce come testo (String). Si usa sempre con una variabile: val nome = readln(). Il valore che ottieni non può essere null: se non c'è niente da leggere, il programma si ferma con un errore invece di darti un valore rotto.

Prima la domanda con print, poi la lettura con readln

L'abitudine più comune nei programmi di console è stampare la domanda con print("Nome: ")senza andare a capo — e leggere subito dopo con readln(): così l'utente scrive il suo dato sulla stessa riga della domanda. È esattamente quello che vedi nella console di questo manuale: il cursore si posiziona in coda all'output, proprio dove la readln() sta leggendo. Con println invece la risposta finirebbe sulla riga sotto la domanda: funziona lo stesso, ma è meno leggibile.

Quante righe servono?

Tanti valori quanti sono le readln() del programma. Nell'esempio 2 servono due valori: prima il nome, poi la città. La console te ne chiede uno per volta: dopo il primo Invio il programma riparte, arriva alla seconda readln() e si ferma di nuovo ad aspettare il secondo valore, sempre con il cursore in coda all'output.

E se ti viene chiesto un dato che non vuoi dare? Puoi scrivere qualsiasi cosa: i valori sono tuoi e puoi sperimentare quanto vuoi. Se invece vuoi proprio vedere il risultato previsto dall'esempio senza digitare, usa ⏎ prova con i dati dell'esempio.

3.2 Trasformare il testo in numeri

Quello che si legge con readln() è sempre una String, anche se contiene cifre. Per fare conti serve toInt() (numeri interi) o toDouble() (numeri decimali). Si scrivono subito dopo la lettura: readln().toInt().

  1. Nella console vedi anche i valori che scrivi tu, uno per riga: fanno parte dell'output, come in una console vera.
  2. Le due readln() leggono le due righe nell'ordine: la prima riga va in a, la seconda in b.
  3. toInt() trasforma il testo in numero: senza di lui 12 + 30 diventerebbe 1230.

Che cosa stampa questo programma, se l'utente scrive 3 e poi 4?

fun main() {
    val a = readln()
    val b = readln()
    println(a + b)
}
7No: readln() restituisce testo, quindi a + b unisce i due testi invece di sommarli.
34Esatto: i due testi vengono attaccati. Per sommarli servirebbe readln().toInt().
Un errore di conversioneNon c'è nessuna conversione in questo programma: nessun errore, ma nemmeno una somma.

Le variabili lette con readln() sono String: finché non le converti con toInt() o toDouble(), sono testo.

Il punto, non la virgola

Kotlin vuole il punto per i numeri decimali: 1.75 va bene, 1,75 fa apparire l'errore "Non posso convertire ... in un numero decimale".

Stringa o numero?

readln() dà testo: se dimentichi toInt(), l'operatore + attacca i testi invece di sommarli. Con i due valori 3 e 4: readln() + readln() produce "34", mentre readln().toInt() + readln().toInt() produce 7.

3.3 Quando il dato non è un numero: toIntOrNull

Se l'utente scrive pippo dove ti aspetti un numero, toInt() fa fermare il programma con un errore. Quando l'input può essere sbagliato si usa toIntOrNull() (o toDoubleOrNull()): fa la conversione se possibile, altrimenti restituisce null. Unendolo all'operatore ?: si sceglie un valore di riserva.

  1. toIntOrNull() prova a convertire pippo: non ci riesce e restituisce null.
  2. L'operatore ?: legge quel null e mette al suo posto il valore di riserva 0.
  3. Quindi n vale 0 e il programma stampa 1, senza fermarsi con un errore.
Il comando da ricordare

readln().toIntOrNull() ?: 0 si legge: "leggi una riga, prova a convertirla in numero intero; se non è un numero, usa 0". È il modo più robusto di leggere un numero senza far fermare il programma. Con i decimali è readln().toDoubleOrNull() ?: 0.0.

3.4 Quando i dati finiscono: readlnOrNull e il valore null

readln() è comodissimo, ma ha un difetto: se i dati sono finiti il programma si ferma con un errore. Quando i dati possono mancare si usa readlnOrNull(), che restituisce una String?: una stringa che può anche valere null, cioè "nessun valore".

Kotlin non ti lascia usare un valore che potrebbe essere null senza dirlo: gli operatori ?. e ?: servono proprio a questo.

ScritturaTipoSignificato
readln()String"leggi una riga, sono sicuro che c'è" (errore se i dati sono finiti)
readlnOrNull()String?"leggi una riga se c'è; altrimenti dammi null"
readlnOrNull() ?: "vuoto"String"se non c'è niente, usa questo valore di riserva"
readlnOrNull()?.toIntOrNull() ?: 0Int"converti solo se il valore c'è; in ogni caso dammi un numero"
readlnOrNull()!!String"sono sicuro al 100% che non è null" (se sbagli, errore)
Prova a premere Ctrl+D

Nell'esempio 7 il programma chiede tre valori. Se al terzo, invece di scrivere, premi Ctrl+D (o il pulsante ⏹ fine dei dati), readlnOrNull() restituisce null e l'operatore !! fa apparire l'errore "Il valore è null: non posso usarlo con !!". È il suo mestiere: !! dice "qui non può mancare niente", e quando manca ti avverte invece di lasciar passare un valore sbagliato. Con readln() non serve mai scrivere !!.

Il programma esegue val s = readlnOrNull() senza che ci sia più niente da leggere (dati finiti). Quanto vale s?

"" (stringa vuota)No: una riga vuota è un dato vero e proprio. Qui non c'è proprio nessun dato.
nullEsatto: readlnOrNull() restituisce String? e, se i dati sono finiti, dà null.
Il programma si ferma con un erroreQuesto succede con readln(), non con readlnOrNull(): è proprio la differenza fra i due comandi.

Se poi usi s senza proteggerlo, Kotlin si ferma: per usarlo in sicurezza servono gli operatori ?. e ?:, che vedi in questo capitolo. Per sapere se s c'è e fare cose diverse nei due casi ti servirà il if, che arriva al capitolo 4.

Che cosa fa il punto interrogativo

testo?.uppercase() significa "esegui uppercase() solo se testo non è null, altrimenti dà null"; il ?: che segue mette il testo di riserva. Senza il ?. Kotlin segnalerebbe subito un errore, perché testo potrebbe non esserci. Qui interessa solo il ?.: i metodi delle stringhe come uppercase(), length e first() sono spiegati per davvero nel capitolo 8. Prova a premere Ctrl+D invece di scrivere: vedrai uscire (nessun testo), 0 e null.

3.5 readLine: il comando dei vecchi tutorial

In molti esempi che trovi online si vede readLine(): fa la stessa cosa di readlnOrNull(), cioè restituisce una String? che può essere null. La documentazione ufficiale di Kotlin consiglia di usare readln() al posto di readLine()!! e readlnOrNull() al posto di readLine(): sono più corti e più chiari. In questo manuale usiamo i comandi nuovi; se incontri readLine(), traduci così:

Codice vecchioCodice da scrivere oggi
readLine()!!readln()
readLine()readlnOrNull()
readLine()!!.toInt()readln().toInt()
readLine() ?: "vuoto"readlnOrNull() ?: "vuoto"
readInt(), readDouble()non esistono in Kotlin: usa readln().toInt(), readln().toDouble()

3.6 Un programma completo con input e output

Mettiamo insieme tutto: lettura dei dati, conversioni, valori di riserva, calcoli e stampa ordinata.

Stai leggendo un numero con readln() e l'utente potrebbe scrivere una parola. Quale scrittura evita di far fermare il programma?

readln().toInt()Questa fa fermare il programma con un errore appena il testo non è un numero.
readln().toIntOrNull() ?: 0Esatto: se la conversione non riesce si usa un valore di riserva e il programma continua.
readln()!!!! non converte niente in numero: serve solo a dire "sono sicuro che non è null", e qui non c'entra.

Le due protezioni si possono anche unire: readlnOrNull()?.toIntOrNull() ?: 0 gestisce sia i dati finiti sia il testo non numerico.

3.7 Riepilogo

ScriviSignificato
val s = readln()una String letta dall'input
val n = readln().toInt()un numero intero (Int)
val x = readln().toDouble()un numero decimale (Double)
readln().toIntOrNull() ?: 0un intero, oppure 0 se il dato non è un numero
val s = readlnOrNull()una String? (può essere null)
readlnOrNull() ?: "vuoto"una String, con valore di riserva
readlnOrNull()?.length ?: 0uso sicuro di un valore che può mancare
Errori tipici del terzo capitolo

3.8 Esercizi guidati

Chiedi tre voti (possono avere decimali), calcola la somma e la media e stampa i tre voti più il risultato. Attenzione: la media deve avere i decimali.

I voti sono già Double, quindi la divisione per 3 è decimale: val media = (voto1 + voto2 + voto3) / 3. Le parentesi servono!

  1. readln().toDouble() legge una riga di testo e la converte subito in numero decimale.
  2. Senza le parentesi, voto1 + voto2 + voto3 / 3 dividerebbe solo il terzo voto per 3, perché la divisione ha la precedenza sulla somma.
  3. Se i voti fossero stati letti con toInt(), la media 7 / 2 avrebbe perso i decimali. Leggendo subito dei Double il problema non si presenta.

Un centro stampa chiede il nome del cliente, il numero di fotocopie e il costo di una fotocopia. Stampa un preventivo di tre righe: nome, numero di copie, totale da pagare. Se l'input non è valido (per esempio il numero di copie non si legge), usa 0 e vai avanti senza errori.

Il totale è copie * costo: moltiplicando un Int per un Double ottieni un Double.

  1. readlnOrNull()?.toIntOrNull() ?: 0 gestisce tre casi: dato presente e numerico, dato non numerico, dato assente. In tutti i casi copie è un numero.
  2. Il primo ?. lavora sulla String? di readlnOrNull(): se il dato manca, salta toIntOrNull() e si arriva subito al ?: 0.
  3. println() senza argomenti lascia una riga vuota, che separa le domande dal preventivo.
  4. copie * costo è il prodotto fra un Int e un Double: Kotlin lo accetta e restituisce un Double.

Il cinema vende biglietti interi e ridotti. Chiedi in ordine: nome del cliente, quantità di biglietti interi, prezzo intero, prezzo ridotto. Poi calcola: il costo dei biglietti interi, il costo di 2 biglietti ridotti e il totale. Stampa uno scontrino con tutte le voci.

readln().toInt() legge e converte in una sola riga: non serve nessun !!. Il costo dei ridotti è 2 * prezzoRidotto.

  1. readln() legge una riga garantendo che ci sia: restituisce una String e non serve nessun !!.
  2. readln().toInt() legge e converte in un solo passaggio; se il dato non è un numero il programma si ferma con un messaggio chiaro (in alternativa readln().toIntOrNull() ?: 0).
  3. Il prezzo è un Double perché può avere i centesimi: readln().toDouble().
  4. I calcoli intermedi in variabili (costoInteri, costoRidotti) rendono lo scontrino più chiaro da scrivere e da correggere.

Scrivi da solo un programma che chiede tre dati e li legge in quest'ordine: il nome di una città, la temperatura minima e la temperatura massima (numeri decimali). Poi stampa una riga vuota e altre tre righe: la città, la temperatura media e l'escursione (cioè la massima meno la minima).

Con i dati Roma, -2.5, 7.5 l'output deve finire con Città: Roma, Media: 2.5 gradi e Escursione: 10.0 gradi. Attenzione al caso limite: se una temperatura non è un numero valido (per esempio l'utente scrive pippo), considerala 0.0 e vai avanti senza fermare il programma.

Leggi ogni temperatura con readlnOrNull()?.toDoubleOrNull() ?: 0.0: il ?. e il ?: coprono sia il dato assente sia il dato non numerico. Calcola prima media ed escursione, poi stampale.

  1. Le tre letture vanno fatte nello stesso ordine in cui il programma le chiede: nome, minima, massima.
  2. readlnOrNull()?.toDoubleOrNull() ?: 0.0 è la forma robusta: se il dato manca o non è un numero, al posto del valore arriva 0.0 e il programma continua.
  3. La media è (minima + massima) / 2: le parentesi servono, altrimenti verrebbe minima + massima / 2.
  4. L'escursione è la differenza fra massima e minima: con 7.5 e -2.5 vale 10.0.

Il programma deve chiedere il nome, l'anno di nascita e l'anno in cui siamo, e stampare l'età, ma non funziona. Eseguilo e, quando la console aspetta, scrivi Giulia e premi Invio, poi 2008, poi l'anno in cui siamo: leggi il messaggio di errore e correggi, fino a ottenere Giulia, 18 anni.

readln() restituisce sempre testo: per fare la sottrazione quel testo deve diventare un numero.

  1. readln() dà una String, anche quando contiene cifre.
  2. Senza .toInt(), annoAttuale - anno non è una sottrazione fra numeri: il programma si ferma con un errore di tipo.
  3. Con readln().toInt() il valore diventa un Int e il conto funziona: con i dati di prova 2026 - 2008 = 18. L'anno attuale non è scritto nel programma, ma letto dall'input: così non invecchia.
  4. Se i dati potessero mancare o non essere numerici, la forma robusta sarebbe readlnOrNull()?.toIntOrNull() ?: 0.
  • so leggere un dato con readln() e so spiegare che tipo ottengo
  • so convertire un dato letto in numero con toInt() e toDouble()
  • so usare toIntOrNull() con ?: per non far fermare il programma
  • so spiegare che differenza c'è fra readln() e readlnOrNull()
  • so usare ?., ?: e so perché !! è pericoloso
  • so scrivere i dati nella console del manuale e so dichiarare la fine dei dati
  • so leggere un messaggio di errore e correggere il programma da solo