Stringhe: Immutabilità e Creazione in Java

Abbiamo già introdotto in precedenza la classe String in Java, esaminandone le caratteristiche principali e i metodi più comuni.

Adesso approfondiremo la classe String in Java, esplorando la sua immutabilità, i costruttori disponibili.

Questa lezione rappresenta la prima di una serie di lezioni dedicate alla gestione delle stringhe in Java, che includeranno anche le classi StringBuffer e StringBuilder per la manipolazione delle stringhe mutabili.

Concetti Chiave
  • Le stringhe in Java sono oggetti immutabili.
  • I costruttori della classe String consentono di creare stringhe in vari modi.
  • Una stringa può essere creata utilizzando una sequenza di caratteri, un array di caratteri o un'altra stringa.
  • Una volta creata, una stringa non può essere modificata; ogni modifica crea un nuovo oggetto String.

Stringhe e Immutabilità

Come accade nella maggior parte degli altri linguaggi di programmazione, in Java una stringa è una sequenza di caratteri. Ma, a differenza di alcuni altri linguaggi che implementano le stringhe come array di caratteri, Java implementa le stringhe come oggetti di tipo String.

Implementare le stringhe come oggetti integrati consente a Java di fornire un insieme completo di funzionalità che rendono conveniente la gestione delle stringhe. Ad esempio, Java ha metodi per confrontare due stringhe, cercare una sotto-stringa, concatenare due stringhe e cambiare le lettere da maiuscole a minuscole e viceversa all'interno di una stringa. Inoltre, gli oggetti String possono essere costruiti in diversi modi, rendendo facile ottenere una stringa quando necessario.

In modo alquanto inaspettato, quando creiamo un oggetto String, stiamo creando una stringa che non può essere cambiata. Cioè, una volta che un oggetto String è stato creato, non possiamo cambiare i caratteri che compongono quella stringa: un oggetto String è immutabile.

All'inizio, questo può sembrare una seria restrizione. Tuttavia, non è così. Possiamo ancora eseguire tutti i tipi di operazioni su stringhe. La differenza è che ogni volta che abbiamo bisogno di una versione alterata di una stringa esistente, viene creato un nuovo oggetto String che contiene le modifiche. La stringa originale rimane invariata. Questo approccio viene utilizzato perché le stringhe fisse e immutabili possono essere implementate in modo più efficiente di quelle modificabili.

Per quei casi in cui è desiderata una stringa modificabile, Java fornisce due opzioni: StringBuffer e StringBuilder. Entrambe contengono stringhe che possono essere modificate dopo essere state create. Analizzeremo queste due classi in dettaglio nelle lezioni successive, ma per ora è importante sapere che sono disponibili per l'uso quando necessario.

Le classi String, StringBuffer e StringBuilder sono definite in java.lang, quindi fanno parte della base del linguaggio. Pertanto, sono disponibili automaticamente per tutti i programmi. Tutte sono dichiarate final, il che significa che nessuna di queste classi può diventare super-classe di altre classi. Questo consente certe ottimizzazioni che aumentano le prestazioni nelle operazioni comuni sulle stringhe. Tutte e tre implementano l'interfaccia CharSequence.

Un ultimo punto: dire che le stringhe all'interno degli oggetti di tipo String sono immutabili significa che il contenuto dell'istanza String non può essere cambiato dopo che è stata creata. Tuttavia, una variabile dichiarata come riferimento String può essere cambiata per puntare a qualche altro oggetto String in qualsiasi momento.

I Costruttori di String

La classe String supporta diversi costruttori. Per creare una String vuota, bisogna chiamare il costruttore di default. Per esempio,

String s = new String();

Questa riga di codice creerà un'istanza di String senza caratteri al suo interno.

Capita molto di frequente che si vogliano creare stringhe che hanno valori iniziali. La classe String fornisce una varietà di costruttori per gestire questo. Per creare una String inizializzata da un array di caratteri, bisogna usare il costruttore mostrato di seguito:

String(char[] caratteri)

Ecco un esempio:

char[] caratteri = { 'a', 'b', 'c' };
String s = new String(caratteri);

Questa riga di codice inizializza s con la stringa "abc".

Possiamo anche specificare un sotto-intervallo di un array di caratteri come inizializzatore usando il seguente costruttore:

String(char[] caratteri, int indiceInizio, int numCaratteri)

Qui, indiceInizio specifica l'indice al quale il sotto-intervallo inizia, e numCaratteri specifica il numero di caratteri da usare. Ecco un esempio:

char[] caratteri = { 'a', 'b', 'c', 'd', 'e', 'f' };
String s = new String(caratteri, 2, 3);

Questa riga di codice inizializza s con i caratteri cde.

Possiamo costruire un oggetto String che contiene la stessa sequenza di caratteri di un altro oggetto String usando questo costruttore:

String(String oggettoStr)

Qui, oggettoStr è un oggetto String.

Consideriamo questo esempio:

// Costruire una String a partire da un'altra.
class CreaString {

    public static void main(String[] args) {
        char[] c = {'J', 'a', 'v', 'a'}; 
        String s1 = new String(c);
        String s2 = new String(s1);
        System.out.println(s1);
        System.out.println(s2);
    }

}

L'output di questo programma è il seguente:

Java
Java

Come possiamo vedere, s1 e s2 contengono la stessa stringa.

Anche se il tipo char di Java usa 16 bit per rappresentare il set di caratteri Unicode di base, il formato tipico per le stringhe su Internet usa array di byte a 8 bit costruiti dal set di caratteri ASCII. Poiché le stringhe ASCII a 8 bit sono comuni, la classe String fornisce costruttori che inizializzano una stringa quando viene dato un array di byte. Due forme sono mostrate qui:

String(byte[] bytes)
String(byte[] bytes, int indiceInizio, int numCaratteri)

Qui, bytes specifica l'array di byte. La seconda forma consente di specificare un sotto-intervallo. In ciascuno di questi costruttori, la conversione byte-carattere è fatta usando la codifica caratteri di default della piattaforma. Il seguente programma illustra questi costruttori:

// Costruire stringa da sottoinsieme di array char.
class SottoStringaCost {

    public static void main(String[] args) {
        byte[] ascii = {65, 66, 67, 68, 69, 70 };
        String s1 = new String(ascii);
        System.out.println(s1);
        String s2 = new String(ascii, 2, 3);
        System.out.println(s2);
    }

}

Questo programma genera il seguente output:

ABCDEF
CDE

Sono definite anche versioni estese dei costruttori byte-stringa, nelle quali possiamo specificare la codifica caratteri che determina come i byte sono convertiti in caratteri. Tuttavia, spesso conviene usare la codifica di default fornita dalla piattaforma.

Definizione

La creazione di una Stringa avviene per copia

Quando costruiamo una String da un array di caratteri, i contenuti dell'array vengono copiati nella stringa. Questo significa che la stringa risultante contiene una copia dei caratteri nell'array al momento della creazione della stringa.

I contenuti dell'array sono copiati ogni volta che creiamo un oggetto String da un array. Se modifichiamo i contenuti dell'array dopo aver creato la stringa, la String rimarrà invariata.

Possiamo costruire una String da un StringBuffer usando il costruttore mostrato qui:

String(StringBuffer oggettoStrBuf)

Possiamo costruire una String da un StringBuilder usando questo costruttore:

String(StringBuilder oggettoStrBuild)

Il seguente costruttore supporta il set di caratteri Unicode esteso:

String(int[] puntiCodice, int indiceInizio, int numCaratteri)

Qui, puntiCodice è un array che contiene punti di codice Unicode. La stringa risultante è costruita dall'intervallo che inizia a indiceInizio e si estende per numCaratteri. Ci sono anche costruttori che consentono di specificare un Charset.

In una serie di lezioni successive, esamineremo Unicode e la sua rappresentazione in Java.