1. Immutabilità della stringa: amica o nemica?
In Java la classe String è immutabile (immutable). Significa che, dopo la creazione, una stringa non può essere modificata. Ogni volta che «modifichi» una stringa, per esempio aggiungendo qualcosa con + o concat(), in realtà viene creato un nuovo oggetto e il vecchio finisce nel cestino della storia (e della memoria).
Esempio:
String s = "Hello";
s = s + " world!";
System.out.println(s); // Hello world!
Sembra che la stringa s sia cambiata, ma in realtà è stata creata una nuova stringa "Hello world!" e la vecchia "Hello" rimane in memoria finché il garbage collector non la rimuove. Se queste operazioni sono molte — ad esempio in un ciclo — il programma inizia a rallentare e a usare memoria in eccesso.
Immagina di costruire una torre di mattoncini e, ogni volta che vuoi aggiungerne uno, dover ricostruire l’intera torre da zero. Non molto efficiente, vero? Ecco, allo stesso modo funziona la normale String in Java quando ci sono modifiche frequenti.
2. StringBuilder: il “costruttore” di stringhe veloce
La classe StringBuilder (pacchetto java.lang, non serve importarla) è uno strumento speciale per comporre e modificare stringhe in modo efficiente. È mutabile (mutable): puoi aggiungere, rimuovere, inserire caratteri e sottostringhe senza creare un nuovo oggetto ad ogni operazione.
Analogia:
Se String è una lastra di cemento, StringBuilder è un set LEGO: aggiungi e rimuovi pezzi quanto vuoi senza dover smontare tutto.
Come creare un StringBuilder
StringBuilder sb = new StringBuilder(); // vuoto
StringBuilder sb2 = new StringBuilder("Valore iniziale");
Metodi principali
| Metodo | Descrizione | Esempio d'uso |
|---|---|---|
|
Aggiunge in coda una stringa, un numero, un carattere, ecc. | |
|
Inserisce un valore nella posizione specificata | |
|
Elimina i caratteri da start (incluso) a end (escluso) | |
|
Sostituisce una parte della stringa con altro contenuto | |
|
Inverte la stringa | |
|
Converte in una normale stringa | |
|
Accorcia o estende la stringa alla lunghezza indicata | |
Esempio d'uso
StringBuilder sb = new StringBuilder();
sb.append("Ciao, ");
sb.append("mondo!");
System.out.println(sb); // Ciao, mondo!
sb.insert(6, "Java "); // inseriamo "Java " dopo "Ciao, "
System.out.println(sb); // Ciao, Java mondo!
sb.replace(6, 10, "altro"); // sostituiamo "Java" con "altro"
System.out.println(sb); // Ciao, altro mondo!
sb.reverse();
System.out.println(sb); // !odnom ortla ,oaiC
3. StringBuffer: il fratello maggiore con protezione per il multithreading
Qual è la differenza tra StringBuilder e StringBuffer?
- StringBuilder è veloce, ma non è thread-safe (non sincronizzato).
- StringBuffer è più lento, ma è thread-safe (sincronizzato).
Se la tua applicazione lavora in un solo thread — usa StringBuilder (più veloce). Se più thread possono modificare la stessa stringa — usa StringBuffer.
4. Quando usare StringBuilder invece di String?
Scenari
- Modifiche frequenti della stringa (aggiunta, eliminazione, inserimento) in un ciclo o durante l’assemblaggio di testi grandi.
- Composizione di una stringa da un array/lista (CSV, HTML, report, ecc.).
- Parsing ed elaborazione di testo con molte operazioni sulle stringhe.
Esempio: comporre una stringa da un array
Modo sbagliato (con String e +):
String[] names = {"Ivan", "Pyotr", "Maria"};
String result = "";
for (int i = 0; i < names.length; i++)
{
result += names[i];
if (i < names.length - 1)
{
result += ", ";
}
}
System.out.println(result);
Modo giusto (con StringBuilder):
String[] names = {"Ivan", "Pyotr", "Maria"};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < names.length; i++)
{
sb.append(names[i]);
if (i < names.length - 1)
{
sb.append(", ");
}
}
System.out.println(sb.toString());
Differenza: nel primo caso ad ogni passo viene creata una nuova stringa, nel secondo — estendiamo un solo oggetto StringBuilder.
5. Confronto prestazionale: String vs StringBuilder
// Con String
long t1 = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 10000; i++)
{
s += i + " ";
}
long t2 = System.currentTimeMillis();
System.out.println("String: " + (t2 - t1) + " ms");
// Con StringBuilder
t1 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.append(i).append(" ");
}
s = sb.toString();
t2 = System.currentTimeMillis();
System.out.println("StringBuilder: " + (t2 - t1) + " ms");
Risultato: nella maggior parte dei casi StringBuilder è ordini di grandezza più veloce nelle concatenazioni multiple.
6. Dettagli utili
Si possono usare i metodi di String?
StringBuilder ha i propri metodi. Per ottenere una stringa — chiama toString().
StringBuilder sb = new StringBuilder("Hello");
String s = sb.toString(); // ora s è una stringa normale
Si possono confrontare due StringBuilder con equals?
Attenzione: sb1.equals(sb2) confronta i riferimenti, non il contenuto. Confronta così:
if (sb1.toString().equals(sb2.toString()))
{
// il contenuto coincide
}
Si può passare StringBuilder a System.out.println?
Sì. System.out.println chiamerà toString() automaticamente.
Si possono leggere i caratteri per indice?
Sì, usa charAt(int index), come con una normale stringa.
7. Errori tipici con StringBuilder e StringBuffer
Errore n. 1: confrontare due StringBuilder con equals o con l’operatore ==. Questi controlli confrontano i riferimenti, non il contenuto. Usa toString() e il confronto tra stringhe.
Errore n. 2: dimenticare di chiamare toString() dove è richiesta una String (valore di ritorno, logging, passaggio a un’API).
Errore n. 3: usare StringBuilder per un paio di semplici concatenazioni. Un’espressione come "Hello, " + name è leggibile ed efficiente.
Errore n. 4: concatenare String in un ciclo con +. È inefficiente in termini di tempo e memoria — usa StringBuilder.
Errore n. 5: confondere StringBuffer e StringBuilder senza motivo. Se non c’è modifica concorrente da più thread — scegli StringBuilder.
Errore n. 6: troncare alla cieca il contenuto di StringBuilder con setLength(). Controlla la correttezza della nuova lunghezza: i dati dopo di essa andranno persi.
GO TO FULL VERSION