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).
readln(): legge una riga di testo e la restituisce comeString;readlnOrNull(): comereadln(), ma restituisceString?(può valerenull);- convertire il testo in numeri:
toInt(),toDouble(),toIntOrNull(); - il valore
null, l'operatore?., l'operatore?:e l'operatore!!; - perché
readln()è il comando giusto da usare oggi, e come tradurre il vecchioreadLine().
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.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")
}Inizio e Totale: 5 euroEsatto: println("Inizio") va a capo, poi due print restano sulla stessa riga e il println(" euro") finale la chiude.Inizio, Totale: 5, euroNo: print non va mai a capo, quindi Totale: 5 euro resta su 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.
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.
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.
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().
- Nella console vedi anche i valori che scrivi tu, uno per riga: fanno parte dell'output, come in una console vera.
- Le due
readln()leggono le due righe nell'ordine: la prima riga va ina, la seconda inb. toInt()trasforma il testo in numero: senza di lui12 + 30diventerebbe1230.
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().Le variabili lette con readln() sono String: finché non le converti con toInt() o toDouble(), sono testo.
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".
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.
toIntOrNull()prova a convertirepippo: non ci riesce e restituiscenull.- L'operatore
?:legge quelnulle mette al suo posto il valore di riserva0. - Quindi
nvale0e il programma stampa1, senza fermarsi con un errore.
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.
| Scrittura | Tipo | Significato |
|---|---|---|
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() ?: 0 | Int | "converti solo se il valore c'è; in ogni caso dammi un numero" |
readlnOrNull()!! | String | "sono sicuro al 100% che non è null" (se sbagli, errore) |
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.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.
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 vecchio | Codice 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
| Scrivi | Significato |
|---|---|
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() ?: 0 | un 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 ?: 0 | uso sicuro di un valore che può mancare |
- readln() non ha trovato altri dati in input → i dati sono finiti e
readln()non può restituire niente. La console si ferma e aspetta: scrivi il valore e premi Invio, oppure usareadlnOrNull()se i dati possono mancare davvero. - Il valore è null: non posso usarlo con !! → hai usato
!!su un valore che eranull. - Non posso convertire "abc" in un numero → il testo letto non è numerico; usa
toIntOrNull() ?: 0. - Somma di testi invece di numeri: se dimentichi
toInt(),"3" + "4"fa"34". - Se il programma dice Nome non definito: "readInt" → in Kotlin non esistono
readInt()ereadDouble(): scrivireadln().toInt()ereadln().toDouble().
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!
readln().toDouble()legge una riga di testo e la converte subito in numero decimale.- Senza le parentesi,
voto1 + voto2 + voto3 / 3dividerebbe solo il terzo voto per 3, perché la divisione ha la precedenza sulla somma. - Se i voti fossero stati letti con
toInt(), la media7 / 2avrebbe perso i decimali. Leggendo subito deiDoubleil 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.
readlnOrNull()?.toIntOrNull() ?: 0gestisce tre casi: dato presente e numerico, dato non numerico, dato assente. In tutti i casicopieè un numero.- Il primo
?.lavora sullaString?direadlnOrNull(): se il dato manca, saltatoIntOrNull()e si arriva subito al?: 0. println()senza argomenti lascia una riga vuota, che separa le domande dal preventivo.copie * costoè il prodotto fra unInte unDouble: Kotlin lo accetta e restituisce unDouble.
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.
readln()legge una riga garantendo che ci sia: restituisce unaStringe non serve nessun!!.readln().toInt()legge e converte in un solo passaggio; se il dato non è un numero il programma si ferma con un messaggio chiaro (in alternativareadln().toIntOrNull() ?: 0).- Il prezzo è un
Doubleperché può avere i centesimi:readln().toDouble(). - 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.
- Le tre letture vanno fatte nello stesso ordine in cui il programma le chiede: nome, minima, massima.
readlnOrNull()?.toDoubleOrNull() ?: 0.0è la forma robusta: se il dato manca o non è un numero, al posto del valore arriva0.0e il programma continua.- La media è
(minima + massima) / 2: le parentesi servono, altrimenti verrebbeminima + massima / 2. - L'escursione è la differenza fra massima e minima: con
7.5e-2.5vale10.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.
readln()dà unaString, anche quando contiene cifre.- Senza
.toInt(),annoAttuale - annonon è una sottrazione fra numeri: il programma si ferma con un errore di tipo. - Con
readln().toInt()il valore diventa unInte il conto funziona: con i dati di prova2026 - 2008 = 18. L'anno attuale non è scritto nel programma, ma letto dall'input: così non invecchia. - Se i dati potessero mancare o non essere numerici, la forma robusta sarebbe
readlnOrNull()?.toIntOrNull() ?: 0.
readln() e so spiegare che tipo ottengotoInt() e toDouble()toIntOrNull() con ?: per non far fermare il programmareadln() e readlnOrNull()?., ?: e so perché !! è pericoloso