Stringhe
Negli esempi visti fino ad ora, abbiamo sempre lavorato
con variabili numeriche. Le variabili di tipo char permettono
di memorizzare un singolo carattere alla volta, ma è
chiaro che per applicazioni di manipolazione testi è
necessario disporre di uno strumento che permetta di
lavorare su parole o su intere frasi: la Stringa. In
molti linguaggi di programmazione, tra i quali C e Pascal,
si utilizzano come stringhe array di caratteri; nel
linguaggio Java, in coerenza con la sua impostazione
Object Oriented, le stringhe vengono implementate come
oggetti.
L'oggetto
String contiene una sequenza di caratteri di lunghezza
arbitraria che può essere utilizzata per memorizzare
parole, frasi o testi di qualunque dimensione. Il contenuto
di un oggetto String viene deciso al momento della sua
creazione, e non può essere modificato in seguito:
String
s = new String("Questo è un esempio di stringa
di testo");
I
metodi di String permettono di ispezionare il contenuto
della stringa, di estrarne dei frammenti (sottostringhe),
di verificare l'uguaglianza con un'altra stringa ed
altre interessanti operazioni. Nei prossimi paragrafi
analizzeremo a fondo caratteristiche e di questo oggetto
fondamentale.
Le Stringhe e il linguaggio Java: creazione, concatenazione
ed uguaglianza
Le stringhe sono uno strumento così importante
nella programmazione, che i progettisti di Java hanno
deciso di introdurre dei costrutti nel linguaggio per
semplificarne l'uso.
Creazione
In fase di creazione, invece di ricorrere all'operatore
new, possiamo utilizzare direttamente un letterale stringa,
ossia una qualsiasi sequenza di caratteri racchiusa
tra doppi apici, come nel seguente esempio:
String
s = "Ciao"; // è eqivalente a String
s = new String("Ciao");
Ogni
letterale stringa viene considerato automaticamente
come un oggetto String, pertanto è possibile
applicare la chiamata a metodo direttamente su di esso.
La seguente riga di codice:
System.out.println("Ciao".length());
stampa
a schermo la lunghezza della stringa, ossia il risultato
della chiamata del metodo length() sulla stringa "Ciao",
non il suo contenuto.
Concatenazione
Java prevede l'uso di + come operatore di concatenazione
in alternativa al metodo concat(String s). Grazie all'operatore
+, un'istruzione come
String
s = "Ecco ".concat("una").concat"Stringa
".concat("Concatenata");
può
essere sostituita dalla più sintetica ed intuitiva
String
s = "Ecco " + "una " + "Stringa
" + "Concatenata";
L'operatore
di concatenazione è particolarmente utile quando
si devono creare stringhe così lunghe da occupare
più di una riga. Dal momento che i letterali
stringa non possono essere interrotti con un ritorno
a capo, è possibile concatenare più letterali
stringa da una riga ricorrendo all'operatore +:
String
s = "La vispa Teresa, " +
"avea tra l'erbetta " +
"a volo sorpresa " +
"gentil farfalletta";
L'operatore
+ effettua in modo automatico e trasparente la conversione
dei tipi primitivi in stringa:
int
risultato = 12 * a;
System.out.println("Il contenuto della variabile
risultato è: " + risultato);
Operatore di uguaglianza e metodo Equals
Quando si lavora su oggetti o vettori, bisogna prestare
molta attenzione all'uso dell'operatore di uguaglianza
"==" che, a differenza di quanto ci si potrebbe
aspettare, non serve a testare l'uguaglianza di contenuto,
ma solo l'uguaglianza di riferimento. In Figura 1 vediamo
due variabili s1 e s2 che puntano a due distinti oggetti
stringa. Nonostante entrambe le strighe contengano la
parola "Ciao!", il test con l'operatore "=="
restituirà false, dal momento che gli oggetti
puntati dalle due variabili sono due oggetti differenti,
localizzati in due zone di memoria distinte. Per verificare
se due stringhe hanno lo stesso contenuto, come in questo
caso, è necessario ricorrere al metodo equals(String
s).
In
Figura 2, invece, le variabili s1 ed s2 puntano allo
stesso oggetto stringa: in questo caso il test con l'operatore
"==" restituirà true. Abbiamo uno scenario
di questo tipo ogni volta che assegniamo lo stesso oggetto
a più di una variabile:
s1
= "Ciao!";
s2 = s1; // s1 ed s2 puntano allo stesso oggetto stringa
Figura 1 - Le variabili s1 ed s2 puntano due oggetti
stringa con lo stesso contenuto
Figura 2 - Le variabili s1 ed s2 puntano allo stesso
oggetto stringa
Operazioni
fondamentali
L'oggetto String comprende più di 50 metodi;
quelli veramente fondamentali comunque sono appena 6.
Vediamoli qui di seguito:
- String
substring(int beginIndex , int endIndex ): Restituisce
una sottostringa che contiene il testo incluso tra
beginIndex e endIndex-1.
- char
charAt(int i): restituisce l'i-esimo carattere della
stringa.
- int
length(): restituisce la lunghezza della stringa
- int
compareTo(String anotherString): effettua una comparazione
con un'altra stringa, e restituisce 0 se l'argomento
è una stringa uguale, un valore inferiore a
0 se l'argomento è una stringa più avanti
rispetto all'ordine alfabetico, un valore superiore
a 0 in caso contrario. Se si desidera che nella comparazione
non venga considerata la differenza tra maiuscole
e minuscole, si può utilizzare il metodo compareToIgnoreCase(String
s).
- String
concat(String str): Effettua la concatenazione con
la stringa str. Come già visto è equivalente
all'operatore +.
- boolean
equals(Object o): Restituisce true se la stringa risulta
uguale all'oggetto passato come parametro. Se si desidera
che venga ignorato l'ordine tra maiuscole e minuscole,
si può utilizzare il metodo equalsIgnoreCase(String
s).
Si
noti che i metodi concat e substring non modificano
l'oggetto Stringa su cui vengono invocati, ma creano
un nuovo oggetto che ha per valore il risultato dell'operazione.
In Figura 3 vediamo una rappresentazione della memoria
dopo l'esecuzione delle seguenti istruzioni:
String
s1 = "Rosso di sera, bel tempo si spera";
String s2 = s1.substring(9,1);
String s3 = s2.concat("ta");
Figura 3 - Rappresentazione della memoria del computer
dopo l'esecuzione di una serie di operazioni su stringhe
Altri metodi utili
L'oggetto String prevede diversi altri metodi utili.
Ne vediamo solo alcuni. Ecco un gruppo di metodi utili
per l'interrogazione
- boolean
startsWith(String prefix): restituisce true se la
stringa inizia con il prefisso specificato dal parametro.
- boolean
endsWith(String suffix): restituisce true se la stringa
finisce con il suffisso specificato dal parametro.
- int
indexOf(String str): scandisce la stringa dall'inizio
alla fine, e verifica se contiene o meno la stringa
specificata come parametro. Se la trova, restituisce
l'indice del primo carattere della sotto stringa cercata,
altrimenti restituisce -1.
Un
esempio tipico di uso di questi metodi è il controllo
di validità dell'estensione di un file:
import
javax.swing.*;
public
class ProvaEndsWith {
public static void main(String argv[]) {
String file = JOptionPane.showInputDialog(null, "Inserisci
il nome di un file java valido");
if( file.endsWith(".java") )
JOptionPane.showMessageDialog(null,"Il nome del
file è valido");
else
JOptionPane.showMessageDialog(null,"Il nome del
file non termina con .java");
}
}
Un
altro gruppo di metodi fornisce alcune funzionalità
utili in fase di manipolazione:
- String
replace(char oldChar , char newChar): restituisce
una stringa in cui il carattere specificato dal primo
parametro è stato sostituito da quello indicato
dal secondo.
- String
toLowerCase(): restitituisce una stringa in cui tutti
I caratteri sono minuscoli.
- String
toUpperCase():restitituisce una stringa in cui tutti
I caratteri sono maiuscoli.
- String
trim(): restituisce una stringa dalla quale sono stati
rimossi gli eventuali spazi all'inizio e alla fine.
Come
esempio riepilogativo, vediamo un programma che inverte
le frasi:
import
javax.swing.*;
public
class Invertitore {
public static void main(String argv[]) {
String s = JOptionPane.showInputDialog(null,
"Inserisci una frase");
s = s.trim();
String inversa = "";
for( int i = s.length() -1 ;
i >= 0 ; i-- )
inversa = inversa
+ s.charAt(i);
JOptionPane.showMessageDialog(null,inversa);
}
}
Il
programma chiede all'utente di inserire una frase, quindi
la scandisce lettera per lettera dall'ultima alla prima
e le aggiunge alla stringa "inversa", creando
in tal modo una frase speculare rispetto a quella inserita
dall'operatore.
Nota:
in questi e nei prossimi esempi si farà uso di
piccole finestre di dialogo per l'input e l'output dei
dati sullo schermo. La forma base dell'istruzione di
input è:
String
s = JOptionPane.showInputDialog(null, messaggio);
Questa
istruzione mostra all'utente una finestra di dialogo
con il messaggio specificato dal parametro, e restituisce
la stringa inserita dall'operatore. Per creare invece
una finestra di output che visualizzi a schermo un messaggio
si ricorre alla seguente istruzione:
JOptionPane.showMessageDialog(null,messaggio);
Se
si desidera utilizzare queste istruzioni nei propri
programmi, è necessario inserire la direttiva
import
javax.swing.*;
all'inizio
del programma. Una descrizione completa dei controlli
grafici e del loro utilizzo è rimandata all'apposito
capitolo.
Vettori dinamici
Nel Capitolo 2 abbiamo imparato ad usare gli array per
lavorare su insiemi di variabili di lunghezza prefissata.
Il principale difetto degli array è la loro natura
statica: la dimensione viene stabilita al momento della
creazione, e non può cambiare in alcun modo.
Questa limitazione viene superata dai vettori dinamici,
entità software simili agli array, ma senza il
limite di una dimensione prefissata. Le librerie Java
offrono un buon numero di vettori dinamici, simili tra
loro nell'interfaccia di programmazione ma diversi nell'implementazione
interna: il più usato di questi è senza
dubbio Vector.
Uso di Vector
Il Vector è un oggetto concettualmente simile
ad un array, ma a differenza di quest'ultimo prevede
un utilizzo esclusivamente Object Oriented. Esso va
in primo luogo creato allo stesso modo di qualunque
altro oggetto Java:
Vector
v = new Vector();
Successivamente
si può riempire il Vector utilizzando il metodo
add(Object o), che aggiunge in coda alla lista l'oggetto
passato come parametro:
String
nome = "Un nome";
v.add(nome);
A
differenza dell'array, che viene crato in modo da contenere
valori di un certo tipo, il Vector memorizza oggetti
di tipo generico: per questa ragione, al momento del
prelievo, è necessario ricorrere all'operatore
di casting per riconvertire l'elemento al suo tipo di
origine:
String
n = (String)v.get(1);
L'approccio
completamente OO permette di incorporare un certo numero
di funzionalità all'intero di Vector e di renderle
disponibili sotto forma di metodi. Ad esempio, se si
desidera conoscere la posizione di un elemento all'interno
della lista, è sufficiente chiamare l'apposito
metodo di ricerca:
int
position = v.indexOf("Un nome");
Se
invece si desidera creare una copia del vettore, si
può utilizzare il metodo clone():
Vector
nuovoVector = (Vector)v.clone() ;
Nota:
Per usare il Vector all'interno di un programma è
necessario aggiungere in testa al programma la direttiva
import
java.util.*;
Metodi
Fondamentali
- boolean
add(Object o): aggiunge l'elemento in coda alla lista.
- void
add(int index , Object element): aggiunge un elemento
nella posizione specificata; gli elementi successivi
vengono spostati in avanti di uno.
- void
clear(): svuota completamente la lista.
- boolean
isEmpty(): restituisce true se la lista è vuota
- Object
get(int i): restituisce l'i-esimo elemento della lista
- int
indexOf(Object o): restituisce l'indice dell'elemento
passato come parametro, o -1 se l'elemento non è
presente nella lista.
- Object
remove(index i): rimuove l'I-esimo elemento della
lista, e sposta l'indice di tutti gli elementi successivi
in avanti di un valore. Il metodo restituisce l'elemento
appena rimosso.
- Object
set(int i , Object element): mette l'elemento specificato
in I-esima posizione, in sostituzione dell'elemento
preesistente. Il metodo restituisce l'elemento appena
rimosso.
·int size(): restituisce la dimensione della
lista.
Iterator
Per effettuare una determinata operazione su tutti gli
elementi di un vettore dinamico, è possibile
ricorrere ad un ciclo for come nel caso di un array:
for(int
i=0;i<v.size();i++)
System.out.println((String)v.get(i));
Tuttavia,
per semplificare questo tipo di operazione e per aggiungere
un tocco di eleganza, è disponibile una soluzione
OO che prevede il ricorso ad un apposito oggetto denominato
Iterator, che può essere prelevato dal Vector
usando un apposito metodo:
Iterator
i = nomi.iterator();
L'Iterator
è un oggetto molto semplice ed intuitivo che
permette unicamente di scandire la lista dal primo all'ultimo
elemento allo scopo di effettuare una determinata operazione
su ciascuno dei suoi elementi. L'utilizzo canonico di
un Iterator ha la forma:
while(i.hasNext())
// verifica se c'è un ulteriore elemento
System.out.println((String)i.next()); // preleva l'elemento
e lo utilizza
Conversione in array
Il Vector dispone anche di un metodo che permette di
trasferire il suo contenuto in un array:
Object[]
toArray(Object[] a)
L'array
fornito come parametro viene ridimensionato, riempito
con gli elementi del Vector e restituito all'utente.
Dal momento il Vector memorizza gli elementi con il
tipo generico Object, è necessario passare come
parametro un array del tipo giusto ed effettuare il
casting sul valore di ritorno:
String[]
lista = (String[])v.toArray(new String[0]);
Un
esempio riepilogativo
Un piccolo esempio permetterà di illustrare un
uso tipico del Vector. All'utente viene richiesto di
inserire un elenco di nomi attraverso una serie di finestre
di dialogo; non appena l'utente avrà segnalato,
premendo il pulsante annulla, il termine della fase
di inserimento, il vettore verrà scandito con
un Iterator, e i suoi valori verranno stampati a schermo:
import
java.util.*;
import javax.swing.*;
public
class ListaNomi {
public static void main(String argv[]) {
Vector v = new Vector();
while(true) {
String nome = JOptionPane.showInputDialog(null,
"Inserisci
un nome");
if(nome == null
|| nome.equals(""))
break;
else
v.add(nome);
}
Iterator i = v.iterator();
while(i.hasNext()) {
System.out.println((String)i.next());
}
}
}
Mappe
Hash
La mappa Hash è un contenitore di oggetti che
mappa chiavi con valori, senza mantenere informazioni
posizionali. In parole semplici, una mappa Hash è
un contenitore di oggetti in cui gli elementi vengono
associati ad oggetti chiave anziché a valori
numerici. La mappa hash più comunemente utilizzata
è Hashtable:
Hashtable
h = new Hashtable();
Per
depositare elementi nella mappa, bisogna utilizzare
il metodo put(Object chiave,Object valore), che richiede
come chiave un valore univoco (tipicamente una stringa
di testo) e come valore l'oggetto da memorizzare:
h.put("nome","Mario");
h.put("cognome","Rossi");
h.put("età","25 anni");
h.put("lavoro","insegnante");
La
mappa può ovviamente contenere elementi duplicati;
non può invece associare più di un elemento
alla stessa chiave. Per recuperare l'elemento, sarà
sufficiente fornire la chiave attraverso il metodo get:
String
s = (String)h.get("nome");
Anche
in questo caso, in fase di recupero è necessario
ricorrere all'operatore di casting.
Metodi Principali
- Object
put(Object key , Object value): inserisce un nuovo
oggetto nella mappa, associandolo alla chiave specificata.
- Object
get(Object key): preleva dalla mappa l'oggetto associato
con la chiave specificata.
- Object
remove(Object key): rimuove dalla mappa l'oggetto
associato alla chiave specificata.
- void
clear(): svuota la mappa.
- int
size(): restituisce il numero di coppie chiave-valore
contenute nella mappa.
- boolean
isEmpty(): restituisce true se la mappa è vuota.
- boolean
containsKey(Object key): verifica se la mappa contiene
la chiave specificata.
- boolean
containsValue(Object value): verifica se la mappa
contiene il valore specificato.
Estrazione
dell'insieme di chiavi o valori
Per ottenere l'insieme delle chiavi o dei valori, esiste
un'apposita coppia di metodi:
Set
keySet(): restituisce l'insieme delle chiavi.
Collection values(): restituisce la lista dei valori.
Questi
metodi restituiscono oggetti di tipo Set e Collection;
entrambi questi oggetti hanno metodi comuni a Vector,
in particolare il metodo iterator() che rende possibile
la scansione degli elementi:
Iterator
keyIterator = h.keySet().iterator();
System.out.println("L'insieme delle chiavi è:");
while(keyIterator.hasNext())
System.out.println((String)keyIterator.next());
Iterator
elementIterator = h.values().iterator();
System.out.println("L'insieme degli elementi è:");
while(elementIterator.hasNext())
System.out.println((String)elementIterator.next());
Oggetti Involucro
Vector e Hashtable possono memorizzare solamente Oggetti:
se si desidera memorizzare al loro interno valori primitivi
è necessario racchiuderli in appositi oggetti
involucro. Il linguaggio Java prevede un oggetto Involucro
per ogni tipo primitivo: Boolean, Byte, Short, Integer,
Long, Float e Double. Il loro uso è abbastanza
semplice: in fase di creazione è sufficiente
specificare nel costruttore il valore da inglobare:
Integer
i = new Integer(15);
E'
disponibile anche un costruttore che accetta valori
sotto forma di String, una funzionalità estremamente
utile in fase di inserimento dati:
String
numeroStringa = JOptionPane.showInputDialog(null, "Inserisci
un numero intero");
Integer numeroInteger = new Integer(numeroStringa);
int numero = numeroInteger.intValue();
Per
recuperare il valore nel suo formato naturale, ogni
oggetto Involucro dispone di un apposito metodo:
boolean
BooleanValue() su oggetti di tipo Boolean
byte byteValue() su oggetti di tipo Byte
short shortValue() su oggetti di tipo Short
int intValue() su oggetti di tipo Integer
long longValue() su oggetti di tipo Long
float floatValue() su oggetti di tipo Float
double doubleValue() su oggetti di tipo Double
Conclusioni
Questo mese abbiamo imparato ad utilizzare alcuni oggetti
molto comuni nei programmi Java: essi ci hanno permesso
di realizzare alcuni esempi che chiariscono l'uso degli
oggetti all'interno delle nostre applicazioni. Il mese
prossimo impareremo a progettare oggetti partendo da
zero, e a realizzare con essi sofisticate strutture
dati.
|