Fondamenti di C++ - Cay Horstmann
Appendice: Da C++ a Java
Obiettivi didattici
Gli argomenti affrontati in questa appendice sono:
• “Hello, World” in Java;
• commenti di documentazione;
• tipi primitivi;
• istruzioni di controllo del flusso;
• riferimenti agli oggetti;
• passaggio dei parametri;
• pacchetti;
• gestione di base delle eccezioni;
• liste di array e array;
• stringhe;
• lettura dell’input;
• campi e metodi statici;
• stile di programmazione.
L’obiettivo di questa appendice è quello di illustrare gli elementi di Java, o almeno di
offrirne un breve compendio, partendo dal presupposto della conoscenza di un linguaggio di programmazione orientato agli oggetti. In particolare, è preferibile avere una
certa familiarità con i concetti di classe e di oggetto. Se si conosce C++ e si capiscono le
classi, le funzioni membro e i costruttori, sarà più facile fare il passaggio a Java.
2
Appendice
A.1
“Hello, World!” in Java
Le classi sono gli elementi fondanti dei programmi Java. Per iniziare, ecco un esempio
classico ma comune di classe:
Greeter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Greeter
{
public Greeter(String aName)
{
name = aName;
}
public String sayHello()
{
return "Hello, " + name + "!";
}
private String name;
}
Questa classe è costituita da tre elementi:
• Un costruttore Greeter(String aName) che viene utilizzato per costruire gli oggetti nuovi della classe.
• Un metodo sayHello() che può essere applicato agli oggetti della classe (Java utilizza il termine metodo per indicare una funzione definita in una classe).
• Un campo name che è presente in ogni oggetto della classe.
Ogni elemento è marcato come public o private. I dettagli di implementazione, come
il campo (name) sono privati. Le funzioni concepite per l’utente della classe (come il
costruttore e il metodo sayHello) sono private. La classe stessa viene dichiarata come
public; la ragione verrà spiegata nel paragrafo relativo ai pacchetti.
Per costruire un oggetto si utilizza l’operatore new seguito da una chiamata al costruttore.
new Greeter("World")
L’operatore new restituisce l’oggetto costruito; più precisamente, restituisce un riferimento a quell’oggetto, come verrà illustrato nel dettaglio nel paragrafo sui riferimenti
agli oggetti.
Su questo oggetto e possibile invocare un metodo. La chiamata
new Greeter("World").sayHello()
restituisce la stringa "Hello, World!", la concatenazione delle stringhe "Hello, ",
name, e "!".
Solitamente si memorizza il valore restituito dall’operatore new in una variabile di
oggetto
Greeter worldGreeter = new Greeter("World");
Da C++ a Java
3
Si invoca un metodo come
String greeting = worldGreeter.sayHello();
Ora che si è visto come definire una classe, si costruirà un primo programma Java,
ovvero il tradizionale programma Java che visualizza le parole “Hello, World!” sullo
schermo.
Si definirà quindi una seconda classe, GreeterTest, per produrre l’output.
GreeterTest.java
1
2
3
4
5
6
7
8
9
public class GreeterTest
{
public static void main(String[] args))
{
Greeter worldGreeter = new Greeter("World");
String greeting = worldGreeter.sayHello();
System.out.println(greeting);
}
}
Questa classe ha un metodo main, necessario per poter avviare l’applicazione Java. Il
metodo main è statico, ossia non opera su un oggetto. Dopotutto, quando l’applicazione
viene avviata non ci sono ancora oggetti. È compito di questo metodo costruire gli oggetti necessari per eseguire il programma.
Il parametro args del metodo main contiene gli argomenti della riga di comando
(che verranno discussi nel paragrafo sugli array).
Si sono così già viste le prime due istruzioni all’interno del metodo main. Queste
istruzioni costruiscono un oggetto Greeter, lo memorizzano in una variabile di oggetto, invocano il metodo sayHello e catturano il risultato in una variabile di stringa.
L’istruzione finale utilizza il metodo println dell’oggetto System.out per stampare il
messaggio e aggiunge una nuova riga allo stream dell’output standard.
Per costruire ed eseguire il programma, si inserisca la classe Greeter in un file
Greeter.java e la classe GreeterTest in un file GreeterTest.java separato. Le
direttive per la compilazione e l’esecuzione del programma dipendono dal proprio ambiente di sviluppo.
Il Java Software Development Kit (SDK) di Sun Microsystems è un set di programmi
a riga di comando per la compilazione, l’esecuzione e la documentazione dei programmi Java. Versioni di SDK per varie piattaforme sono disponibili all’indirizzo http://
java.sun.com/j2se. Se si decide di utilizzare SDK, si seguano le istruzioni riportate
di seguito.
1. Creare una nuova directory a propria scelta che conterrà i file di programma.
2. Utilizzare un editor di testo a propria scelta per preparare i file Greeter.java e
GreeterTest.java. Collocare i file nella directory appena creata.
3. Aprire una finestra della shell.
4. Utilizzare il comando cd per passare alla directory appena creata.
5. Eseguire il compilatore con il comando
javac GreeterTest.java
4
Appendice
Se il compilatore Java non è nel percorso di ricerca, sarà necessario utilizzare il
percorso completo (per esempio, /usr/local/j2sdk1.4/bin/javac oppure
c:\j2sdk1.4\bin\javac ), invece del solo javac . Si noti che anche il file
Greeter.java viene compilato automaticamente, poiché la classe GreeterTest
richiede la classe Greeter. Se vengono riportati eventuali errori di compilazione,
si prenda nota del file e dei numeri di riga e si apportino le eventuali correzioni.
6. Si considerino ora i file nella directory corrente. Si verifichi che il compilatore
abbia generato due file di classe, Greeter.class e GreeterTest.class.
7. Avviare l’interprete Java con il comando.
java GreeterTest
Verrà visualizzato un messaggio “Hello, World” nella finestra della shell (Figura A.1).
La struttura di questo programma è tipica di un’applicazione Java. Il programma è
costituito da una collezione di classi, delle quali una ha un metodo main. Per eseguire il
programma è necessario lanciare l’interprete Java con il nome della classe il cui metodo
main contiene le istruzioni per l’avvio delle attività del programma.
L’ambiente di sviluppo BlueJ, sviluppato presso la Monash University, consente di
testare le classi senza dover scrivere un nuovo programma per ogni nuovo test. BlueJ
fornisce un ambiente interattivo per costruire oggetti e invocare metodi su di essi. Può
essere prelevato all’indirizzo http://www.bluej.org.
Con BlueJ non è necessaria una classe GreeterTest per testare la classe Greeter.
Si segua invece la procedura qui riportata.
1. Selezionare Project|New dalla barra dei menu; nella finestra di dialogo dei file,
selezionare una directory a propria scelta e digitare il nome della sottodirectory che
dovrà contenere il nome delle proprie classi. BlueJ creerà una sottodirectory con il
nome precedentemente digitato.
2. Fare clic sul pulsante New Class e digitare la classe Greeter.
3. Fare clic sul pulsante Compile per compilare la classe. Fare clic sul pulsante Close.
Figura A.1
Esecuzione del programma “Hello, World!” in una finestra di shell.
Da C++ a Java
5
4. La classe viene rappresentata come icona a forma di rettangolo. Fare clic con il
pulsante destro del mouse sul rettangolo della classe e selezionare new
Greeter(aName) per costruire un nuovo oggetto. Chiamare l’oggetto worldGreeter e fornire il parametro del costruttore "Hello" (comprese le virgolette).
5. L’oggetto apparirà nell’area relativa. Fare clic con il pulsante destro del mouse sul
rettangolo dell’oggetto e selezionare String sayHello( ) per eseguire il metodo
sayHello.
6. Apparirà una finestra di dialogo che visualizzerà il risultato (Figura A.2).
Come si può vedere, BlueJ permette di pensare agli oggetti e alle classi senza doversi
affannare con public static void main.
Figura A.2
A.2
Test di una classe con BlueJ.
Commenti di documentazione
Java ha una forma standard per i commenti di documentazione che descrivono i metodi
e le classi. L’SDK di Java contiene uno strumento chiamato javadoc che genera automaticamente un set di pagine HTML che documentano le classi.
I commenti di documentazione sono delimitati da /** e */. Tanto i documenti di
classe quanto quelli di metodo iniziano con un testo libero. L’utilità javadoc copia la
prima frase di ogni commento in una tabella riepilogativa. È quindi preferibile scrivere
la prima frase con attenzione, assicurandosi che inizi con una maiuscola e termini con
un punto. Non deve essere una frase compiuta dal punto di vista grammaticale, ma deve
avere un senso quando viene estratta dal commento per poter essere visualizzata in un
riepilogo.
6
Appendice
I commenti dei metodi contengono informazioni supplementari. Per ogni parametro di
metodo è necessario fornire una riga che inizia con il tag @param, seguito dal nome di
parametro e da una breve spiegazione. Per descrivere il valore restituito si fornisce una
riga che inizia con @return. Il tag @param viene omesso quando i metodi non hanno
parametri, mentre il tag @return viene omesso per i metodi il cui tipo restituito è void.
Ecco la classe Greeter con i commenti di documentazione per la classe e la sua
interfaccia pubblica.
/**
Una classe per produrre un saluto semplice.
*/
class Greeter
{
/**
Costruisce un oggetto Greeter che può salutare una persona o un’entità.
@param aName il nome della persona o entità a cui deve essere
rivolto il saluto.
*/
public Greeter(String aName)
{
name = aName;
}
/**
Saluta con un messaggio “Hello”.
@return un messaggio che contiene "Hello" e il nome della
persona o entità a cui si rivolge il saluto.
*/
public String sayHello()
{
return "Hello, " + name + "!";
}
private String name;
}
La prima reazione di fronte a questa classe può essere di preoccupazione: è davvero
necessario dover scrivere tutto questo codice? Questi commenti sembrano anche piuttosto ridondanti, ma è necessario prendersi un po’ di tempo per scriverli, anche se può
sembrare sciocco. Ci sono tre motivi per farlo.
Il primo è che l’utility javadoc formatta i commenti in un set definito di documenti
HTML e utilizza spesso frasi apparentemente ripetitive. La prima frase di ogni commento di metodo viene utilizzata per una tabella riepilogativa di tutti i metodi della classe
(Figura A.3). I commenti @param e @return sono formattati chiaramente nella descrizione dettagliata di ciascuno metodo (Figura A.4). Se si omette uno qualsiasi di essi,
javadoc genera documenti che appaiono stranamente vuoti.
In realtà diventa più facile perdere tempo a pensare che un commento è troppo banale per scriverlo che non a scriverlo direttamente. Nella programmazione pratica, i metodi molto semplici sono rari. Avere metodi semplici sovracommentati è meno pericoloso
che non avere un metodo complesso senza commenti, cosa che può comportare grossi
problemi ai programmatori nella manutenzione successiva. In base allo stile di documentazione standard di Java, ogni classe, metodo, parametro e valore restituito dovrebbe avere un commento.
Infine, è sempre una buona idea scrivere prima il commento al codice e solo in seguito il codice del metodo. È un test eccellente per vedere se si hanno le idee chiare sullo
scopo del programma. Se non si riesce a spiegare cosa fanno una classe o un metodo,
non si è pronti per implementarli.
Da C++ a Java
Figura A.3
Una classe riepilogativa di javadoc.
Figura A.4
La documentazione sui parametri e i valori restituiti in javadoc.
Una volta scritti i commenti, si invochi l’utility javadoc.
1. Aprire una finestra della shell.
2. Utilizzare il comando cd per passare alla directory appena creata.
3. Eseguire l’utility javadoc.
7
8
Appendice
javadoc *.java
Se gli strumenti SDK non sono nel percorso di ricerca, sarà necessario utilizzare il
percorso completo (per esempio /usr/local/j2sdk1.4/bin/javadoc oppure
c:\j2sdk1.4\bin\javadoc) invece del solo javadoc.
L’utility javadoc produrrà un file HTML per ogni classe (come Greeter.html e
GreeterTest.html), oltre a un file index.html e ad alcuni altri file di riepilogo. Il file
index.html contiene collegamenti a tutte le classi.
Lo strumento javadoc è eccezionale proprio perché consente di assemblare la documentazione con il codice. In questo modo, quando si aggiorna il programma, si può
vedere immediatamente quale documentazione deve essere aggiornata (sperando che
non sia un’operazione da eseguire troppo spesso). In seguito si potrà eseguire nuovamente javadoc per ottenere un set di pagine HTML ben formattate con i commenti
aggiornati.
INTERNET Il programma DocCheck rileva gli eventuali commenti javadoc
mancanti. Prelevatelo all’indirizzo http://java.sun.com/j2se/javadoc/doccheck/.
L’SDK di Java contiene la documentazione per tutte le classi nella libreria di Java, chiamata anche interfaccia di programmazione dell’applicazione o API. La Figura A.5 mostra la documentazione relativa alla classe String. Questa documentazione viene estratta
direttamente dal codice sorgente della libreria. I programmatori che scrivono la libreria
di Java hanno documentato ogni classe e metodo e hanno quindi eseguito javadoc per
estrarre la documentazione HTML.
Si scarichi la documentazione all’indirizzo http://java.sun.com/j2se e la si installi nello stesso punto dell’SDK di Java. Si punti quindi il browser al file docs/api/
index.html nella propria directory dell’SDK di Java e si crei un segnalibro. È importante farlo appena possibile, perché sarà necessario accedervi di frequente.
Figura A.5
La documentazione API di Java.
Da C++ a Java
A.3
9
Tipi primitivi
In Java, i numeri, i caratteri e i valori booleani non sono oggetti, ma valori di tipo primitivo. La Tabella A.1 mostra otto tipi primitivi del linguaggio Java.
Tabella A.1
I tipi primitivi del linguaggio Java.
Tipo
Dimensioni
Intervallo
int
32 byte
–2147483648 . . . 2147483647
long
64 byte
–9223372036854775808L . . .
9223372036854775807L
16 byte
short
byte
8 byte
char
16 byte
1 byte
boolean
double
64 byte
float
32 byte
–32768 . . . 32767
–128 . . . 127
'\u0000' - '\uFFFF'
false, true
approssimativamente ± 1,79769313486231570E+308
approssimativamente ± 3,40282347E+38F
Si noti che le costanti di tipo long hanno un suffisso L, quelle di tipo float hanno un
suffisso F, come 10000000000L o 3.1415927F.
I caratteri sono codificati in Unicode, uno schema di codifica uniforme per molte
lingue. Le costanti di carattere sono racchiuse tra virgolette semplici, come 'a'. Diversi
caratteri, come quello di nuova riga 'n', sono rappresentati come sequenza di due caratteri escape. La Tabella A.2 mostra tutte le sequenze escape ammesse. I caratteri Unicode
arbitrari sono indicati da una \u seguita da quattro cifre esadecimali racchiuse tra virgolette. Per esempio, '\u2122' è il simbolo di marchio brevettato (TM).
Tabella A.2
Sequenze di caratteri escape.
Sequenza di escape
Significato
\b
cancella il carattere a sinistra (\u0008)
\f
avanzamento di pagina (\u000C)
\n
nuova riga (\u000A)
\r
ritorno a capo (\u000D)
\t
tabulatore (\u0009)
\\
barra retroversa
\'
virgolette semplici
\"
virgolette doppie
\un n n n
1
2
3
4
codifica Unicode
10
Appendice
INTERNET Per trovare le codifiche di decine di migliaia di lettere in numerosi
alfabeti, si visiti il sito http://www.unicode.org.
Le conversioni che non comportano una perdita di informazioni (come da short a int
o da float a double) sono sempre valide. I valori di tipo char possono essere convertiti in int. Tutti i tipi interi possono essere convertiti in float o double, anche se
questo produce una perdita di precisione. Tutte le altre conversioni richiedono un cast,
come in
double x = 10.0 / 3.0; // imposta x a 3.3333333333333335
int n = (int)x; // imposta n a 3
float f = (float)x; // imposta f a 3.3333333
Non è possibile eseguire una conversione tra tipi booleani e tipi numerici.
La classe Math implementa funzioni matematiche utili, elencate nella Tabella A.3. I
metodi di questa classe sono statici, ossia non operano su oggetti (si ricordi che in Java
i numeri non sono oggetti). Per esempio, ecco come viene chiamato il metodo sqrt:
double y = Math.sqrt(x);
Poiché il metodo non opera su un oggetto, è necessario fornire il nome della classe per
dire al compilatore che il metodo sqrt si trova nella classe Math. In Java, tutti i metodi
devono appartenere a una classe.
Tabella A.3
Metodi matematici.
Metodo
Descrizione
Math.sqrt(x)
radice quadrata di x, x
Math.pow(x, y)
xy(x > 0, o x = 0 e y > 0, o x > 0 e y è un intero)
Math.sin(x)
seno di x (x in radianti)
Math.cos(x)
coseno di x
Math.tan(x)
tangente di x
Math.asin(x)
(arco seno) sen–1 x ∈ [– π/2, π/2], x ∈[–1,1]
Math.acos(x)
(arco coseno) arco sen–1 x ∈[0, π], x ∈[–1,1]
Math.atan(x)
(arco tangente) tan–1(x/y) ∈ [– π/2, π/2]
Math.atan2(y,x)
(arco tangente) tan–1 (x/y) ∈[– π/2, π/2], x può essere 0
Math.toRadians(x)
converte x gradi in radianti (per esempio, restituisce x ⋅ p/180)
Math.toDegrees(x)
converte x radianti in gradi (per esempio, restituisce x ⋅ 180/p)
Math.exp(x)
ex
Math.log(x)
(log naturale) ln(x), x > 0
Math.round(x)
l’intero più vicino a x (come long)
Math.ceil(x)
l’intero più piccolo ≥ x
Math.floor(x)
l’intero più grande ≤ x
Math.abs(x)
valore assoluto x
Da C++ a Java
A.4
11
Istruzioni di controllo del flusso
L’istruzione if viene utilizzata per l’esecuzione condizionale. La condizione else è
facoltativa.
if (x >= 0) y = Math.sqrt(x); else y = 0;
Le istruzioni while e do vengono utilizzate per i cicli. Il corpo di un ciclo do viene
eseguito almeno una volta.
while (x
{
x = x
n++;
}
do
{
x = x
n++;
}
while (x
< target)
* a;
* a;
< target);
L’istruzione for viene utilizzata per i cicli che sono controllati da un contatore di cicli.
for (i = 1; i <= n; i++)
{
x = x * a;
sum = sum + x;
}
È possibile definire una variabile in un ciclo for. Il suo ambito si estende fino alla fine
del ciclo.
for (int i = 1; = 1; i <= n; i++)
{
x = x * a;
sum = sum + x;
}
// i non più definita qui
A.5
Riferimenti agli oggetti
In Java, un valore di oggetto è sempre un riferimento a un oggetto o, in altre parole, un
valore che descrive la posizione dell’oggetto. Per esempio, si consideri l’istruzione
Greeter worldGreeter = new Greeter("World");
Il valore dell’espressione new è la posizione dell’oggetto appena costruito. La variabile
worldGreeter può contenere la posizione di qualsiasi oggetto Greeter e viene riempita con la posizione del nuovo oggetto (Figura A.6.).
È possibile avere più riferimenti allo stesso oggetto. Per esempio, dopo l’istruzione
Greeter anotherGreeter = worldGreeter;
le due variabili di oggetto condividono entrambe un singolo oggetto (Figura A.7).
12
Appendice
Figura A.6
Un riferimento a un oggetto.
Figura A.7
Un oggetto condiviso.
Se la classe Greeter ha un metodo che consente la modifica dell’oggetto (come un
metodo setName), e se quel metodo viene invocato dal riferimento all’oggetto, allora
anche tutti i riferimenti condivisi accederanno all’oggetto modificato.
anotherGreeter.setName("Dave");
//anche now worldGreeter si riferisce all’oggetto modificato
Il riferimento speciale null non fa riferimento ad alcun oggetto. È possibile impostare
una variabile di oggetto su null:
worldGreeter = null;
È possibile verificare se un riferimento a un oggetto è correntemente null:
if (worldGreeter == null) ...
Se si invoca un metodo o un riferimento null, viene rilevata un’eccezione. A meno che
non si fornisca un gestore per l’eccezione, il programma termina.
Può accadere che un oggetto non abbia alcun riferimento che punta a esso, per esempio quando tutte le variabili di oggetto a cui si fa riferimento vengono riempite con altri
valori o sono state riciclate. In questi casi la memoria dell’oggetto viene recuperata
automaticamente dal garbage collector. In Java non è mai necessario riciclare manualmente la memoria.
A.6
Passaggio dei parametri
In Java, un metodo può modificare lo stato di un parametro di oggetto perché la variabile di parametro corrispondente è impostata a una copia del riferimento di oggetto passato. Si consideri questo metodo di esempio della classe Greeter:
/**
Imposta un nome diverso per il salutante.
Da C++ a Java
13
@param other un riferimento all’altro Greeter
*/
public void setName(Greeter other) {
other.name = name;
}
Si consideri ora questa chiamata:
Greeter worldGreeter = new Greeter("World");
Greeter daveGreeter = new Greeter("Dave");
worldGreeter.setName(daveGreeter);
La Figura A.8 mostra come l’altra variabile di parametro venga inizializzata con una
copia del riferimento daveGreeter. Il metodo setName cambia other.name, e dopo la
restituzione del metodo, daveGreeter.name è stato cambiato.
Figura A.8
Accedere a un oggetto attraverso una variabile di parametro.
Sebbene un metodo possa modificare lo stato di un oggetto che viene passato come
parametro, non può mai aggiornare il contenuto di nessuna variabile. Se si chiama
obj.f(var);
dove var è una variabile, allora il contenuto di var sarà lo stesso numero, valore di tipo
Boolean o posizione di oggetto prima o dopo la chiamata. In altre parole, è impossibile
scrivere un metodo che imposta il valore corrente di var con un altro valore di tipo
primitivo o posizione di oggetto.
Si consideri un altro insieme di metodi di esempio:
/**
Prova a copiare la lunghezza del nome del salutante in una variabile di tipo integer.
@param n la variabile in cui il metodo prova a copiare la lunghezza
*/
public void setLength(int n) {
// questa assegnazione non ha alcun effetto all’esterno del metodo
n = name.length();
}
/**
Prova a impostare un altro oggetto Greeter a una copia di questo oggetto.
@param diverso dall’oggetto Greeter da inizializzare
*/
public void setGreeter(Greeter other)
{
// questa assegnazione non ha alcun effetto all’esterno del metodo
other = new Greeter(name);
}
14
Appendice
Si chiamino questi due metodi:
int length = 0;
Greeter worldGreeter = new Greeter("World");
Greeter daveGreeter = new Greeter("Dave");
worldGreeter.setLength(length); // non ha effetti sul contenuto di length
worldGreeter.setGreeter(daveGreeter);
// non ha effetti sul contenuto di daveGreeter
Non ci sono chiamate che hanno effetti. La modifica del valore della variabile di parametro non influisce sulla variabile fornita nella chiamata del metodo. Java non prevede
quindi chiamate per riferimento; sia i tipi primitivi sia i riferimenti agli oggetti vengono
passati per valore.
A.7
Pacchetti
Le classi di Java possono essere raggruppate in pacchetti. I nomi dei pacchetti sono
sequenze di identificatori separati con un punto, come in
java.util
javax.swing
com.sun.misc
edu.sjsu.cs.cs151.alice
Per assicurare univocità ai nomi dei pacchetti, Sun consiglia di iniziare un nome di
pacchetto con un nome di dominio (per esempio, com.sun o edu.sjsu.cs), poiché i
nomi di dominio sono sempre univoci. Si segua quindi un ulteriore criterio all’interno
della propria organizzazione per assicurarsi che anche il riferimento al nome di pacchetto sia univoco.
Per collocare una classe all’interno di un pacchetto si deve aggiungere un’istruzione
di pacchetto all’inizio del file sorgente:
package edu.sjsu.cs.cs151.alice;
public class Greeter
{
...
}
Qualsiasi classe senza un’istruzione di pacchetto si troverà senza nome nel pacchetto
predefinito.
Il nome completo di una classe è costituito dal nome del pacchetto seguito da quello
della classe, come in edu.cs.cs151.alice.Greeter. Esempi tratti dalla libreria di
Java sono java.util.ArrayList e javax.swing.JOptionPane.
Poiché è abbastanza noioso utilizzare questi nomi completi nel proprio codice, si
possono utilizzare le istruzioni di importazione per utilizzare i nomi di classe. Per esempio, dopo aver collocato un’istruzione
import java.util.ArrayList;
nel proprio file sorgente, è possibile fare riferimento alla classe semplicemente come
ArrayList. Se si utilizzano contemporaneamente due classi che hanno lo stesso nome
breve (come java.util.Date e java.sql.Date), allora si sarà costretti a utilizzare i
nomi completi.
Da C++ a Java
15
È anche possibile importare tutte le classi da un pacchetto:
import java.util.*;
Non è tuttavia necessario importare le classi nel pacchetto java.lang, come String o
Math.
I programmi più grandi sono costituiti da più classi in più pacchetti. I file della classe
devono essere collocati in sottodirectory che coincidono con il nome del pacchetto. Per
esempio, il file di classe Greeter.class per la classe
edu.sjsu.cs.cs151.alice.Greeter
deve trovarsi nella sottodirectory
edu/sjsu/cs/cs151/alice
della directory di base del progetto. Questa directory è quella che contiene tutte le directory di pacchetto e le classi che sono contenute nel pacchetto predefinito, ossia il
pacchetto senza nome (Figura A.9).
Directory di base
Figura A.9
Il nome del pacchetto deve coincidere con il percorso della directory.
La compilazione deve avvenire sempre dalla directory di base; per esempio
javac edu/sjsu/cs/cs151/alice/Greeter.java
oppure
javac edu\sjsu\cs\cs151\alice\Greeter.java
Il file della classe viene collocato automaticamente nella posizione corretta.
Per eseguire un programma si deve avviare l’interprete Java nella directory di base e
specificare il nome completo della classe che contiene il metodo main:
java edu.sjsu.cs.cs151.alice.Greeter.java
A.8
Gestione di base delle eccezioni
Quando un programma esegue un’azione non valida, viene generata un’eccezione. Ecco
un caso comune: si supponga di inizializzare una variabile con il riferimento null e che
s’intenda assegnare un effettivo riferimento di oggetto successivamente. Per errore, si
utilizza però la variabile quando è ancora null:
16
Appendice
String name = null;
int n = name.toUpperCase(); //ERRORE
Questo è un errore. Non è possibile applicare una chiamata a un metodo su null. Il
computer virtuale rileva un’eccezione NullPointerException. A meno che il proprio
programma non gestisca questa eccezione, il programma verrà terminato dopo aver visualizzato una traccia di stack come questa:
Exception in thread "main" java.lang.NullPointerException
at Greeter.sayHello(Greeter.java:25)
at GreeterTest.main(GreeterTest.java:6)
Diversi errori di programmazione generano eccezioni differenti. Per esempio, provare
ad aprire un file con un nome di file non valido comporta un’eccezione IOException.
Il linguaggio di programmazione di Java fa una distinzione importante tra due tipi di
eccezioni, chiamate rispetticamente non controllata e controllata. L’eccezione NullPointerException è di tipo controllato. In questi casi il compilatore non controlla se
il codice gestisce l’eccezione. Se si verifica un’eccezione, questa viene individuata durante il runtime e può terminare il programma. L’eccezione IOException è invece un’eccezione non controllata. Se si chiama un metodo che può generare un’eccezione, è necessario specificare anche come si vuole che il programma si comporti nell’evenienza.
In linea generale, un’eccezione controllata ha una causa esterna che va al di là del
controllo del programmatore. Le eccezioni che si verificano durante l’input e l’output
sono solitamente controllate, perché il file system o la rete possono provocare spontaneamente problemi che il programmatore non può controllare. Il compilatore insiste quindi
affinché il programmatore fornisca il codice per gestire queste eventualità.
Le eccezioni non controllate sono invece di responsabilità del programmatore. Non
si dovrebbe mai verificare un’eccezione NullPointerException. In questi casi il compilatore non chiede di fornire un gestore per l’eccezione; è quindi indispensabile sforzarsi per evitare che l’errore si verifichi. Si consiglia di inizializzare le variabili nel
modo appropriato o di verificare che non siano null prima di eseguire una chiamata a
un metodo.
Ogni volta che si scrive un codice che può provocare un’eccezione non controllata,
si può procedere in due modi:
1. Dichiarare l’eccezione nell’intestazione del metodo (soluzione consigliata).
2. Catturare l’eccezione.
Si consideri il prossimo esempio. Si vogliono leggere alcuni dati da un file.
public void read(String filename)
{
FileReader r = new FileReader(filename);
...
}
Se non ci sono file con il nome dato, il costruttore FileReader rileva un’eccezione
IOException. Poiché si tratta di un’eccezione controllata, il compilatore insiste perché
venga gestita. Il rimedio ideale è quello di consentire la propagazione dell’eccezione
fino al suo chiamante. Questo significa che il metodo read termina e che l’eccezione
viene rilevata nel metodo che l’ha chiamata.
Da C++ a Java
17
Quando un metodo propaga un’eccezione controllata, si deve dichiarare l’eccezione
nell’intestazione del metodo, come in questo caso:
public void read(String filename) throws IOException
}
FileReader r = new FileReader(filename)
...
}
Non c’è da vergognarsi nel riconoscere che il metodo creato genera un’eccezione: errare è umano.
Se un metodo può rilevare più eccezioni, queste devono essere elencate separate da
virgole:
public void read(String filename)
throws IOException, ClassNotFoundException
Chiunque chiama questo metodo è messo sull’avviso che nella clausola throws può
verificarsi un’eccezione controllata. Ovviamente questi metodi di chiamata devono anche saper gestire le eccezioni. In linea generale, i metodi di chiamata aggiungono anche
dichiarazioni throws. Quando si esegue questo processo per l’intero programma, anche
il metodo main finisce per essere indicato con tag:
public static void main(String[] args)
throws IOException, ClassNotFoundException
{
...
}
Se un’eccezione si verifica davvero, il metodo main viene terminato, viene visualizzata
una traccia di stack e il programma viene chiuso.
Tuttavia, se si scrive un programma professionale, non si vuole certo che il programma termini ogni volta che un utente immette un nome di file non valido. In questo caso
si vuole generare l’eccezione. Si utilizzi la seguente sintassi:
try
{
...
codice che può rilevare un’eccezione IOException
...
}
catch (IOException exception)
{
esegue l’azione correttiva
}
Un’azione correttiva appropriata potrebbe essere quella di visualizzare un messaggio di
errore e di informare l’utente che il tentativo di leggere il file è fallito.
Nella maggior parte dei programmi, i metodi a livello più basso propagano semplicemente le eccezioni ai loro chiamanti. Qualche metodo di livello alto, come main o
parte dell’interfaccia utente, cattura le eccezioni e informa l’utente.
Può accadere che occorra catturare un’eccezione in un punto dove non è possibile
intraprendere un’azione correttiva.
18
Appendice
Per ora, si visualizzi la traccia dello stack e si esca dal programma. Per esempio,
try
{
...
codice che può rilevare un’eccezione IOException
...
}
catch (IOException exception)
{
// non può eseguire l’azione correttiva
exception.printStackTrace();
System.exit(0);
}
Per fare un lavoro migliore, è necessario sapere qualcosa di più sulla gestione delle
eccezioni. Si consulti in merito il Capitolo 5.
A.9
Liste di array e array
La classe ArrayList del pacchetto java.util consente di raccogliere una sequenza di
oggetti di qualsiasi tipo. Il metodo add aggiunge un oggetto alla fine della lista degli
array.
ArrayList countries = new ArrayList();
countries.add("Belgium");
countries.add("Italy");
countries.add("Thailand");
Il metodo size restituisce il numero di oggetti nella lista di array. Il metodo get restituisce l’oggetto in una posizione data; le posizioni valide vanno da 0 a size() – 1. Il tipo
restituito del metodo get è però Object, la superclasse comune di tutte le classi di Java.
È quindi necessario ricordare il tipo di oggetti che si inseriscono in una lista di array
particolare ed eseguire il cast sul valore restituito su quel tipo. Per esempio,
for (int i = 0; i < countries.size(); i++)
{
String country = (String)countries.get(i);
...
}
Il metodo set consente di sovrascrivere un elemento esistente con un altro.
countries.set(1, "France");
Se si accede a una posizione non esistente (< 0 or >= size()), allora viene rilevata
un’eccezione IndexOutOfBoundsException.
Infine è possibile inserire e rimuove oggetti nel mezzo della lista di array.
countries.insert(1, "Germany");
countries.remove(0);
Da C++ a Java
19
Queste operazioni spostano gli elementi rimanenti in alto o in basso. Il termine lista di
array indica che l’interfaccia pubblica consente sia operazioni di array (get/set) sia di
lista (insert/remove).
Le liste di array hanno uno svantaggio: possono contenere solamente oggetti e non
tipi di valori primitivi. Gli array invece possono contenere sequenze di valori arbitrari.
Un array viene costruito come
new T[n]
dove T è qualsiasi tipo e qualsiasi espressione n valutata come intero. L’array restituito
è di tipo T[]. Per esempio,
int[] numbers == new int[10];
Ora numbers è un riferimento a un array di 10 interi (Figura A.10). Quando un array
viene costruito, i suoi elementi sono impostati come 0, false o null.
Figura A.10 Un riferimento di array.
La lunghezza di un array viene memorizzata nel campo length.
int length = numbers.length
Si noti che un array vuoto di lunghezza 0
new int[0]
è diverso da null, ossia da un riferimento a nessun array.
Per accedere a un elemento di array, si deve racchiudere l’indice tra parentesi quadre, come in
int number = numbers[i]
Se si accede a una posizione non esistente(< 0 o >= length), viene generata un’eccezione ArrayIndexOutOfBoundsException.
Dopo che un array è stato costruito, non è possibile modificarne la lunghezza. Se si
desidera un array più grande, è necessario costruirne uno nuovo e spostare gli elementi
dal vecchio array al nuovo. È un compito molto noioso, e la maggior parte dei programmatori preferisce utilizzare un ArrayList per memorizzare gli oggetti.
20
Appendice
È possibile ottenere un array a due dimensioni con una chiamata a un costruttore, come
int[][] table = new int[10][20];
Si accede agli elementi come table[row][column].
Il parametro args del metodo main è un array di stringhe, in particolare di quelle
specificate nella riga di comando. args[0] è la prima stringa dopo il nome di classe.
Per esempio, se si invoca un programma come
java GreeterTest Mars
args.length sarà 1 e args[0] sarà "Mars" e non "java" o "GreetingTest".
A.10 Stringhe
Le stringhe di Java sono sequenze di caratteri Unicode. Il metodo charAt definisce i
caratteri di una stringa. Le posizioni delle stringhe iniziano dallo 0.
String greeting = "Hello";
char ch = greeting.charAt(1); // imposta ch su 'e'
Le stringhe di Java sono immutabili e una volta create, non possono essere modificate.
Non c’è quindi alcun metodo setCharAt. Può sembrare una restrizione severa, ma in
realtà non lo è. Per esempio, si immagini di aver inizializzato il saluto come "Hello". È
sempre possibile cambiare idea:
greeting = "Goodbye";
L’oggetto stringa "Hello" non è stato modificato, ma ora il saluto fa riferimento a un
oggetto stringa diverso. Il metodo length fornisce la lunghezza di una stringa. Per
esempio, "Hello".length() è 5. Si noti che la stringa "" vuota di lunghezza 0 è diversa da un riferimento null, ossia un riferimento a nessuna stringa.
Il metodo substring calcola le sottostringhe di una stringa. È necessario specificare le posizione del primo carattere che si vuole includere nella sottostringa e del primo
carattere che non si vuole includere. Per esempio, "Hello".substring(1, 3) è la
stringa "el" (Figura A.11). Si noti che la differenza tra le due posizioni è uguale alla
lunghezza della sottostringa.
Figura A.11 Estrazione di una sottostringa.
Poiché le stringhe sono oggetti, è necessario utilizzare il metodo equals per confrontare due stringhe che hanno lo stesso contenuto.
Da C++ a Java
21
if (greeting.equals("Hello")) ... // OK
Se si utilizza l’operatore ==, il test viene seguito solo se due riferimenti di stringa hanno
la stessa identica posizione. Per esempio, il test seguente fallisce:
if ("Hello".substring(1, 3) == "el") ... // NO
La sottostringa non è nella stessa posizione della stringa di costante "el", anche se ha lo
stesso contenuto.
Se le sottostringhe di una stringa sono separate da un delimitatore come una virgola
o uno spazio bianco, è possibile utilizzare la classe StringTokenizer del pacchetto
java.util per enumerare le sottostringhe. Per esempio,
String countries = "Germany,France,Italy";
StringTokenizer tokenizer = new StringTokenizer(countries, ",");
while (tokenizer.hasMoreTokens())
{
String country = tokenizer.nextToken();
...
}
Se non si fornisce un delimitatore nel costruttore, i token verranno delimitati con spazi
bianchi.
È già stato esaminato l’operatore di concatenamento delle stringhe: "Hello, " +
name è il concatenamento della stringa "Hello" e l’oggetto di stringa a cui name fa
riferimento.
Se uno degli argomenti dell’operatore + è una stringa, allora l’altro viene convertito
in una stringa. Per esempio,
int n = 7;
String greeting = "Hello, " + n;
costruisce la stringa "Hello, 7".
Se una stringa e un oggetto sono concatenati, allora l’oggetto viene convertito dal
metodo toString. Ogni classe eredita un’implementazione predefinita del metodo
toString dalla superclasse Object ed è libera di sovrascriverlo per restituire una stringa più appropriata. Per esempio, il metodo toString della classe Date nel pacchetto
java.util restituisce una stringa che contiene la data e l’ora incapsulate nell’oggetto
Date. Ecco cosa accade quando si concatena una stringa e un oggetto Date:
// il costruttore predefinito Date imposta la data e l’ora correnti
Date now = new Date();
String greeting = "Hello, " + now;
// greeting è una stringa come "Hello, Wed Jan 17 16:57:18 PST 2001"
Può accadere di avere una stringa che contiene un numero, per esempio la stringa "7".
Per convertire la stringa nel suo valore di numero si utilizzano i metodi Integer.parseInt e Double.parseDouble. Per esempio,
String input = "7";
n = Integer.parseInt(input); // imposta n su 7
Se la stringa non contiene un numero o se contiene altri caratteri oltre a un numero,
viene rilevata un’eccezione NumberFormatException.
22
Appendice
A.11 Lettura dell’input
Il modo più semplice per leggere l’input in un programma Java è utilizzare il metodo
statico showInputDialog nella classe JoptionPane, mediante il quale si fornisce una
stringa di prompt come parametro. Il metodo visualizza una finestra di dialogo (Figura
A.12) e restituisce la stringa fornita dall’utente, oppure null se l’utente ha chiuso la
finestra. Per leggere un numero è necessario convertire la stringa in un numero con
Integer.parseInt or Double.parseDouble. Per esempio,
String input = JOptionPane.showInputDialog("How old are you?");
if (input != null) age = Integer.parseInt(input);
Figura A.12 Una finestra di dialogo di input.
In effetti è un po’ strano avere finestre di dialogo che appaiono a ogni input. Non è
nemmeno possibile ridirigere l’input per fornire l’input da un file di testo. Per leggere
dall’input standard occorre un lavoro supplementare.
L’input da console legge dall’oggetto System.in . Tuttavia, a differenza di
System.out, che era concepito appositamente per visualizzare numeri e stringhe,
System.in può solo leggere byte. L’input da tastiera è costituito da caratteri. Per ottenere un lettore per i caratteri, occorre convertire System.in in un oggetto InputStreamReader, come questo:
InputStreamReader reader = new InputStreamReader(System.in);
Un lettore di stream di input può leggere i caratteri ma non può leggere un’intera stringa
in una volta sola. Questo non lo rende molto pratico, visto che non si vuole certo mettere
insieme ogni riga in input incollandone i singoli caratteri. Per superare questo limite si
può convertire un lettore di stream in un oggetto BufferedReader.
BufferedReader console = new BufferedReader(
new InputStreamReader(System.in));
Ora si utilizzi il metodo readLine per leggere una riga in input, come questa:
System.out.println("How old are you?");
String input = console.readLine();
int count = Integer.parseInt(input);
Il metodo readLine può rilevare un’eccezione IOException se c’è un errore di input.
Quando viene raggiunta la fine dell’input, il metodo readLine restituisce null.
Da C++ a Java
23
A.12 Campi e metodi statici
Può accadere che sia necessario condividere una variabile tra tutti gli oggetti di una
classe. Ecco un tipico esempio. La classe Random nel pacchetto java.util implementa
un generatore di numeri casuale. Ha metodi come nextInt, nextDouble e nextBoolean, che restituiscono interi casuali, numeri a virgola mobile e valori di tipo booleano.
Per esempio, ecco come vengono visualizzati 10 interi casuali:
Random generator = new Random();
for (int i = 1; i <= 10; i++)
System.out.println(generator.nextInt());
Si utilizzi un generatore di numeri casuali nella classe Greeter:
public String saySomething()
{
if (generator.nextBoolean())
return "Hello, " + name + "!";
else
return "Goodbye, " + name + "!";
}
Sarebbe uno spreco dotare ogni oggetto Greeter del proprio generatore di numeri casuali. Per condividere un generatore tra tutti gli oggetti Greeter, si dichiari il campo
come statico:
public class Greeter
{
...
private static Random generator = new Random();
}
Le variabili condivise come questa sono relativamente rare. Un impiego più comune
della parola chiave static è quello che riguarda la definizione delle costanti. Per esempio, la classe Math contiene le seguenti definizioni:
public class Math
{
...
public static final double E = 2.7182818284590452354;
public static final double PI = 3.14159265358979323846;
}
La parola chiave final denota un valore costante. Dopo che una variabile final è stata
inizializzata, non è possibile modificarne il valore.
Queste costanti sono pubbliche e vi si fa riferimento come Math.PI e Math.E.
Un metodo statico è un metodo che non agisce sugli oggetti. Un esempio di metodo
statico già incontrato è Math.sqrt. Un altro impiego dei metodi statici è nei metodi
factory, ossia i metodi che restituiscono un oggetto, analogamente a un costruttore. Ecco
un metodo factory per la classe Greeter che restituisce un oggetto greeter con un
nome casuale:
public class Greeter
{
24
Appendice
public static Greeter getRandomInstance()
{
if (generator.nextBoolean())
return new Greeter("World");
else
return new Greeter("Mars");
}
...
}
Questo metodo viene invocato come Greeter.getRandomInstance(). Il metodo statico può accedere ai campi statici ma non a quelli di istanza, poiché non operano su un
oggetto.
I campi e i metodi statici hanno la loro posizione, ma sono abbastanza rari nei programmi orientati agli oggetti. Se un programma contiene molti campi e metodi statici,
significa che si è persa l’occasione di scoprire un numero di classi sufficienti per implementare il programma in una modalità orientata agli oggetti. Ecco un cattivo esempio che mostra come si possa scrivere programmi poveri e non orientati agli oggetti con
campi e metodi statici:
public class BadGreeter
{
public static void main(String[] args)
{
name = "World";
printHello();
}
public static void printHello() // cattivo stile
{
System.out.println("Hello, " + name + "!");
}
private static String name; // cattivo stile
}
A.13 Stile di programmazione
I nomi delle classi dovrebbero sempre iniziare con una lettera maiuscola, come String,
e utilizzare una capitalizzazione mista, come in StringTokenizer. I nomi dei pacchetti dovrebbero essere sempre in minuscolo, come edu.sjsu.cs.cs151.alice. I campi
e i metodi dovrebbero sempre iniziare come una minuscola e utilizzare una capitalizzazione mista, come in name e sayHello. I caratteri di sottolineatura (_) non vengono
utilizzati molto spesso nei nomi di classi e metodi. Le costanti dovrebbero essere tutte in
maiuscolo con un carattere di sottolineatura occasionale, come in PI or MAX_VALUE.
Questo non è un requisito del linguaggio Java, ma una convenzione che viene seguita
pressoché da tutti i programmatori Java. I programmi in cui le classi iniziano in minuscolo e i metodi in maiuscolo possono sembrare molto strani ai programmatori Java.
Inoltre la maggior parte di essi non ritiene che sia una buona scelta di stile quella di
utilizzare i prefissi per i campi (come in _name o m_Name).
È molto comune utilizzare i prefissi get e set per i metodi che ottengono o impostano la
proprietà di un oggetto, come
public String getName()
public void setName(String aName)
Da C++ a Java
25
Una proprietà Boolean ha tuttavia i prefissi is e set, come in
public boolean isPolite()
public void setPolite(boolean b)
Per quanto riguarda le parentesi graffe, esistono due stili principali: lo stile di Allmann,
in cui le parentesi vengono allineate, e il più compatto ma meno chiaro stile di Kernighan e Ritchie. Ecco la classe Greeter formattata con lo stile di Kernighan e Ritchie.
public class Greeter {
public Greeter(String aName) {
name = aName;
}
public String sayHello() {
return "Hello, " + name + "!";
}
private String name;
}
In questo libro si utilizza lo stile di Allmann.
Alcuni programmatori elencano i campi prima dei metodi in una classe:
public class Greeter
{
private String name;
// elencare per prime le caratteristiche private non è una buona idea
public Greeter(String aName)
{
...
}
}
Dal punto di vista della programmazione orientata agli oggetti, ha più senso elencare
per prima l’interfaccia pubblica. Questo è l’approccio utilizzato in questo libro.
A eccezione dei campi public static finali, tutti i campi dovrebbero essere dichiarati come private. Se si omette lo specificatore di accesso, il campo avrà una visibilità
di pacchetto e tutti i metodi delle classi nello stesso pacchetto potranno accedervi. È una
pratica poco sicura che è preferibile evitare.
Ai programmatori di C++ è fatta una piccola concessione: è tecnicamente legale
dichiarare le variabili di array come
int numbers[]
In Java è preferibile evitare questo stile e utilizzare
int[] numbers
Questo stile mostra chiaramente il tipo int[] della variabile.
Tutte le classi, i metodi, i parametri e i valori restituiti dovrebbero avere commenti di
documentazione. Alcuni programmatori amano formattare questi commenti con una
colonna di asterischi, come in questo esempio:
/**
*
Saluta con un messaggio "Hello".
26
Appendice
*
*
*/
@return un messaggio che contiene "Hello" e il nome della
persona o entità salutata.
È un codice bello da guardare, ma rende difficile modificare i commenti. Non è quindi
uno stile consigliabile.
Si raccomanda di collocare spazi attorno agli operatori binari e dopo le parole chiave, ma non dopo i nomi di metodi o cast.
Stile buono
Stile cattivo
x > y
x>y
if (x > y)
if(x > y)
Math.sqrt(x)
Math.sqrt (x)
(int)x
(int) x
È preferibile non utilizzare numeri magici. Si utilizzino invece costanti denominate (variabili final). Per esempio, non si utilizzi
h = 31 * h + val[off]; // Male--cos’è 31?
Cos’è 31? Il numero di giorni di gennaio? La posizione del bit più alto in un intero? No,
è il moltiplicatore hash.
La soluzione corretta è quella di dichiarare una costante locale nel metodo
final int HASH_MULTIPLIER = 31
o una costante statica nella classe (se è utilizzata da più di un metodo)
private static final int HASH_MULTIPLIER = 31
e utilizzare quindi HASH_MULTIPLIER invece di 31.
Esercizi
Esercizio A.1. Aggiungere un metodo sayGoodbye alla classe Greeter e aggiungere
una chiamata per verificare la classe GreeterTest (oppure eseguire il testo in BlueJ).
Esercizio A.2. Cosa accade quando si esegue l’interprete Java sulla classe Greeter
invece che sulla classe GreeterTest? Si provino i due casi e li si illustri.
Esercizio A.3. Aggiungere commenti alla classe GreeterTest e al metodo main. Gli
args di documento sono “non utilizzati”. Utilizzare javadoc per generare un file
GreeterTest.html. Esaminare il file utilizzando il proprio browser.
Esercizio A.4. Creare un segnalibro docs/api/index.html nel proprio browser. Trovare la documentazione della classe String. Quanti metodi ha questa classe?
Esercizio A.5. Scrivere un programma che visualizzi “Hello, San José”. Utilizzare una
sequenza di escape a \u per indicare la lettera é.
Esercizio A.6. Qual è il carattere Unicode per la lettera greca “pi” (π)? E per il carattere
cinese “bu” ( )?
Da C++ a Java
27
Esercizio A.7. Eseguire l’utility javadoc sulla classe Greeter. Quale output si ottiene?
Come cambia l’output quando si elimina parte dei commenti di documentazione?
Esercizio A.8. Prelevare da Internet l’utility DocCheck e installarla. Quale output si
ottiene quando si elimina parte dei commenti di documentazione della classe Greeter?
Esercizio A.9. Scrivere un programma che calcoli e visualizzi la radice quadrata di
1000, arrotondata all’intero più vicino.
Esercizio A.10. Scrivere un programma che calcoli e visualizzi la somma degli interi da
1 a 100 e da 100 a 1000. Creare una classe Summer appropriata che non abbia metodi
main per questo scopo. Se non si utilizza BlueJ, creare una seconda classe con un metodo main per istanziare due oggetti della classe Summer.
Esercizio A.11. Aggiungere un metodo setName alla classe Greeter. Scrivere un programma con due variabili Greeter che fanno riferimento allo stesso oggetto Greeter.
Invocare setName su uno dei riferimenti e sayHello sull’altro. Visualizzare il valore
restituito e illustrare il risultato.
Esercizio A.12. Scrivere un programma che imposti una variabile Greeter su null e
che in seguito chiami sayHello su quella variabile. Illustrare l’output risultante. Cosa
indica il numero dietro al nome di file?
Esercizio A.13. Scrivere un programma di test che verifichi i tre metodi set dell’esempio, visualizzando le variabili di parametro prima e dopo la chiamata del metodo.
Esercizio A.14. Scrivere un metodo void swapNames(Greeter other) della classe
Greeter che scambi i nomi di questo oggetto Greeter e di un altro.
Esercizio A.15. Scrivere un programma in cui G r e e t e r è nel pacchetto
edu.sjsu.cs.yourcourse.yourname e GreeterTest è nel pacchetto predefinito. In
quali directory devono essere collocati i file sorgente e di classe?
Esercizio A.16. Cosa c’è di errato in questo segmento di codice?
ArrayList strings;
strings.add("France");
Esercizio A.17. Scrivere un programma GreeterTest che costruisca gli oggetti Greeter per tutti gli argomenti della riga di comando e visualizzi i risultati del sayHello
chiamante. Per esempio, se il programma viene invocato come
java GreeterTest Mars Venus
il programma dovrà visualizzare
Hello, Mars!
Hello, Venus!
Esercizio A.18. Qual è il valore delle espressioni seguenti?
a. 2 + 2 + "2"
b. "" + countries, dove countries è un ArrayList riempito con numerose stringhe
c. "Hello" + new Greeter("World")
Scrivere un piccolo programma di esempio per trovare le risposte e illustrarle.
Esercizio A.19. Scrivere un programma che visualizzi la somma dei suoi argomenti
della riga di comando (presumendo che siano numeri). Per esempio
java Adder 3 2.5 -4.1
28
Appendice
dovrebbe visualizzare The sum is 1.4.
Esercizio A.20. Scrivere un programma GreeterTest che chieda all’utente “What is
your name?” e quindi visualizzi “Hello, nomeutente”. Utilizzare una finestra di dialogo.
Esercizio A.21. Scrivere un programma GreeterTest che chieda all’utente “What is
your name?” e quindi visualizzi “Hello, nomeutente”. Utilizzare un lettore bufferizzato.
Esercizio A.22. Scrivere una classe che possa generare stringhe casuali con caratteri in
un set dato. Per esempio,
RandomStringGenerator generator = new RandomStringGenerator();
generator.addRange('a', 'z');
generator.addRange('A', 'Z');
String s = generator.nextString(10);
// una stringa casuale costituita da dieci caratteri inglesi
// minuscoli o maiuscoli
La classe dovrebbe gestire un ArrayList di oggetti Range.
Esercizio A.23. Scrivere un programma che giochi a Tris con un utente umano. Utilizzare una classe TicTacToeBoard che memorizzi un array 3 × 3 di valori char (riempiti
con 'x', 'o' o caratteri di spazio). Il programma dovrebbe utilizzare una generatore di
numeri casuali per decidere chi deve iniziare. Quando è il turno del computer, generare
casualmente una mossa valida. Quando è il turno dell’utente umano, leggere la mossa e
verificare che sia valida.
Esercizio A.24. Scrivere un programma che legga i dati in input di un utente e visualizzi
i valori minimo, massimo e medio dei dati in input. Utilizzare una classe DataAnalyzer e una classe DataAnalyzerTest separate. Utilizzare una sequenza di finestre di
dialogo di input JoptionPane per leggere l’input.
Esercizio A.25. Ripetere l’esercizio precedente, ma leggere i dati da un file. Per leggerli, creare un BufferedReader come questo:
BufferedReader reader = new BufferedReader(new FileReader(filename));
Utilizzare quindi i metodi BufferedReader.readLine e Double.parseDouble.
Esercizio A.26. Migliorare le prestazioni del metodo factory getRandomInstance restituendo uno dei due oggetti Greeter corretti (memorizzati in campi statici) invece di
costruire un nuovo oggetto a ogni chiamata.
Esercizio A.27. Utilizzare una qualsiasi utility di compressione dell’SDK di Java per
decomprimere il file scr.jar che è parte dell’SDK. Osservare il codice della classe
String in java/lang/String.java. Quante regole di stile sono state violate dai programmatori? Osservare il metodo hashCode. Come dovrebbe essere riscritto in modo
meno confuso?