La gestione delle stringhe assume un ruolo fondamentale in qualsiasi linguaggio di programmazione. La manipolazione di esse sta alla base di molti algoritmi e di tutta la programmazione in generale. In alcuni linguaggi le stringhe vengono rappresentate da insiemi di tipi primitivi (array di char) oppure da oggetti che ne gestiscono le operazioni primitive su di esse.
Nel linguaggio java non esiste un tipo primitivo stringa, ma bensi la classe String del package java.lang che si occupa di gestire una stringa in tutti i suoi aspetti. In quest’articolo analizzeremo (con l’ausilio di codice di esempio) i metodi di questa classe cercando di comprendere le operazioni fondamentali come la concatenazione, l’estrazione, il confronto e via dicendo.
La classe String
Una caratteristica fondamentale della classe String è che rappresenta un oggetto immutabile e la classe di conseguenza è definita final quindi non ereditabile. Immutabile significa che ogni metodo di questa classe (che effettua una qualsiasi operazione) ritorna una stringa nuova, cioè l’oggetto non viene modificato, ma ne viene creato uno nuovo con l’operazione che abbiamo effettuato. Questo ci sarà più chiaro negli esempi.
Vediamo adesso come ottenere un’istanza della classe. Ci sono diversi costruttori, ma il modo più semplice è:
String str = "Ciao";
che è un costrutto riservato ai tipi primitivi del linguaggio java, ad eccezione solo delle stringhe che sono oggetti. Possiamo comunque istanziare una stringa a partire da un’array di char:
char [] array = {'c','i','a','o'};
String str = new String(array);
oppure da un’array di byte, dove ogni elemento dell’array corrisponde al carattere in codifica ASCII:
byte [] array = {};
String str = new String(array);
In questo caso è disponibile anche il costruttore dove è possibile specificare il charset (UTF-8, ISO) per interpretare l’array di byte. Naturalmente il charset deve essere disponibile:
byte [] array = {};
String str = new String(array, "UTF-8");
Concatenazione di stringhe
Concatenare due stringhe significa unire diverse sequenze di caratteri in un’unica entità. In java abbiamo due diversi metodi per effettuare questa operazione. Il primo metodo, il più semplice, ci consente di usufruire della concatenazione utilizzando l’operatore +:
String a = "conca";
String b = "tenazione";
String c = a+b;
System.out.println(c);
....
//Output
concatenazione
Oppure possiamo utilizzare il metodo concat della classe String che ritorna una nuova stringa concatenata:
String c = a.concat(b);
La concatenazione può avvenire anche tra diverse tipologie di dati. Ad esempio se vogliamo convertire un intero in una stringa attraverso la concatenazione implicita il meccanismo è molto immediato:
int n = 100;
String str = "Il valore di n :"+n;
System.out.println(str);
...
Ouputut:
Il valore di n : 100;
Questo vale anche per gli altri tipi primitivi come boolean, float, double etc.. Esiste anche il metodo statico valueOf() che riceve in ingresso un tipo primitivo e lo converte in stringa:
boolean b = true;
String strBoolean = String.valueOf(b);
...
int n = 10;
String strInteger = String.valueOf(n);
...
In poche parole esiste un metodo statico valueOf() per ogni tipo primitivo che lo converte in stringa.
Trasformazione di stringhe
Adesso vediamo nel dettaglio come effettuare delle trasformazioni ad una generica stringa. Possiamo effettuare diversi tipi di trasformazioni, come ad esempio trasformare una stringa tutta in minuscolo/maiuscolo, eliminare spazi iniziali e finali oppure solo determinati caratteri etc.. Analiziamo i diversi metodi per ciascuno di queste operazioni.
I metodi toLowerCase() e toUpperCase()
I metodi toLowerCase() e toUpperCase() ritornano rispettivamente una stringa tutta in minuscolo oppure in maiuscolo:
String str = "tEsT";
//Stringa in minuscolo
System.out.println(str.toLowerCase());
Dove l’output sarà:
test
Mentre per il maiuscolo:
//Stringa in maiuscolo
System.out.println(str.toUpperCase());
Output:
TEST
Come accennavo, un oggetto String è immutabile. Di conseguenza ogni chiamata di un metodo che effettua una qualche modifica ritorna una nuova stringa, quindi l’oggetto in questione non viene modificato. Esempio:
String str = "PRova"; str.toLowerCase(); ...
Dopo la chiamata al metodo toLowerCase() la stringa str non sarà in minuscolo. Per applicare la modifica alla stessa stringa bisogna procedere in questo modo:
String str = "PRova"; str = str.toLowerCase();
oppure assegnarla ad un’altra stringa.
Quanto detto èvalido per i tutti i metdodi che abbiamo visto e che vedremo.
Il metodo trim()
Adesso invece analizziamo un’altra caratteristica che ci consente di eliminare gli spazi iniziali e finali di una stringa. Il metodo in questione è trim():
String str = " abc ";
String noSpaces = str.trim();
System.out.println("*"+noSpaces+"*");
...
Output
*abc*
Ho concatenato i due caratteri * per risaltare l’eliminazione degli spazi iniziali e finali.
Sostituzione di sottostringhe
Se invece vogliamo eliminare tutti gli spazi che sono presenti in una stringa dobbiamo optare per un’altro meccanismo molto più generico che ci consente di sostituire una generica sequenza di caratteri presente all’interno di una stringa, con un’altra scelta da noi. Questo meccanismo comprende i seguenti metodi:
replaceAll(String regex, String replacement)
Questo metodo sostituisce tutte le occorrenze dell’ espressione regolare regex con replacement. Senza addentrarci nel discorso dell’espressioni regolari che è molto complesso possiamo tranquillamente inserire una qualsiasi sequenza di caratteri (che già in se rappresenta un’espressione regolare) da sostituire con un’altra. Per il nostro esempio, cioè eliminare tutti gli spazi presenti in una stringa, basta fare in questo modo:
String str = " a b cd e f" String noAllSpace = str.replaceAll(" ","")); System.out.println(noAllSpace); ... Output: abcdef
Quindi abbiamo sostituito lo spazio con una stringa vuota che consiste appunto nell’eliminazione degli spazi. Possiamo comunque utilizzare questo metodo non solo per eliminare caratteri ma anche per sostituire diverse sottosequenze con altre:
String str = "aa/acda/abcd" String noAllSpace = str.replaceAll("/","#")); System.out.println(noAllSpace); ... Output: aa#acda#abcd
replaceFirst(String regex, String replacement)
Come sopra con l’unica differenza che sostituisce solo la prima occorrenza trovata.
replace(char oldChar, char newChar)
Questo metodo sostituisce tutti i caratteri oldChar con newChar
replace(CharSequence oldChar, CharSequence newChar)
Questo metodo è stato introdotto a partire dalla versione 1.5 di java con l’introduzione dell’interfaccia CharSequence di cui la classe String ne è un’implementazione. Tralasciando quest’aspetto in poche parole il metodo è molto simile al precedente solo che possiamo inserire una sequenza di caratteri invece che un singolo carattere che produce lo stesso effetto del metodo replaceAll().
Estrazione di sottostringhe
Adesso vediamo invece come accedere ad una sottosequenza di una stringa.
Il metodo substring()
Il metodo fondamentale per questo scopo è substring() che possiamo utilizzare in diversi modi. Un primo utilizzo può essere quello di estrarre una sottostringa a partire da una data posizione (offset) fino alla fine della stringa:
String str = "Ciao Mondo"; String newS = str.substring(5); System.out.println(newS); ... Mondo ...
L’altro modo consiste invece nell’estrarre una sottostringa a partire dalla posizione ‘start’ fino a ‘end’. Per far ciò, si usa un’altra implementazione del metodo substring() che riceve appunto due parametri interi, l’indice di partenza e quello finale:
String str = "Ciao Mondo"; String newS = str.substring(5,10); System.out.println(newS); ... Mondo ...
Il metodo split()
Esistono altre modalità di estrazioni di sottostringhe. Ad esempio estrarre una serie di sottostringhe che hanno come separatore alcuni caratteri particolari (un punto o una virgola) oppure una espressione regolare. La classe String fornisce il metodo split(String regex) che ritorna un’array di stringhe dove ciascun elemento è una sottostringa divisa dalla ‘regex’ (espressione regolare) che abbiamo inserito:
String str = "a.b.c.d.e.f"; String [] splits = str.split("\\."); for(String s:splits) System.out.println(s); .. Output .. a b c d e f ...
Il metodo split() è stato introdotto a partire dalla versione Java 1.4 che va a sostituire la classe StringTokenizer del package java.util dove l’estrazione di una serie di sottostringhe era molto meno immediata del metodo split().
Confronto di stringhe
Adesso analizziamo i metodi che ci consentono di verificare l’uguaglianza fra due stringhe.
Il metodo è equals della classe Objcet:
String a = "Casa"; String b = "Casa"; boolean c = a.equals(b); System.out.println(c); ... true; ...
Quindi ritorna true se sono uguali o false altrimenti. Questo metodo è ‘sensibile’ alle lettere maiuscole/minuscole detto case-sensitive. Ad esempio in questo caso la stringa ‘Casa’ risulta diversa da ‘casa’. Quindi per effettuare un confronto che non sia case-sensitive bisogna utilizzare il metodo equalsIgnoreCase() che effettua un confronto senza badare alle differenze maiuscolo/minuscolo:
String a = "Casa"; String b = "casa"; boolean c = a.equalsIgnoreCase(b); System.out.println(c); ... true; ...
Altri metodi importanti
Esistono ancora altri metodi usatissimi che non ricadono nelle categorie che abbiamo visto. Ad esempio il metodo charAt(int i) che ritorna il carattere alla posizione i oppure i metodi startsWith(String str) e endWith(String str) rispettivamente ritornano true se la stringa inizia/finisce con la sequenza str.
Esistono ancora tanti altri metodi meno conosciuti. Vi rimando a consultare la documentazione ufficiale della classe String.
Esempi di algoritmi con le Stringhe
Vediamo adesso alcuni algoritmi sulle stringhe che spesso sono presenti in ambito didattico, come ad esempio verificare se una stringa è palindroma o meno.
Una stringa palindroma è una stringa che letta da sinistra verso destra oppure da destra verso sinistra rimane identica. Esempio : anna, ala, radar, afa etc..
Utilizziamo in questo caso il metodo della classe stringa charAt() che ritorna il carattere all’indice che indichiamo noi. Quindi basta iterare la stringa dall’ultimo carattere fino all’inizio e poi confrontarla con la stringa iniziale:
public boolean palindroma(String str) { String rts = ""; for(int i = str.length()-1; i >= 0; i--) { rts += str.charAt(i); } return str.equals(rts); }
Adesso vediamo invece come contare le occorrenze di una stringa all’interno di un testo. Esistono diversi modi per risolvere il problema. Noi utilizzeremo il metodo substring che abbiamo già visto ma in questo caso l’utilizzo è un po più complesso. Vediamo il codice:
public static int contaOccorrenze(String text, String token) { int n = 0; for(int i = 0; i <= text.length()-token.length(); i++) { String str = text.substring(i, i+token.length()); if(str.equals(token)) n++; } return n; }
Abbiamo confrontato tutte le sottostringhe – all’interno di text – di lunghezza token.length() e confrontato con la stringa che cercavamo. Quindi alla fine dell’algoritmo avremo n occorrenze trovate.
Conclusioni
Concludiamo nel dire che una buona dimestichezza con la manipolazione delle stringhe rende il lavoro dello sviluppatore più sintetico ed efficiente. Sicuramente quello che abbiamo visto è una breve panoramica sui metodi più conosciuti della classe String. Ciò può essere una base o un buon punto di partenza per approfondire l’argomento. Invito il lettore a consultare la documentazione ufficiale.