Le stringhe e i loro metodi
Le stringhe sono il tipo di dato che userai più spesso: nomi, frasi, codici, righe di un file. Kotlin gli mette a disposizione decine di metodi pronti: questa è la cassetta degli attrezzi per pulire, tagliare, cercare e trasformare il testo.
- lunghezza e singoli caratteri:
length,s[i],first(),last(); - pulizia e formato:
trim(),uppercase(),lowercase(),padStart(); - ricerca e sostituzione:
contains,startsWith,indexOf,replace; - tagliare e dividere:
substring,split; - conversioni e formattazione:
toInt(),toDouble(),"%.2f".format(...); - costruire nuove stringhe con i cicli.
Ripasso lampo: mappe e conteggi
Le stringhe si spezzano in liste e si contano con le mappe: ripasso del capitolo 7.
Che cosa stampa questo programma?
fun main() {
val prezzi = mutableMapOf("caffe" to 1)
println(prezzi["te"])
println(prezzi["te"] ?: 0)
}null e poi 0Esatto: la chiave assente dà null, e ?: sostituisce il valore di riserva.0 e poi 0La prima riga non ha nessun ?:: stampa proprio null.Una chiave assente non è un errore: la mappa restituisce null, e il valore di riserva lo decide l'operatore ?:. Il programma si ferma solo se usi quel null come se ci fosse un valore.
Che cosa stampa questo programma?
fun main() {
val m = mutableMapOf("a" to 40, "b" to 15)
for ((k, v) in m) println("$k=$v")
}a=40 e b=15Esatto: nella coppia (k, v) il primo è la chiave e il secondo il valore.40=a e 15=bAttenzione all'ordine: prima la chiave, poi il valore.forSi può: è la destrutturazione della coppia chiave-valore.Nel ciclo su una mappa la coppia si spacchetta nell'ordine in cui è stata creata: prima la chiave, poi il valore. Scriverlo al contrario non dà errore, ma stampa le cose invertite.
8.1 Una stringa è una sequenza di caratteri
length dice quanti caratteri contiene la stringa, e ogni carattere ha un indice
che parte da 0: s[0] è il primo, s[s.length - 1] l'ultimo.
Un singolo carattere è di tipo Char.
c.isDigit(), c.isLetter(), c.isLetterOrDigit(),
c.isWhitespace(), c.isUpperCase(), c.isLowerCase(),
c.uppercaseChar(), c.lowercaseChar(), c.digitToInt().
Si usano dentro ai cicli sul testo.
8.2 Pulire e sistemare il testo
Spesso il testo arriva "sporco": spazi davanti e dietro, maiuscole messe a caso.
trim() toglie gli spazi esterni, uppercase() e lowercase()
cambiano il caso delle lettere.
s.lengthconta tutti i caratteri, spazi compresi:14.trim()toglie gli spazi davanti e dietro, quindis.trim().lengthè10.uppercase()lavora sulla stringa ripulita e restituisceCIAO MONDO. La variabilesresta però identica: i metodi delle stringhe non modificano l'originale.
Per non appesantire lo studio, considera base (da sapere subito):
length, s[i], trim, uppercase/lowercase,
contains, startsWith, substring, split,
toIntOrNull. Considera extra (da consultare quando serve,
vedi tabella del riepilogo): padStart, padEnd,
"%.2f".format(...), repeat. L'esempio 5 e l'esempio 12 usano
l'extra formattazione: se li trovi difficili, torna al riepilogo dopo aver fatto gli esercizi base.
8.3 Cercare dentro al testo
contains dice se una parte di testo c'è, startsWith e
endsWith controllano inizio e fine, indexOf dice in che posizione si trova
(e restituisce -1 se non c'è).
Che cosa stampa questo programma?
fun main() {
val frase = "gatto"
frase.replace("a", "o")
println(frase)
}gottoAttenzione: replace restituisce una nuova stringa, ma qui il risultato non viene salvato da nessuna parte.gattoEsatto: la stringa originale resta identica. Servirebbe val nuova = frase.replace("a", "o").replace non esistereplace esiste eccome: semplicemente non modifica la stringa su cui lo chiami.Vale per replace, trim, uppercase, substring: le stringhe sono immutabili, quindi il risultato va assegnato a una variabile.
8.4 Tagliare e dividere: substring e split
substring(a, b) prende i caratteri dalla posizione a (compresa) alla
b (esclusa). split(separatore) spezzetta una stringa in una
lista di pezzi.
Che cosa stampa "programmare".substring(0, 7)?
programm (8 caratteri)Attenzione: il secondo numero è escluso, quindi la parte presa va dall'indice 0 all'indice 6.program (7 caratteri)Esatto: substring(a, b) prende da a compreso a b escluso.rogrammaQuesto è il risultato di substring(1, 8): il primo numero è l'inizio, non il conteggio.Con substring vale la stessa regola degli indici: si conta da 0 e la fine è esclusa. Per gli ultimi caratteri si usa substring(n).
split(" ")taglia la frase su ogni spazio: si ottiene una lista di tre parole.joinToString("-")fa il lavoro inverso: rimette insieme gli elementi della lista separandoli con un trattino.- Da qui le due righe:
3eIl-gatto-dorme.
split spezza un testo in una lista; joinToString(", ") fa l'operazione inversa,
unendo una lista in un unico testo. Nel prossimo esempio li usi insieme.
8.5 Conversioni e confronti
Per confrontare due testi si usa == (in Kotlin confronta il contenuto, non l'indirizzo di memoria).
Le conversioni fra testo e numeri si fanno con toInt(), toDouble(),
toIntOrNull() e toString().
Quando devi comporre un testo lungo (per esempio le iniziali di tutte le parole) usa una
var vuota e il +=: var sigla = "" e poi
sigla += parola[0] dentro al ciclo.
8.6 Riepilogo
Le righe base sono quelle da sapere a memoria per gli esercizi.
Le righe extra (padStart, padEnd, repeat,
"%.2f".format) servono per i report ordinati del
capitolo 11: consultale quando ti servono, senza memorizzarle ora.
Con tanti metodi a disposizione è facile perdersi: non partire dal nome, parti dall'intento. Queste sono le quattro domande più comuni e la famiglia di metodi che risponde.
- Il testo è sporco? (spazi ai bordi, maiuscole a caso) →
trim(),trimStart(),trimEnd(), poiuppercase()olowercase(). - Devo sapere se contiene qualcosa, come inizia o come finisce? →
contains(),startsWith(),endsWith(),isEmpty(),isBlank(). - Devo sapere dove si trova una parte di testo? →
indexOf(),lastIndexOf()(e-1significa "non c'è"). - Devo prendere un pezzo o cambiarlo? → per prenderlo
substring(),split(); per cambiarloreplace(),replaceFirst().
| Metodo o proprietà | Cosa fa |
|---|---|
s.length (base) | numero di caratteri |
s[i], s.get(i) (base) | il carattere in posizione i (tipo Char) |
s.first(), s.last() (base) | primo e ultimo carattere |
uppercase(), lowercase() (base) | tutto maiuscolo / minuscolo |
trim(), trimStart(), trimEnd() (base) | toglie gli spazi esterni |
isEmpty(), isNotEmpty(), isBlank() (base) | stringa vuota / non vuota / solo spazi |
contains(x) (base) | contiene la parte di testo x |
startsWith(x), endsWith(x) (base) | inizia / finisce con x |
indexOf(x), lastIndexOf(x) (base) | posizione di x (oppure -1) |
replace(a, b), replaceFirst(a, b) (base) | sostituisce parti di testo |
substring(a), substring(a, b) (base) | taglia da a (incluso) a b (escluso) |
split(sep) (base) | divide in una lista di pezzi |
reversed() (base) | la stringa al contrario |
repeat(n), padStart(n, c), padEnd(n, c) (extra) | ripete / completa a sinistra / a destra |
toInt(), toDouble(), toIntOrNull(), toDoubleOrNull() (base) | conversioni in numero |
"%.2f".format(x) (extra) | formatta un numero con due decimali |
forEach { } (base) | esegue qualcosa per ogni carattere |
s.replace("a", "b") restituisce una nuova stringa: quella originale resta
identica. Se vuoi conservare il risultato devi assegnarlo:
var s = s.replace("a", "b") oppure val nuovo = s.replace(...).
Lo stesso vale per trim, uppercase, substring.
Qui nel manuale "%.2f".format(2.5) stampa sempre 2.50, con il punto.
In un progetto IntelliJ il risultato può dipendere dalla lingua impostata sul computer: su un
sistema in italiano la stessa istruzione può stampare 2,50, con la virgola.
È lo stesso tema del "il punto, non la virgola" del capitolo 2 e 3 — ma al contrario: qui è
il computer a scegliere il separatore quando formatta per farlo leggere a chi usa il
programma. Nella console del manuale la scelta è fissa al punto, così non ci sono sorprese
mentre impari; quando incontrerai la virgola in IntelliJ, sappi che non è un errore.
8.7 Esercizi guidati
Leggi una parola, puliscila con trim().lowercase() e stabilisci se è palindroma,
cioè se si legge uguale da sinistra a destra e da destra a sinistra (per esempio "ingegni", "anna", "osso").
Il metodo più corto è if (parola == parola.reversed()).
Se preferisci farlo a mano, confronta parola[i] con parola[parola.length - 1 - i]
dentro un ciclo.
trim().lowercase()normalizza l'input: così "Anna " e "anna" vengono considerate uguali.reversed()restituisce una nuova stringa con i caratteri al contrario: non modificaparola.- Il confronto con
==in Kotlin confronta il contenuto delle due stringhe, quindi funziona come ci si aspetta. - Nel ramo
elsestampiamo anche la versione invertita: è utile per capire a colpo d'occhio cosa non tornava.
Leggi una frase e stampa: quante parole contiene, la parola più lunga, quante parole iniziano con la lettera "i" e la frase con le iniziali maiuscole di ogni parola.
Per la parola più lunga parti dalla prima parola come riferimento e aggiorna se ne trovi una
con length maggiore. Per la frase con le iniziali maiuscole usa una
var titolo = "" e aggiungi parola.substring(0, 1).uppercase() + " " a ogni giro.
split(" ")trasforma la frase in una lista di parole: da lì in poi si lavora con cicli e metodi delle liste.- Per il confronto sull'iniziale si usa
lowercase()prima distartsWith("i"): così funzionano anche "Impariamo" e "I". titoloè unavarche cresce con+=: prima lettera maiuscola, resto minuscolo, spazio in fondo.- Alla fine
titolo.trim()toglie lo spazio finale che l'ultima parola ha lasciato.
Leggi nome e cognome separati da uno spazio (es. "mario rossi") e l'anno di nascita.
Costruisci un codice tessera con questo formato:
3 lettere del cognome in maiuscolo + iniziale del nome +
ultime due cifre dell'anno, per esempio ROSSM08.
Attenzione al caso limite: se il cognome ha meno di 3 lettere (es. "Li", "Fo"),
completa con X fino a 3 caratteri (es. "Li" diventa LIX).
Il cognome è parti[1], il nome parti[0]. Le ultime due cifre dell'anno
sono anno.substring(anno.length - 2). Per il cognome corto usa
if (cognome.length >= 3) cognome.substring(0, 3).uppercase() else cognome.uppercase().padEnd(3, 'X').
split(" ")separa nome e cognome:parti[0]è il nome,parti[1]il cognome.substring(0, 3)prende i primi tre caratteri (indici 0, 1, 2), ma solo se esistono: con "Li" il programma si fermerebbe con un errore di indice.- Il ramo
elseconpadEnd(3, 'X')completa i cognomi corti ("Li" diventa "LIX"): il codice resta sempre di 3 + 1 + 2 = 6 caratteri. substring(anno.length - 2)prende gli ultimi due caratteri: con "2008" si ottiene "08".
I dati arrivano come 2026-05-17 e il programma deve stampare 17/05/2026 e poi il mese in maiuscolo (05). Non funziona: correggi.
I metodi si chiamano con le parentesi tonde, anche quando non hanno argomenti: uppercase(). E nel template si scrive $mese, non mese.toString().
parti[1].uppercasesenza parentesi non chiama il metodo: il programma si ferma perché si aspetta una chiamata.- La forma corretta è
parti[1].uppercase(), che restituisce05: le cifre non hanno maiuscolo. - Per stampare il nome del mese (
MAGGIO) servirebbe una mappa dei mesi;uppercase()cambia solo le lettere che trova. println(mese)stampa già il testo:toString()qui è inutile, perché Kotlin lo applica da solo dentro aprintln.
length e s[i] per lavorare sui singoli caratteritrim() e cambiarne le maiuscolecontains, startsWith, indexOfsubstring e splitjoinToString e formattare i numeri con "%.2f".format(...)replace va assegnato a una variabile