dispensa metodi informatici

Transcript

dispensa metodi informatici
Dispensa di programmazione in JAVA
Manlio De Domenico
Introduzione
Questa dispensa non pretende in alcun modo di essere esaustiva riguardo la programmazione in
generale e la programmazione in linguaggio JAVA in particolare. Il suo scopo è quello di fornire un
minimo di documentazione di rapida consultazione su questi argomenti.
I requisiti minimi richiesti sono nozioni base di informatica.
1 Fondamenti
1.1 Linguaggi di programmazione
Scopo del programmatore è quello di realizzare una procedura automatizzata che sulla base di dati
in input restituisca dopo una fase di elaborazione, dei dati in output.
Dunque possiamo così schematizzare il ruolo del programma, quell'insieme di procedure che
realizzano lo scopo del programmatore:
1) Raccolta dei dati di input da utilizzare;
2) Elaborazione di tali dati;
3) Risultato dell'elaborazione dei dati (output).
L'elaborazione viene svolta tramite istruzioni che il programmatore dice di eseguire alla macchina,
nel linguaggio della macchina stessa.
Per svolgere questo compito il programmatore ricorre appunto all'utilizzo di un linguaggio di
programmazione.
Il linguaggio è una struttura più o meno complessa di elementi che, sulla base di determinate regole,
permette di realizzare una procedura, ovvero una collezione di istruzioni da passare alla macchina
per fargli eseguire l'elaborazione dei dati richiesta.
Ogni linguaggio di programmazione, esattamente come un linguaggio verbale (francese, tedesco,
japponese), necessita di:
-
Alfabeto: che definisce l'insieme dei simboli utilizzabili;
Dizionario: che definisce l'insieme delle parole corrette che si possono costruire utilizzando
l'alfabeto;
Sintassi: che definisce le relazioni tra le parole e le regole per utilizzarle.
Questi sono gli elementi di base di un qualunque linguaggio di programmazione. Definendo
opportunamente un alfabeto, un dizionario e delle regole sintattiche, è possibile costruire per
esempio Perl, Python, C, C++, Visual Basic, Java.
In linea di principio tutti i linguaggi di programmazione sono identici, in quanto ciascuno di essi
può essere utilizzato per realizzare una procedura nel senso precedentemente definito.
Ciò che li distingue gli uni dagli altri sono la loro facilità d'uso (semplicità del dizionario e delle
regole sintattiche) per il programmatore, la loro portabilità (ovvero su quale sistema operativo
possono essere eseguiti) e soprattutto la loro idoneità alla procedura che si intende realizzare (in
altre parole, quanto sono validi per il problema da risolvere, in gergo : orientamento al problema): in
Java, come in C o in Visual Basic è possibile realizzare applicazioni di qualunque natura, grafiche,
scientifiche, etc.; tuttavia uno può essere più idoneo a elaborazioni grafiche, un altro a quelle
scientifiche, etc.
Il modo in cui un linguaggio di programmazione, come lo abbiamo appena definito, permette di
realizzare una procedura si basa su azioni e istruzioni che esso è in grado di far compiere alla
macchina (dopo la compilazione o l'assemblamento, che vedremo tra poco).
Definiamo un algoritmo come una sequenza ordinata e finita di passi (azioni o istruzioni) che
producono un ben determinato risultato in un tempo finito.
Dunque un programma è un insieme di algoritmi scritti in un linguaggio di programmazione che
adesso vedremo come si interfaccia con la macchina.
Abbiamo già anticipato che la macchina interpreta soltanto il PROPRIO linguaggio. Questo
significa che macchine diverse hanno linguaggi in linea di princ ipio (e anche in pratica) diversi.
Per poter eseguire un programma è dunque necessario scriverlo nel linguaggio macchina. Questo
rappresenta un notevole ostacolo alla stesura del programma stesso, nonchè alla sua potenziale
modifica e alla sua portabilità: scrivere in linguaggio macchina significa scrivere migliaia di righe
di codice praticamente illegibile e per una sola macchina.
Questo ostacolo può essere aggirato realizzando una struttura (ambiente di programmazione) che
garantisca:
- Un linguaggio universale, utilizzabile su più macchine, possibilmente diverse tra loro;
- Un traduttore fortemente legato alla macchina su cui è utilizzato il programma.
In questo modo il programmatore scrive del codice di programmazione in un linguaggio noto a tutti
definito secondo le regole precedenti e lascia al traduttore il compito di "tradurre" il suo
programma in linguaggio macchina.
Esistono due modi per tradurre, e questo differenzia in due grandi categorie i linguaggi di
programmazione:
-
-
Compilatori: un programma P scritto nel linguaggio L viene tradotto in un programma Q
equivalente scritto in linguaggio macchina; il programma Q viene eseguito. Esempi di
linguaggi compilati: C++, Pascal, Cobol, Fortran.
Interpreti: ogni istruzione del programma P viene tradotta generando la corrispondente
istruzione in linguaggio macchina; si esegue il codice in linguaggio macchina. Esempi di
linguaggi interpretati: Visual Basic, Lisp, Prolog, Java.
Se il grado di astrazione del linguaggio è alto, dunque si avvicina molto al linguaggio umano e a
costrutti più o meno complessi, si parla di linguaggio di alto livello; in caso contrario, se il livello
di astrazione è basso e i costrutti sono semplicistici, il linguaggio si avvicina a quello della
macchina e viene detto di basso livello.
Nei compilatori, prima di giungere al programma direttamente eseguibile, si percorre una fase
intermedia: in C tale fase prende il nome di Linking (il compilatore genera un file da passare al
linker che si occupa di collegare il programma con le librerie utilizzate), in JAVA invece viene
prodotto un file class che viene passato alla Virtual Machine, in Visual Basic viene creato un file
OBJ che viene poi linkato.
1.2 Teoria della programmazione
La realizzazione di un programma prevede diverse tappe fondamentali. Prima ancora di scrivere un
programma è sempre necessario avere un’idea chiara di come fare.
Se a prima vista questo può sembrare un appunto banale per piccole applicazioni, risulta di
fondamentale importanza quando a lavorare sul programma non è solo un individuo e il programma
risulta essere di grosse dimensioni.
Per tale scopo sono stati creati due metodi, fondamentalmente sostitutivi del vecchio ‘carta e
penna’:
-
Diagrammi di flusso;
Notazione Lineare Strutturata.
Il primo metodo consente di avere una visualizzazione grafica del programma e delle operazioni che
deve svolgere. Tuttavia si rivela del tutto inutilizzabile quando le operazioni cominciano ad essere
tante e complicate.
Il secondo metodo consente di scrivere il programma in una forma detta pseudo-codice: utilizzando
parole chiave comuni ad ogni linguaggio di programmazione (start, end, if then else, for next) si
crea una prima stesura del programma, ovviamente non funzionante perché scritta in un linguaggio
che non esiste, ma che permette al programmatore di sapere in anticipo cosa andare a scrivere.
Utilizzare pseudo-codice consente insomma di ideare un programma che, in seguito all’adattamento
ad un preciso alfabeto, ad un preciso dizionario e ad una precisa sintassi, è assolutamente
universale. Questo riadattamento tuttavia deve essere realizzato dall’uomo.
Le linee guida per la stesura del programma sono quindi:
-
Analisi del problema;
Scomposizione del problema in sottoproblemi;
Stesura in pseudo-codice;
Programmazione nel codice scelto;
Test del programma.
La fase di stesura è tutt’altro che semplice: essa richiede di ideare una strategia da seguire per
affinché il programma sia scritto nella maniera più semplice, più efficiente e di più facile
manutenzione possibile.
L’ingegneria del software cerca di aiutare lo sviluppo del software distinguendo le seguenti fasi:
-
Analisi del problema;
Progettazione dello schema di base;
Codifica dell’algoritmo;
Verifica di correttezza;
Correzione;
Stesura della documentazione;
Manutenzione e re visione;
Aggiornamento.
Tutti i linguaggi di programmazione, in maniera più o meno esplicita, presentano i seguenti caratteri
comuni, che caratterizzano ogni programma:
-
-
Dichiarazione delle costanti e delle variabili;
Inizializzazione delle costanti e delle variabili;
Raccolta dei dati di input dalle periferiche IN assegnando il valore di tali dati a delle
variabili o meno;
Istruzioni native (intrinseche del linguaggio) e funzioni (intrinseche del linguaggio o definite
dal programmatore utilizzando insiemi di istruzioni native) che possono o meno alterare il
valore delle variabili in seguito ad un insieme finito di azioni (algoritmo);
Raccolta dei dati di output ottenuti in seguito all’elaborazione dei dati provenienti da IN
tramite uno o più algoritmi.
Poiché fondamentalmente la macchina esegue solo operazioni, è compito del programmatore
smembrare in ogni singola operazione elementare il processo che egli vuole che la macchina
esegua.
La fase di individuazione di queste fasi elementari non è banale e spesso gran parte del tempo speso
a programmare è impiegato proprio a questo problema.
1.3 Elementi di programmazione
1.3.1 Tipizzazione
Una variabile è un dato individuato da un identificatore, che rappresenta l’indirizzo della cella di
memoria in cui il dato è archiviato.
Nella fase di dichiarazione (che non è obbligatoria in alcuni linguaggi, in altri si) si dichiara il tipo
di dato da associare alla variabile. Generalmente le tre grandi categorie di tipi sono: numeriche, di
stringhe, logiche.
Per esempio se var = 5, allora var è una variabile numerica; se var = “valore”, allora var
è una stringa; se var = true, allora var è una variabile logica.
Analogamente per i valori da assegnare alle costanti.
In ogni linguaggio di programmazione è possibile raccogliere gruppi di elementi sotto un’unica
entità che viene chiamato array. Si può pensare ad un array come ad una matrice m x n, che ha per
elementi delle variabili e\o delle costanti di qualunque tipo. Un array di dimensione 1 viene in
genere chiamato vettore; ne è un esempio: vec() = {1,5,7,3} che è un array di dimensione 1
x 4, ovvero un vettore a 4 componenti. La sintassi utilizzata in questo esempio non è universale:
ogni linguaggio ha il suo modo di definire un array e di assegnare i suoi elementi.
1.3.2 Operatori
Gli algoritmi fanno uso di istruzioni, funzioni e operatori: gli operatori sono dei particolari simboli
che hanno bisogno di uno o due dati in entrata (per esempio il NOT e il simbolo >) e restituiscono
un risultato in uscita. Tali operatori possono essere di varia natura ed agire su tipi diversi di dati (per
esempio operatori logici o operatori matematici o operatori che agiscono su stringhe).
1.3.3 Espressioni condizionali
Ogni linguaggio di programmazione è sensibile ad espressioni condizionali, ovvero particolari
costrutti che permettono di eseguire un algoritmo in relazione al verificarsi di una condizione,
l’argomento appunto dell’espressione condizionale.
In pseudo-codice possiamo definire l’espressione condizionale più semplice come
If (condizione) then
Istruzione 1
…
Istruzione n
[else
Istruzione 1
…
Istruzione m]
End if
dove abbiamo indicato tra [] un blocco di codice che non è obbligatorio inserire a meno che non lo
richieda il risultato che si vuole ottenere. Si può ottenere una forma più estesa del costrutto if..then
come
If (condizione) then
Istruzione 1
…
Istruzione m
[ElseIf (condizione) then
Istruzione 1
…
Istruzione n
ElseIf (condizione) then
Istruzione 1
…
Istruzione p
…
else
Istruzione 1
…
Istruzione q]
End if
in modo da tenere in considerazione non solo un’espressione condizionale e la sua diretta
alternativa, ma più espressioni condizioni e una alternativa generica (quella relativa all’else) nel
caso in cui nessuna delle precedenti condizioni non si sia verificata.
Un altro costrutto condizionale è, sempre in pseudo-codice, lo switch:
switch (id_selezione)
case 1:
Istruzione 1
…
Istruzione p
…
case m:
Istruzione 1
…
Istruzione q
Default:
Istruzione 1
…
Istruzione r
end switch
Con questo costrutto stiamo dicendo al programma di controllare quale caso si verifica tra quelli
definiti da un numero che va da 1 a m (case) in funzione del parametro id_selezione che
viene passato allo switch. Il programma eseguirà dunque le istruzioni interne al caso identificato,
e qualora nessuno dei casi fosse pari ad id_selezione verranno eseguite le istruzioni che si
trovano nel caso predefinito Default.
Tanto per portare qualche esempio, questo costrutto mantiene questa forma quasi totalmente in
JAVA e in C, e diviene invece Select..Case in Visual Basic.
1.3.4 Cicli e espressioni iterative
Ogni linguaggio prevede almeno un costrutto che permetta di svolgere in maniera iterativa delle
operazioni. Per esempio se volessimo stampare tutti i nominativi interni ad un archivio, usiamo
un’espressione iterativa che svolge continuamente l’operazione di lettura dall’archivio e di stampa,
finchè una condizione assegnata non si verifica. In pseudo-codice possiamo identificare almeno 3
costrutti di questo genere. Il primo si chiama For…Next:
For counter = min to max [step step_val]
Istruzione 1
…
Istruzione n
Next
Dove stiamo chiedendo di eseguire il blocco di n istruzioni per un numero di volte pari a (max –
min) / step_val, essendo step_val il salto che il contatore (counter) deve eseguire nel
procedere da min a max. Esempio:
For counter = 1 to 5 step 2
Stampa counter
Next
Produrrà in output i numeri 1,3,5. Lo step è facoltativo, se non viene definito il suo valore di
default è pari ad 1 per la grande maggioranza di linguaggi di programmazione.
Il secondo costrutto si chiama Do:
Do (condizione)
Istruzione 1
…
Istruzione n
Loop
In pratica con questo costrutto eseguiremo il blocco di istruzioni fino a quando condizione non
sarà verificata. Questo costrutto può non eseguire alcun blocco se per esempio condizione è
verificata fin da subito. La parola chiave Loop è qui inserita solo per dare una chiusura al costrutto
Do.
È per questo che esiste un terzo costrutto, Do…While, che almeno una volta esegue il blocco di
istruzioni e poi eventualmente lo riesegue ciclicamente fino a quando non viene verificata
condizione:
Do
Istruzione 1
…
Istruzione n
While (condizione)
1.3.5 Funzioni
Il concetto di funzione per un linguaggio di programmazione è identico al concetto di funzione
matematica classico, con la sola differenza che al posto di eseguire un’operazione matematica viene
eseguito un algoritmo, che restituisce un risultato che sarà chiamato valore della funzione in
rapporto al valore che è stato passato alla funzione stessa, che prende il nome di parametro. Una
funzione è caratterizzata da un nome, dai parametri che necessita per funzionare correttamente e
dalle istruzioni che esegue per restituire il risultato per cui è stata creata.
È frequente poi richiamare la funzione stessa in altre parti del programma; questo è fatto per rendere
molto più snello il programma stesso e la stesura del suo codice. Esempio in pseudo-codice:
Function TestF(par1, par2, …, parN)
Istruzione 1
…
Istruzione M
End Function
result = TestF(a, b, …, n)
Dove le M istruzioni possono o meno agire sugli N parametri al fine di elaborarli per ottenere un
risultato. È importante sottolineare che i parametri possono essere di tipi diversi tra loro e che il
risultato di una funzione è invece di un solo tipo (numerico, logico, stringa).
Per ottenere tale risultato dobbiamo richiamare (result) in qualche parte del codice questa
funzione.
1.3.6 SubProcedure
Una sottoprocedura è una sorta di programma all’interno del programma. Quando abbiamo parlato
di smembrare in sottoproble mi il problema da far risolvere al programma intendevamo suddividere
la procedura da eseguire in procedure elementari. Il richiamo (Call) nel giusto ordine di tali
procedure elementari costituisce il programma stesso. Strutturalmente simile ad una funzione, la
sottoprocedura non ha come fine quello di dare un risultato di un qualche tipo, ma quello di
svolgere delle operazioni, di eseguire istruzioni. Essa rappresenta una tappa fondamentale al
raggiungimento del risultato, al contrario della funzione che viene utilizzata soltanto come mezzo
per ottenere un risultato assegnato ad una variabile. Esempio in pseudo-codice:
Procedure TestP(par1, par2, …, parN)
Istruzione 1
…
Istruzione M
End Procedure
Call TestP(a, b, …, n)
1.3.7 Commenti al codice
Per concludere, menzioniamo i commenti al codice, che sono delle righe che contengono
spiegazioni dettagliate su punti particolarmente complessi del codice o semplici note che il
programmatore vuole rendere note a chi leggerà il suo codice o a sé stesso, nel caso voglia
modificare il codice in futuro. Commentare un codice molto spesso permette a chi programma di
risparmiare tempo per la manutenzione del codice stesso. La sintassi generica di un commento si
può schematizzare utilizzando dei delimitatori, costituiti da uno o più caratteri in successione senza
spazi e il commento. In C si può commentare come
/* commento */
// commento
In Visual Basic basta anteporre un apostrofo:
‘Commento
In Bash basta anteporre un #:
#Commento
1.4 Cenni di programmazione ad oggetti
Finora abbiamo soltanto discusso di linguaggi di programmazione interpretati o compilati. Pur
mantenendo questa classificazione binaria, possiamo introdurre una nuova classificazione in 3
categorie:
1) Imperativi;
2) Object Oriented;
3) Funzionali.
I linguaggi cosi classificati possono essere, all’interno di ogni categoria, ulteriormente catalogati in
interpretati o compilati; per esempio: C è imperativo e compilato; JAVA è Object Oriented e
compilato; PHP è Object Oriented e interpretato.
I linguaggi OO utilizzano una diversa filosofia di programmazione rispetto a quelli imperativi,
indipendentemente dal fatto che siano interpretati o compilati.
Procediamo con un esempio: supponiamo che ci sia Mario che deve studiare, un’azione
relativamente semplice da programmare.
Utilizzando un linguaggio imperativo, in pseudo-codice avremmo:
Dichiara Mario come Uomo
Dichiara Testo come Libro
PrendiLibro Testo
Siedi Mario
ApriLibro (Mario, Testo)
Studia (Mario, Testo)
Con questo esempio abbiamo dichiarato che Mario è un uomo (un tipo, come se fosse un numero o
una stringa per intenderci) e Testo è un libro. Le istruzioni native (in pseudo-codice)
“PrendiLibro, Siedi, ApriLibro, Studia” eseguono delle azioni rispettivamente sui
parametri passati come argomenti Testo, Mario, Mario-Testo, Mario-Testo (in quanto le istruzioni
possono agire su uno o più parametri contemporaneamente).
Vediamo di tradurre in linguaggio OO la stessa operazione. Un oggetto è un’entità con delle
caratteristiche, che in sostanza permette di svolgere delle operazioni in maniera differente per
esempio dal modo utilizzato da un linguaggio imperativo.
Un oggetto può essere interno o esterno al linguaggio (come le funzioni, che possono essere native
o definite dal programmatore) ed ha delle proprietà che ne individuano particolari caratteristiche e
dei metodi che invece gli fanno eseguire un’azione. Per esempio, supponiamo di avere interno al
linguaggio l’oggetto Uomo; le sue caratteristiche potrebbero essere il colore dei capelli o degli
occhi, l’altezza, il peso: esse si richiamano seguendo la sintassi Oggetto.proprietà, ovvero
nel nostro esempio, rispettivamente
Uomo.ColoreCapelli
Uomo.ColoreOcchi
Uomo.Altezza
Uomo.Peso
Le proprietà ovviamente, possono essere di due tipi: modificabili (read-write) e non modificabili
(read). Il colore degli occhi per esempio non può essere modificato (in sostanza ‘scritto’ dal
programmatore) ma può soltanto essere osservato (‘letto’); al contrario la proprietà Indirizzo di un
dato Uomo può essere sia richiamata in lettura, sia impostata dal programmatore, poiché l’indirizzo
è una cosa che può essere modificata. In generale le proprietà r e quelle rw possono o meno
necessitare di parametri come argomenti.
Oltre alle proprietà, gli oggetti possono o meno avere dei metodi, che come abbiamo già anticipato
fanno eseguire un’azione precisa all’oggetto in funzione o meno di parametri passati dal
programmatore; in generale la sintassi è Oggetto.metodo. Ne sono esempi:
Uomo.Mangia (Cibo)
Uomo.ApriLibro (Testo)
Uomo.Siedi (Posizione)
Uomo.Studia (Testo)
Detto questo, passiamo a riscrivere la precedente operazione in linguaggio OO utilizzando sempre
pseudo-codice e premettendo che per utilizzare l’oggeto Mario, dovremo dire che esso rappresenta
una nuova istanza dell’oggetto Uomo:
Dichiara Studente come New Uomo
Dichiara Testo come New Libro
Dichiara Stanza_Mario come New Camera
Studente.Nome = Mario
Studente.PrendiLibro Testo.Titolo
Studente.Siedi Stanza_Mario.Sedia
Studente.ApriLibro Testo.Titolo
Studente.Studia Testo.Titolo
che serve solo a livello intuitivo per capire la differenza di un OO da un linguaggio imperativo. Nei
metodi che abbiamo utilizzato, come per esempio Studia, abbiamo supposto (ma questa è una regola
che va definita nel linguaggio stesso) che l’argomento da passare fosse il titolo del libro (che è una
proprietà dell’oggetto Testo che è un’istanza dell’oggetto Libro), ma nessuno vieta al nostro
pseudo-codice di pretendere per esempio il codice ISBN del libro (molto meno intuitivo ma pur
sempre corretto in quanto l’ ISBN identifica univocamente un testo) richiamando la proprietà
Testo.ISBN.
1.5 Cenni sulle librerie
Molti linguaggi, che siano essi interpretati o compilati, in genere utilizzano un ristretto set di
istruzioni, operatori e funzioni. È diventato frequente fornire insieme al linguaggio stesso, in suo
supporto, un insieme di algoritmi più o meno avanzati scritti utilizzando gli elementi di base del
linguaggio e suddivisi in librerie. Le librerie permettono di utilizzare un numero molto più ampio di
elementi, che non sono nativi ma che sono compatibili con il linguaggio proprio perché costruiti con
elementi nativi del linguaggio stesso.
Le librerie sono entità molto potenti, che permettono al programmatore di distribuire il proprio
programma scindendo il corpo del programma (dove egli non fa altro che utilizzare elementi nativi
e richiami agli elementi interni alle librerie) dall’insieme degli elementi che egli utilizza per
risolvere il problema per cui ha realizzato il programma, ma che non sono nativi; in questo modo
permette ad altri programmatori di utilizzare le sue stesse librerie per risolvere problemi affini a
quelli suoi.
Le librerie possono fare parte dell’ambiente di programmazione del linguaggio oppure possono
essere scritte dal programmatore. Per esempio, esistono in supporto a molti linguaggi librerie che
gestiscono funzioni matematiche avanzate di utilizzo comune (arctanh, sinh, etc) e librerie che
invece permettono di calcolare trasformate di Fourier o di Laplace ma che invece sono state scritte
per esempio da uno studente del MIT per risolvere un suo problema specifico, ma che possono
essere liberamente reperite e aggiunte alla propria collezione di librerie.
Ci sono linguaggi di programmazione fondamentalmente scarni, imperativi o OO, come
rispettivamente C e JAVA, che si basano quasi interamente sull’utilizzo di librerie esterne che
ormai sono distribuite a corredo dell’ambiente di programmazione e sviluppo (SDK). Dunque
fondamentalmente il problema dello studio di tali linguaggi è si rivolto alla completa assimilazione
dei loro alfabeti, dizionari e sintassi, ma è maggiormente legato alle funzioni che è possibile
utilizzare tramite le librerie, che in pseudo-codice devono essere sempre richiamate (se utilizzate
internamente al programma che stiamo scrivendo) secondo
Include percorso_libreria
La sintassi di richiamo della libreria varia da linguaggio a linguaggio.
2 Linguaggio di programmazione JAVA
Con JAVA generalmente ci riferiamo sia ad un linguaggio di programmazione di alto livello
orientato agli oggetti e compilato sia ad una piattaforma.
Il compilatore JAVA, compilando un file di estensione .java, produce un file con estensione class,
che non contiene codice nativo del processore ma del semplice bytecode (linguaggio macchine della
JVM, Java Virtual Machine) e che di fatto viene passato alla JVM che lo esegue. Questo permette a
JAVA di essere un linguaggio portabile: avendo installata l’opportuna JVM relativa al sistema
operativo in uso, è possibile compilare ed eseguire un file scritto in JAVA praticamente ovunque.
La piattaforma JAVA è un software eseguito su piattaforme hardware; in generale una piattaforma è
descritta come una combinazione tra il sistema operativo e l’hardware. Piattaforme popolari sono
MS Windows, Solaris OS, Mac OS.
2.1 Struttura della piattaforma
Fondamentalmente JAVA è costituito dalla JVM, dalle JAPI (Java Application Program Interface) e
ovviamente dal suo alfabeto, dizionario, sintassi; la prima la abbiamo già incontrata, le seconde
rappresentano un insieme di componenti software orientati a determinate applicazioni e che sono
raggruppate in librerie, che in JAVA prendono il nome di package.
Possiamo schematizzare la realizzazione di un programma JAVA come in figura:
Il codice utilizza le JAPI e queste, insieme al resto del programma, vengono eseguite sulla JVM che
viene eseguita sulla piattaforma hardware.
Il generico programma, per essere realizzato ed eseguito, passa per le seguenti fasi:
EDITOR
Compilatore
Disco
Disco
Caricatore delle classi
Verificatore di
bytecode
Java Virtual Machine
Disco
Fase 1:
Il programma
viene
creato in un editor
e
memorizzato sul
disco con
l’estensione .java
Fase 2:
Il compilatore
crea il
bytecode
e lo
memorizza sul
disco
in un file con
l’estensione
.class
memoria
primaria
Fase 3:
Il caricatore delle
classi
legge i file .class
contenenti
i bytecode dal disco
e li
carica in memoria
memoria
primaria
memoria
primaria
Fase 4:
Il verificatore di
bytecode
conferma che tutti
i bytecode
sono validi e non
violano i
vincoli di sicurezza
di Java
Fase 5:
Per eseguire un
programma
la Java Virtual
Machine
legge i bytecode e li
traduce
nel linguaggio che il
computer
è in grado di
comprendere,
eventualmente
memorizzando i dati
durante l’esecuzione
del programma
Il codice può essere scritto in un qualunque editor di testo, sia che si tratti di MS Windows, sia di
Linux, e poi va compilato ed eseguito da shell (interprete dei comandi del sistema operativo, che sia
MS Windows o Linux non importa, a patto che la piattaforma JAVA sia opportunamente installata).
2.2 Struttura del codice
L’unità fondamentale di un programma scritto in JAVA è la classe. Tutte le operazioni che il
programma deve svolgere sono inserite all’interno di una o più classi eventualmente legate tra loro.
Una classe è definita dalla parola chiave di dizionario class e dall’identificatore della classe (il
suo nome):
class NomeClasse {
…
}
dove in sostanza si indica l’inizio e la fine del blocco di codice da eseguire con i delimitatori { }.
Gli stessi delimitatori vedremo come vengono utilizzati anche nelle espressioni condizionali e
iterative, nonché nei metodi.
Una classe definisce un nuovo tipo di dati, che successivamente può essere utilizzato per creare
oggetti di quel tipo. Quindi una classe è un modello di un oggetto mentre l’oggetto è un istanza di
una classe.
Adesso resta da definire quale deve essere la struttura dei dati da inserire tra le {} al posto dei “…”.
I dati associati ad una classe sono immagazzinati in variabili; il comportamento della classe è
invece implementato dai metodi, che descrivono i dati ed il comportamento associato con un’istanza
della classe. Esiste un metodo fondamentale detto main cui nessuna classe può prescindere: una
classe senza metodo main semplicemente genera un errore in fase di compilazione e quindi non
produrrà alcun programma eseguibile. È il metodo main che può o meno richiamare gli altri metodi
della classe o, come vedremo successivamente, di altri classi. La struttura del generico metodo è
[statements] NomeMetodo(type par) {
…
}
dove è necessario specificare che statements non è una parola chiave ma un insieme di parole
chiavi che il programmatore può utilizzare, NomeMetodo è l’identificatore del metodo, type non
è una parola chiave ma indica il tipo (numerico, stringa, etc) del parametro par: qualsiasi
informazione si voglia passare ad un metodo viene ricevuta dalle variabili specificate all’interno
delle serie di parentesi tonde che seguono il nome del metodo.
In statements possiamo definire:
-
-
-
La parola chiave public: è un modificatore d’accesso che consente al programmatore di
controllare la visibilità dei membri della classe. Quando un membro è preceduto da public
significa che questo ammette un accesso da parte di un codice esterno alla classe in cui viene
dichiarato.
La parola chiave static: consente a NomeMetodo() di essere chiamato senza dover
istanziare un’istanza particolare della classe. Questo si rende necessario per main() perché
viene chiamato dall’interprete Java prima che vengano creati degli oggetti.
La parola chiave void: indica al compilatore che NomeMetodo() non restituisce alcun
valore (vedremo che i metodi possono anche restituire valori!).
Il metodo main accetta un solo argomento: un array di elementi di tipo String (gli oggetti di tipo
string memorizzano le stringhe di caratteri). Attraverso l’array args durante l’esecuzione (a
runtime) si ha la possibilità di passare informazioni all’applicazione (argomenti). Le stringhe
dell’array sono chiamate argomenti da linea di comando (command-line arguments) e devono
essere separate da spazi.
La sintassi del metodo main è
public static void main(String[] args) {
…
}
A questo punto la struttura fondamentale di una generica classe diventa
class NomeClasse {
public static void main(String[] args) {
…
}
[statements] NomeMetodo1(type par) {
…
}
…
[statements] NomeMetodoN(type par) {
…
}
}
Per concludere anticipiamo che JAVA richiede che ogni istruzione termini con il simbolo ; che ne
denota la fine.
2.3 Alfabeto e dizionario
L’alfabeto del linguaggio JAVA è costituito dalle 26 lettere dell’alfabeto inglese e dai caratteri:
\|!”£$%&/()=?^’[]{}+-*,.;:<>_
Le parole chiave, fondamentalmente il dizionario, è costituito da:
abstract
assert
boolean
break
byte
case
catch
char
class
const
continue
default
do
double
else
extends
final
2.4 Tipi di dati
finally
float
For
goto
if
implements
import
instanceof
int
interface
long
native
new
null
package
private
protected
public
return
short
static
strictfp
Super
switch
synchronized
this
throw
throws
transient
try
void
volatile
while
In JAVA si deve dichiarare ogni tipo di variabile. Si distinguono otto tipi di dati primitivi:
•
•
•
•
interi (byte, short, int, long)
a virgola mobile (float, double)
caratteri (char)
booleani (boolean)
2.4.1 Interi
Tipo
Byte
Minimum Value
Maximum Value
int
short
long
byte
4
2
8
1
-2147483648
-32768
-9223372036854775808
-128
2147483647
32767
9223372036854775807
127
Il numero di valori dell’intervallo è indipendente dal computer su cui si esegue il codice
(architetture a 16, 32 o 64 bit).
•
•
•
I long hanno il suffisso L (es. 4000000000L);
Gli esadecimali hanno il prefisso 0x (es. 0xCAFE);
I numeri ottali hanno il prefisso 0 (es. 010).
2.4.2 Virgola mobile
Tipo
Byte Minimum Value
Maximum Value
Float (6 o 7 cifre decimali 4
significative)
-3.40282347E+38F
Double
(15
cifre
significative)
-1.7976931348623157E+308 +1.7976931348623157E+308
8
3.40282347E+38F
decimali
I numeri di tipo float hanno un suffisso (es. 3.402F). I numeri in virgola mobile senza suffisso
sono sempre considerati double.
Da JDK 5.0 è possibile definire i numeri a virgola mobile in formato esadecimale (es. 0.125
equivale a 0x1.0p-3)
Tre valori a virgola mobile speciali:
•
•
•
Infinito positivo (Double.POSITIVE_INFINITY)
Infinito negativo (Double.NEGATIVE_INFINITY)
NaN (not a number (Double.Nan) )
Per testare se un numero è NaN: if(Double.isNaN(x)).
2.4.3 Char
Le variabili di tipo char contengono caratteri codificati secondo la codifica UTF-16 (evuluzione
UNICODE). In Java il tipo char descrive un’unità di codice della codifica UTF-16. La codifica
Unicode usa 2 byte per rappresentare un carattere, consentendo quindi 65,536 caratteri differenti.
I caratteri sono delimitati dai singoli apici ’ ’ (es. 't' 'Y' '_' '$‘). È necessario usare il singolo apice
per i caratteri e il doppio apice per le stringhe: ‘h’ e “h” sono dati diversi, l’uno è un carattere e
l’altro è una stringa di lunghezza 1.
Si raccomanda di non utilizzare il tipo char nei programmi java a meno che si stiano
effettiva mente elaborando le unità di codice UTF-16.
Definiamo punto codice il valore di codice associato ad un carattere in uno schema di codifica. Lo
standard UNICODE prevede che i punti codice siano scritti in esadecimale con il prefisso U+.
La prima superficie di codice è chiamata superficie multilingua di base ed è costituita dai caratteri
UNICODE tradizionali.
Sono codificati tra i valori U+0000 (\u0000) e U+FFFF (\uffff). La sequenza di escape
\u indica che il numero che segue è un carattere UNICODE
Sequenze di escape per indicare caratteri particolari:
\b
\n
\t
\r
\’
\”
\\
backspace
\u0008
avanzamento di riga
\u0009
tabulazione
\u000a
Invio a capo
\u000d
Apice
‘
\u0027
doppio apice “
\u0022
backslash (barra retroversa \) \u005c
2.4.4 Boolean
Il tipo boolean può assumere due valori: false e true. È utilizzato per valutare condizioni di
tipo logico:
boolean isFun;
isFun = true;
Non è possibile effettuare la conversione tra valori interi e boolean.
2.4.5 Tipi enumerati
A volte una variabile può assumere solo un insieme limitato di valori: per esempio una pizzeria può
vendere pizze solo in quattro formati diversi tra loro: piccola, media, grande, extralarge.
Un tipo enumerato prevede un numero finito di valori nominali:
enum Size = {SMALL, MEDIUM, LARGE, EXTRALARGE}
Quindi è possibile dichiarare una variabile del tipo indicato di seguito
Size s = Size.LARGE;
di immediata generalizzazione.
2.4.6 Stringhe
Da un punto di vista concettuale in JAVA le stringhe non sono altro che una sequenza di caratteri,
un array di char. La definizione di una stringa avviene tramite il tipo String :
String NomeStringa = “Stringa_da_assegnare”;
In JAVA le stringhe si concatenano tra loro per mezzo dell’operatore +:
String a = “Hello";
String b = “world";
String message = a+b;
// conterrà “Helloworld”
int eta = 13;
String v = "PG" + eta;
// conterrà "PG13"
Per testare se due stringhe sono uguali si utilizza il metodo s.equals(t):
"Hello".equals(greeting)
"Hello".equalsIgnoreCase("hello")
Non usare l’operatore == per testare se due stringhe sono uguali! Infatti esso determina solo se le
due stringhe sono memorizzate nella stessa locazione.
String greeting = "Hello"; //inizializza greeting
if (greeting == "Hello") . . . // forse vero
if (greeting.substring(0, 3) == "Hel") . . . // forse falso
Vederemo successivamente l’ampia gamma di funzioni per la manipolazione delle stringhe che
offre JAVA.
2.4.7 Dichiarazione di variabili, costanti e inizializzazione
È di uso comune strutturare il nome delle variabili indicando l’identificatore (il nome della
variabile) preceduto dal tipo della variabile: intNumero e int_numero sono modi comuni di
nominare una variabile intera chiamata Numero. La sintassi della dichiarazione è
Tipo NomeVariabile;
essendo Tipo uno dei tipi trattati nei precedenti paragrafi. NomeVariabile è un’espressione che
non può contenere caratteri speciali come per esempio spazi, tab o + , . : ? / etc., ma soltanto lettere,
numeri e _ . È necessario che essa cominci con una lettera.
Si possono avere più dichiarazioni nella stessa riga:
Tipo NomeVariabile1, NomeVariabile2, …, NomeVariabileN,;
tutte dichiarate dello stesso tipo Tipo.
Dopo aver dichiarato una variabile, è necessario inizializzarla, ossia assegnarle un valore. La
mancata assegnazione genererà un errore in fase di compilazione. La sintassi è
NomeVariabile = ValoreDaAssegnare;
È possibile inizializzare una variabile in fase di dichiarazione:
Tipo NomeVariabile = ValoreDaAssegnare;
È importante sottolineare che il valore da assegnare deve essere compatibile con il tipo della
variabile; per intenderci se tipo è int e il valore che assegnamo è ‘x’ verrà generato un errore.
L’operatore di assegnazione è il simbolo =. Esso fondamentalmente prende il dato che sta alla sua
destra e valorizza la variabile indicata a sinistra con il valore di quel dato, sovrascrivendo (e quindi
causando la perdita) dell’eventuale dato precedentemente assegnato a quella stessa variabile. Per
esempio:
int N = 5;
N = 6;
\\ dichiara l’intero N e lo inizializza a 5
\\ modifica il valore di N a 6 causando la
\\ perdita del dato precedente: 5
In quest’ultimo esempio notiamo i commenti al codice, che abbiamo già definito a livello teorico nel
capitolo precedente. In JAVA i commenti utilizzano all’inizio della nota il simbolo \\.
In Java le costanti vengono definite attraverso l’uso della parola chiave final che indica che la
variabile viene valorizzata solo una volta al momento della sua definizione all’interno del metodo:
public static void main(String[] args) {
final double NUMERO_STUDENTI = 132;
}
Usualmente le costanti si indicano tutte in maiuscolo.
Può presentarsi la necessità di dover utilizzare il valore della costante in più metodi della classe
(costanti di calsse). Per questo motivo è possibile definire la costante all’esterno dei metodi:
Class Test {
public static final double NUMERO_STUDENTI = 132;
public static void main(String[] args) {
…
}
}
dove abbiamo fatto uso degli statements public static davanti alla definizione che avevamo
dato prima nel caso di dichiarazione interna ad un metodo.
Se vogliamo richiamare il valore di tale costante da un’altra classe, dobbiamo semplicemente usare
la sintassi Test.NUMERO_STUDENTI dove ci interessa.
2.5 Operatori
In Java esistono operatori predefiniti che possono essere applicati sulle variabili istanziate. Tipi di
operatori predefiniti:
•
•
•
Aritmetici
Relazionali e Logici
Bitwise (operatori sui bit)
Essendo Java un “linguaggio fortemente tipizzato” è importante applicare gli operatori a variabili
dello stesso tipo (o quando necessario di tipi compatibili).
2.5.1 Algebrici
Gli usuali operatori aritmetici sono
•
•
•
•
•
+
*
/
%
(somma)
(sottrazione)
(prodotto)
(divisione)
(resto) – applicabile su variabili di tipo intero;
Con tali operatori possono essere realizzate le espressioni aritmetiche, combinazioni di operandi ed
operatori.
A seconda del tipo di variabile (intera o a virgola mobile) l’operatore “/” da una risposta diversa
Divisione intera
Divisione in virgola mobile
15 / 2 = 7
3/5 =0
19 / 3 = 6
15.0 / 2 = 7.5
Resto (chiamato anche modulo)
14 % 2 = 0
3 %5 =3
19 % 3 = 1
La divisione per 0 di un numero intero da luogo ad un errore in fase di esecuzione (che chiameremo
eccezione).
Gli operatori hanno una precedenza, operatori con la stessa precedenza sono valutati da sinistra a
destra:
+/*
hanno la stessa precedenza
hanno la stessa precedenza
Mediante le parentesi si può cambiare la precedenza degli operatori. L’assegnamento è l’ultima
operazione che viene eseguita.
La divisione per zero di un numero a virgola mobile (float o double) da come risultato infinito o un
NaN.
In JAVA vi sono particolari operatori che permettono di assegnare ed operare con un’unica
scrittura:
+=
-=
*=
/=
%=
x
x
x
x
x
+=
-=
*=
/=
%=
y
y
y
y
y
equivale
equivale
equivale
equivale
equivale
a
a
a
a
a
x
x
x
x
x
=
=
=
=
=
x
x
x
x
x
+
*
/
%
y
y
y
y
y
L’incremento (decremento) unitario x=x+1 (x=x-1) di una variabile è una delle operazioni più
frequenti. A tale scopo JAVA ne implementa una versione compatta:
x++
++x
(x--)
(--x)
(notazione postfissa)
(notazione prefissa)
I due esemp i producono lo stesso risultato (si tratta di operatori unari che richiedono un solo
operando);
(++)
(--)
aggiunge 1 al suo operando;
sottrae 1 al suo operando;
Notazione prefissa: l’operazione viene effettuata prima di usare il valore dell’operando;
Notazione suffissa: l’operatore viene applicato dopo l’uso dell’operando;
int
int
int
int
x
y
a
b
=
=
=
=
2;
2;
2 * ++x; (a = 6) (x = 3)
2 * y++; (b = 4) (y = 3)
Se si combinano due valori con un operatore binario, come ad esempio n+f con n intero ed f float
entrambi gli operandi vengono convertiti in un unico tipo prima delle conversione.
•
•
•
•
Se uno dei due operandi è di tipo double l’altro viene convertito in double;
In caso contrario, se uno dei due operandi è di tipo float l’altro viene convertito in
float;
In caso contrario, se uno dei due dei due operandi è long l’altro viene convertito in long;
In caso contrario entrambi gli operandi vengono convertiti in int;
La conversione di tipo può essere forzata tramite il cast la cui sintassi richiede di indicare il tipo di
destinazione tra parentesi, cui si fa seguire il nome della variabile:
doulbe x = 9.997;
int nx = (int)x;
int i = 5, j = 10;
float x = i/j; // Errore logico
float x = (float) x/y;
2.5.2 Relazionali
Sono definiti come :
==
(A == B)
true se A e B sono uguali false altrimenti
<
(A < B)
true se A è minore B; se A è maggiore o uguale a B restituisce false;
<=
(A <= B)
true se A è minore o uguale a B; false altrimenti;
>
(A > B)
true se A è maggiore di B; se A è minore o uguale a B restituisce false;
>=
(A >= B)
true se A è maggiore o uguale a B; false altrimenti;
!=
(A != B)
true se A è diverso da B; false altrimenti;
2.5.3 Logici
Nelle espressioni booleane possono essere usati gli operatori logici:
•
•
•
! NOT
&& AND
|| OR
(operatore unario)
(operatore binario)
(operatore binario)
Richiedono operandi di tipo boolean e producono risultati boolean secondo le corrispondenti tavole
di verità.
Quando si costruiscono espressioni che fanno uso di più operatori logici, è buona abitudine
suddividere con parentesi ( ) le operazioni che vogliamo siano svolte, onde evitare problemi di
compilazione o risultati inaspettati.
JAVA supporta anche un operatore ternario ( ?: ) tale che se la condizione è true, la prima
espressione viene valutata altrimenti viene valutata la seconda espressione:
condizione ? espressione1 : espressione2
2.5.4 Bitwise
Supponiamo di avere i numeri:
a = 18 00000000 00000000 00000000 00001010
b = 34 00000000 00000000 00000000 00010010
Gli operatori bitwise sono definiti come segue:
And
(&)
a & b
00000000 00000000 00000000 00000010
Or (|)
a | b
00000000 00000000 00000000 00011010
Xor (^) or esclusivo
a ^ b 00000000 00000000 00000000 00011000
not
~a
11111111 11111111 11111111 11110101
shift a destra >>
a >> 2 00000000 00000000 00000000 00000010
Shift a sinistra <<
a << 4 00000000 00000000 00000000 10100000
2.5.5 Precedenze operazionali
Operatore
Associatività
[] . () (chiamata di metodo) Left to right
! ~ ++ -- + (unary) – (unary) Right to left
() (cast) new
* / %
Left to right
+ Left to right
<< >> >>>
Left to right
< <= > >= instanceof
Left to right
== !=
Left to right
&
Left to right
^
Left to right
|
Left to right
&&
Left to right
||
Left to right
?:
Right to left
= += -= *= /= %= &= |= ^= <<= Right to left
>>= >>>=
2.6 Array
Un array è una struttura dati che memorizza una collezione di valori dello stesso tipo. Il tipo degli
elementi dell’array è chiamato tipo di base dell’array ed il numero di elementi dell’array è
chiamato lunghezza dell’array. JAVA supporta array di tutti i tipi primitivi.
Si può accedere ad ogni valore individuale dell’array attraverso un indice intero. Per esempio se a è
un array di interi, a[i] è l’i-esimo elemento dell’array.
La dichiarazione di una variabile array avviene specificando il tipo array attraverso l’uso delle
parentesi quadrate [] e del nome della variabile array; per esempio:
int[] a; // stile Java
int a[]; // stile C
int [] arrayOfInts;
Questa istruzione dichiara solamente la variabile a di tipo array ma non inizializza un array
effettivo:
int[] a; // dichiara una variabile di tipo array
Per creare un array bisogna utilizzare l’operatore new, usato per creare un’istanza di un array.
Dopo l’operatore new viene specificato il tipo di base dell’array e la sua lunghezza con
un’espressione intera tra parentesi quadre.
int[] a = new int[100]; //crea un array con 100 elementi
arrayOfInts = new int [42];
someStrings = new String [ number + 2 ];
I due passi (dichiarazione e allocazione) possono essere combinati:
double [] someNumbers = new double [20];
Component [] widgets = new Component [12];
Gli indici di un array partono da 0. Il primo elemento di someNumbers[20] è 0, e l’ultimo è 19.
Dopo la creazione gli elementi di un array sono inizializzati ad un valore di default in base al loro
tipo.
Per tipi numerici questo significa che gli elementi vengono inizializzati a zero:
int [] grades = new int [30];
grades[0] = 99;
grades[1] = 72;
// grades[2] == 0
…
// grades[29] == 0
Esempio di classe che utilizza gli array:
public class InitArray
{
public static void main( String args[] )
{
int array[]; // declare array named array
array = new int[ 10 ]; // create the space for array
System.out.printf( "%s%8s\n", "Index", "Value" );
// column headings
// output each array element's value
for ( int counter = 0; counter < array.length; counter++ )
System.out.printf("%5d%8d\n", counter, array[counter]);
} // end main
} // end class InitArray
Come esempio applicativo riportiamo il codice dell’analisi delle frequenze di risposte ad un
questionario per esempio:
// Poll analysis program.
public class StudentPoll
{
public static void main( String args[] )
{
// array of survey responses
int responses[] = { 1, 2, 6, 4, 8, 5, 9, 7, 8, 10, 1, 6, 3, 8, 6,
10, 3, 8, 2, 7, 6, 5, 7, 6, 8, 6, 7, 5, 6, 6, 5, 6, 7, 5, 6,
4, 8, 6, 8, 10 };
int frequency[] = new int[ 11 ]; // array of frequency counters
// for each answer, select responses element and use that value
// as frequency index to determine element to increment
for ( int answer = 0; answer < responses.length; answer++ )
++frequency[ responses[ answer ] ];
System.out.printf( "%s%10s\n", "Rating", "Frequency" );
// output each array element's value
for ( int rating = 1; rating < frequency.length; rating++ )
System.out.printf( "%6d%10d\n", rating, frequency[ rating ] );
} // end main
} // end class StudentPoll
/**************************************************************************
* (C) Copyright 1992-2005 by Deitel & Associates, Inc. and
*
* Pearson Education, Inc. All Rights Reserved.
*
**************************************************************************/
Gli elementi di un array di oggetti sono riferimenti agli oggetti, non si tratta di istanze dell’oggetto.
Il valore di default di ogni elemento è null fino a quando non viene istanziato con l’appropriato
oggetto. Per esempio:
String names [] = new String [4];
names [0] = new String( );
names [1] = "Boofa";
names [2] = someObject.toString( );
// names[3] == null
Attenzione : in altri linguaggi, l’azione di creare un array è la stessa dell’allocazione di spazio per i
suoi elementi; in JAVA un array di oggetti, allocato, contiene solamente riferimenti ognuno con il
valore null:
JAVA supporta la creazione di array e la corrispondente inizializzazione dando l’elenco di valori
racchiuso tra parentesi graffe e separato da virgole:
int [] primes = { 1, 2, 3, 5, 7, 7+4 }; // primes[2] = 3
Un array di oggetti del corrispondente tipo e della corrispondente lunghezza viene creato
implicitamente. Si noti che non viene utilizzata la parola chiave new.
La sintassi di creazione attraverso l’uso delle parentesi graffe {} può essere usata anche per array di
oggetti. In questo caso ogni espressione deve corrispondere o ad un oggetto che viene assegnato al
corrispondente elemento dell’array o al valore null. Per esempio:
String [] verbs = { "run", "jump", someWord.toString( ) };
Button [] controls = { stopButton, new Button("Forwards"), new
Button("Backwards") };
// All types are subtypes of Object
Object [] objects = { stopButton, "A word", null };
//The following are equivalent:
Button [] threeButtons = new Button [3];
Button [] threeButtons = { null, null, null };
La dimensione di un array è ottenibile attraverso l’uso di una variabile public chiamata length:
char [] alphabet = new char [26];
int alphaLen = alphabet.length;
// alphaLen == 26
String [] musketeers = { "one", "two", "three" };
int num = musketeers.length; // num == 3
Il campo length è l’unico accessibile.
Per esempio, creiamo un array di riferimenti ad oggetti Button chiamati keyPad e quindi lo
riempiamo con oggetti Button:
Button [] keyPad = new Button [ 10 ];
for ( int i=0; i < keyPad.length; i++ )
keyPad[ i ] = new Button( Integer.toString( i ) );
Quando si tenta di accedere ad un elemento che è al di fuori del range degli elementi di un array
questo causa un’eccezione ArrayIndexOutOfBoundsException.
Si tratta di una eccezione a runtime RuntimeException, cioè si verifica solo in fase di
esecuzione.
Si può decidere di catturarla nel caso si sospetti che si possa verificare:
String [] states = new String [50];
try {
states[0] = "California";
states[1] = "Oregon";
...
states[50] = "McDonald's Land";
// Error: array out of bounds
}
catch ( ArrayIndexOutOfBoundsException err ) { System.out.println("Handled
error: " + err.getMessage());
}
Possiamo riassumere quanto detto finora con un esempio. Il programma simula la permutazione e la
distribuzione delle carte da gioco:
•
•
Usa la generazione di numeri casuali
Usa un array di riferimenti per rappresentare le carte
Vengono definite tre classi:
•
•
•
Card: Rappresenta una carta giocata
DeckOfCards : Mazzo di 52 carte
DeckOfCardsTest: Classe di test
// Card class represents a playing card.
public class Card
{
private String face; // face of card ("Ace", "Deuce", ...)
private String suit; // suit of card ("Hearts", "Diamonds", ...)
// two-argument constructor initializes card's face and suit
public Card( String cardFace, String cardSuit )
{
face = cardFace; // initialize face of card
suit = cardSuit; // initialize suit of card
} // end two-argument Card constructor
// return String representation of Card
public String toString()
{
return face + " of " + suit;
} // end method toString
} // end class Card
// DeckOfCards class represents a deck of playing cards.
import java.util.Random;
public class DeckOfCards
{
private Card deck[]; // array of Card objects
private int currentCard; // index of next Card to be dealt
private final int NUMBER_OF_CARDS = 52; // constant number of Cards
private Random randomNumbers; // random number generator
// constructor fills deck of Cards
public DeckOfCards()
{
String faces[] = { "Ace", "Deuce", "Three", "Four", "Five", "Six",
"Seven", "Eight", "Nine", "Ten", "Jack", "Queen", "King" };
String suits[] = { "Hearts", "Diamonds", "Clubs", "Spades" };
deck = new Card[ NUMBER_OF_CARDS ]; // create array of Card objects
currentCard = 0; // set currentCard so first Card dealt is deck[ 0 ]
randomNumbers = new Random(); // create random number generator
// populate deck with Card objects
for ( int count = 0; count < deck.length; count++ )
deck[ count ] =
new Card( faces[ count % 13 ], suits[ count / 13 ] );
} // end DeckOfCards constructor
// shuffle deck of Cards with one-pass algorithm
public void shuffle()
{
// after shuffling, dealing should start at deck[ 0 ] again
currentCard = 0; // reinitialize currentCard
// for each Card, pick another random Card and swap them
for ( int first = 0; first < deck.length; first++ )
{
// select a random number between 0 and 51
int second = randomNumbers.nextInt( NUMBER_OF_CARDS );
// swap current Card with randomly selected Card
Card temp = deck[ first ];
deck[ first ] = deck[ second ];
deck[ second ] = temp;
} // end for
} // end method shuffle
// deal one Card
public Card dealCard()
{
// determine whether Cards remain to be dealt
if ( currentCard < deck.length )
return deck[ currentCard++ ]; // return current Card in array
else
return null; // return null to indicate that all Cards were dealt
} // end method dealCard
} // end class DeckOfCards
// Card shuffling and dealing application.
public class DeckOfCardsTest
{
// execute application
public static void main( String args[] )
{
DeckOfCards myDeckOfCards = new DeckOfCards();
myDeckOfCards.shuffle(); // place Cards in random order
// print all 52 Cards in the order in which they are dealt
for ( int i = 0; i < 13; i++ )
{
// deal and print 4 Cards
System.out.printf( "%-20s%-20s%-20s%-20s\n",
myDeckOfCards.dealCard(), myDeckOfCards.dealCard(),
myDeckOfCards.dealCard(), myDeckOfCards.dealCard() );
} // end for
} // end main
} // end class DeckOfCardsTest
/**************************************************************************
* (C) Copyright 1992-2005 by Deitel & Associates, Inc. and
*
* Pearson Education, Inc. All Rights Reserved.
*
*
*
**************************************************************************/
Un task comune è la copia di un range di elementi da un array ad un altro array. JAVA offre il
metodo arraycopy( ) della classe System:
System.arraycopy(source,sourceStart,destination,destStart,length);
Il seguente esempio crea un array di dimensione pari al doppio dell’originale:
String [] tmpVar = new String [ 2 * names.length ];
System.arraycopy( names, 0, tmpVar, 0, names.length );
names = tmpVar;
2.6.1 Parametri del metodo main
Abbiamo visto come il metodo main abbia come argomento un array di stringhe. Questo parametro
indica che il metodo può ricevere un’array di stringhe come argomenti da linea di comando.
public class Message
{
public static void main(String[] args)
{
if (args[0].equals("-h"))
System.out.print("Hello,");
else if (args[0].equals("-g"))
System.out.print("Goodbye,");
// print the other command-line arguments
for (int i = 1; i < args.length; i++)
System.out.print(" " + args[i]);
System.out.println("!");
}
}
Se il programma è chiamato con java Message -g cruel world allora avremo
args[0]: "-g"
args[1]: "cruel"
args[2]: "world"
Il programma stamperà il messaggio “Goodbye, cruel world!”
2.6.2 Array anonimi
Spesso è conveniente creare array “anonimi”, ovvero array che sono usati in una parte limitata di
codice. Questi array non hanno bisogno di un nome perché non saranno mai referenziati altrove in
nessun altro contesto, come per esempio se si desidera creare una collezione di oggetti per passarli
come argomento ad un qualche metodo.
Supponiamo di avere la necessità di un metodo chiamato setPets( ) che prende come argomento
un elenco di oggetti di tipo Animal. Supponendo che Cat e Dog sono sottoclassi di Animal
ecco come chiamare setPets( ) usando un’array anonimo:
Dog pokey = new Dog ("grey");
Cat boojum = new Cat ("grey");
Cat simon = new Cat ("orange");
setPets ( new Animal [] { pokey, boojum, simon });
new int[] { 17, 19, 23, 29, 31, 37 } \\ array anonimo di interi
2.6.3 Array multidimensionali
Il concetto di array è già stato affrontato: un array monodimensionale è un vettore i cui elementi
possono simbolicamente essere identificati come le sue componenti.
Analogamente nel caso di array multidimensionali possiamo immaginare che i suoi elementi siano i
corrispondenti di una matrice di ordine pari alle dimensioni dell’array:
JAVA supporta gli array multidimensionali (matrici) nella forma di array di array di tipo oggetto.
L’array multidimensionale viene creato con una sintassi C- like, utilizzando multipli di coppie di
parentesi quadre una per ogni dimensione.
Inoltre la stessa sintassi è usata per accedere agli elementi nelle varie posizioni dell’array
multidimensionale. Per esempio una scacchiera:
ChessPiece [][] chessBoard;
chessBoard = new ChessPiece [8][8];
chessBoard[0][0] = new ChessPiece.Rook;
chessBoard[1][0] = new ChessPiece.Pawn;
...
chessBoard è dichiarata come una variabile di tipo ChessPiece[][] (un array di array di
tipo ChessPiece).
Questa dichiarazione in modo implicito crea il tipo ChessPiece[].
L’esempio mette in evidenza la forma speciale dell’operatore new usata per creare array
multidimensionali.
Crea un array di oggetti di tipo ChessPiece[] e quindi successivamente istanzia ognuno degli
elementi dell’array in un array contenente oggetti di tipo ChessPiece.
Un esempio di array bidimensionale (matrice) è la matrice triangolare definita come tale che
•
•
j non può essere mai più grande di i, in una matrice triangolare;
La riga i-esima ha i+1 elementi.
int[][] odds = new int[NMAX + 1][];
for (int n = 0; n <= NMAX; n++)
odds[n] = new int[n + 1];
Si possono creare array multidimensionali con più di due dimensioni:
Color [][][] rgbCube = new Color [256][256][256];
rgbCube[0][0][0] = Color.black;
rgbCube[255][255][0] = Color.yellow;
...
Possiamo specificare un indice parziale di un array multidimensionale per ottenere un sottoarray
dello stesso tipo di oggetti ma con meno dimensioni.
La variabile chessBoard è di tipo ChessPiece[][].
L’espressione chessBoard[0] è valida e si riferisce la primo elemento di chessBoard, che in
JAVA è di tipo ChessPiece[]. Esempio tipico è la scacchiera una riga alla volta:
ChessPiece [] homeRow = { new ChessPiece("Rook"),
new ChessPiece("Knight"),
new ChessPiece("Bishop"),
new ChessPiece("King"),
new ChessPiece("Queen"),
new ChessPiece("Bishop"),
new ChessPiece("Knight"),
new ChessPiece("Rook") };
chessBoard[0] = homeRow;
Non dobbiamo necessariamente specificare le dimensioni di un array multidimensionale con una
sola operazione. La sintassi dell’operatore new consente di lasciare non specificate alcune
dimensioni; almeno la prima dimensione va specificata. Le altre dimensioni possono essere
specificate dopo.
Creiamo una scacchiera di valore Boolean values come segue:
boolean [][] checkerBoard;
checkerBoard = new boolean [8][];
checkerBoard[0] = new boolean [8];
checkerBoard[1] = new boolean [8];
...
checkerBoard[7] = new boolean [8];
Equivale a:
boolean [][] checkerBoard = new boolean [8][8];
Gli array possono avere anche dimensioni diverse in quanto gli array multidimensio nali non devono
essere necessariamente rettangolari. Esempio:
checkerBoard[2] = new boolean [3];
checkerBoard[3] = new boolean [10];
int [][] triangle = new int [5][];
for (int i = 0; i < triangle.length; i++)
{
triangle[i] = new int [i + 1];
for (int j = 0; j < i + 1; j++)
triangle[i][j] = i + j;
}
Possiamo sfruttare l’esempio InitArray.java:
// Initializing two-dimensional arrays.
public class InitArray
{
// create and output two-dimensional arrays
public static void main( String args[] )
{
int array1[][] = { { 1, 2, 3 }, { 4, 5, 6 } };
int array2[][] = { { 1, 2 }, { 3 }, { 4, 5, 6 } };
System.out.println( "Values in array1 by row are" );
outputArray( array1 ); // displays array1 by row
System.out.println( "\nValues in array2 by row are" );
outputArray( array2 ); // displays array2 by row
} // end main
// output rows and columns of a two-dimensional array
public static void outputArray( int array[][] )
{
// loop through array's rows
for ( int row = 0; row < array.length; row++ )
{
// loop through columns of current row
for ( int column = 0; column < array[ row ].length; column++ )
System.out.printf( "%d ", array[ row ][ column ] );
System.out.println(); // start new line of output
} // end outer for
} // end method outputArray
} // end class InitArray
/**************************************************************************
* (C) Copyright 1992-2005 by Deitel & Associates, Inc. and
*
* Pearson Education, Inc. All Rights Reserved.
*
*
*
**************************************************************************/
2.7 Input\Output
Un aspetto importante nella programmazione è legato alla possibilità di accettare input da parte
dell’utente e di formattare l’output in modo opportuno. Di fatto la maggior parte dei programmi
moderni utilizzano le GUI per interagire con l’utente. Questo sarà visto più avanti.
2.7.1 Input
Prima di JDK 5.0 non c’era un metodo conveniente per leggere l’input da console. Per nostra
fortuna questo problema è stato risolto. Per leggere l’input da console prima bisogna costruire uno
Scanner che viene agganciato allo "standard input stream" che è System.in.
Scanner in = new Scanner(System.in);
Adesso si hanno a disposizione diversi metodi della classe Scanner per leggere l’input. Per esempio:
System.out.print(“Come ti chiami? ");
String name = in.nextLine();
È stato utilizzato il metodo nextLine perchè l’input può contenere spazi.
Per leggere singole parole si può usare
String firstName = in.next();
Per leggere interi
int age = in.nextInt();
In modo simile per leggere double basta usare il metodo nextdouble:
double nextDouble()
Per verifica se c’è un’altra parola in input:
boolean hasNext()
mentre per verificare se la successiva sequenza di caratteri rappresenta un intero o un floating-point:
boolean hasNextInt()
boolean hasNextDouble()
Importante aggiungere la riga di codice all’inizio del programma:
import java.util.*;
per indicare che stiamo utilizzando le funzioni relative alla libreria util.
2.7.2 Output
JDK 5.0 implementa la funzione printf, metodo che proviene dalla libreria del linguaggio C. Per
esempio
System.out.printf("%8.2f", x);
stampa x con un campo con 8 caratteri ed una precisione di due caratteri . In questo caso l’output
contiene uno spazio iniziale e poi 7 caratteri. Si possono dare diversi parametri:
System.out.printf("Hello, %s. Next year, you'll be %d",name,age);
I parametric che gestiscono la formattazione dell’output prendono il nome di formattatori e possono
essere riassunti in tabella:
Conversion Character
d
Type
Decimal integer
Example
159
x
o
f
e
g
a
s
c
b
h
tx
%
n
Hexadecimal integer
9f
Octal integer
237
Fixed-point floating-point
15.9
Exponential floating-point
1.59e+01
General floating-point (the shorter of e and f)
Hexadecimal floating point
0x1.fccdp3
String
Hello
Character
H
Boolean
TRue
Hash code
42628b2
Date and time
The percent symbol
%
The platform-dependent line separator
2.8 Espressioni condizionali
Definiamo la sintassi dell’espressione condizionale if come
If (condizione) {
…
[Else If (condizione1){
…
}
…
Else If (condizioneN){
…
}
Else {
…
}]
}
dove abbiamo proposto la struttura più generale come introdotto nel primo capitolo; tale struttura si
definisce annidata: nelle istruzioni annidate ogni clausola else è associata alla istruzione if più
vicina (quella immediatamente precedente ad essa).
Java implementa il comando switch esattamente come viene implementato dai linguaggi C e
C++.
La forma più comune del comando switch è quella che prende come argomento un tipo intero (o
un tipo che può essere promosso automaticamente ad intero) e quindi effettua una selezione
attraverso un numero di alternative. La sua sintassi è la seguente:
switch (int expression)
{
case int constantExpression :
statement;
[ case int constantExpression statement; ]
...
[ default : statement; ]
}
Spesso viene utilizzato il comando break in unione a switch (o anche a if) per interrompere il
matching dei case nel caso in cui uno si sia verificato e si vuole uscire dallo switch non appena
vengono eseguite le istruzioni all’interno del case che verifica int expression.
Il comportamento a caduta del comando switch è utile quando si vogliono coprire diversi
possibili case con lo stesso comando, per esempio:
int value = getSize( );
switch( value )
{
case MINISCULE:
case TEENYWEENIE:
case SMALL:
System.out.println("Small" );
break;
case MEDIUM:
System.out.println("Medium" );
break;
case LARGE:
case EXTRALARGE:
System.out.println("Large" );
break;
}
raggruppa i sei possibili valori in tre case.
Il tipo enum è pensato per rimpiazzare l’uso di costanti intere. Enumeration usa oggetti come i
loro valori invece di interi ma preserva la nozione di ordinamento e confronto. Per esempio
enum Size { Small, Medium, Large }
// usage
Size size = ...;
switch ( size )
{
case Small: // equivalente a 1
...
case Medium: // equivalente a 2
...
case Large: // equivalente a 3
...
}
2.9 Espressioni iterative
Come anticipato nel primo capitolo in pseudo-codice, le espressioni iterative in JAVA sono 3, con i
significati già discussi:
while (condizione)
{
…
}
do
{
…
}
while (condizione);
for (inizializzazione; condizione; iterazione)
{
…
}
dove nel ciclo for si ha che inizializzazione è il valore che viene utilizzato per
inizializzare il contatore, condizione è la condizione che sarà testata prima di ogni esecuzione
del loop, iterazione effettua l’incremento o il decremento del contatore. Per esempio:
for ( int i = 0; i < 100; i++ )
{
System.out.println( i );
}
for ( int i = 100; i > 0; i-- )
{
System.out.println( “conto alla rovescia ”+ i );
}
Nel ciclo for si possono utilizzare più espressioni separandole con la virgola nelle sezioni di
inizializzazione ed incremento, per esempio:
for (int i = 0, j = 10; i < j; i++,j-- )
{
...
}
Il comando continue per i cicli for e while consente di saltare alla successiva iterazione
facendo ritornare il calcolo al punto di controllo della condizione
Il seguente esempio stampa i numeri da 0 a 99 saltando il numero 33:
for( int i=0; i < 100; i++ )
{
if ( i == 33 ) {
continue;
System.out.println( i );
}
}
Nota: se nel ciclo while si ha che condizione è sempre verificata, il ciclo non si arresterà mai,
causando l’inutilizzabilità del programma!
Con Java 5.0 è stata definita una nuova forma del for loop. È una forma semplificata che agisce
in modo simile al comando "foreach" presente in altri linguaggi. Consente di iterare su una serie di
valori in un array o in altri tipi di collection secondo la sintassi
for ( varDeclaration : iterable )
statement;
Può essere usato per iterare su un array di qualsiasi tipo o su un qualsisi tipo di oggetto JAVA che
implementa l’interfaccia java.lang.Iterable interface. Questo include la maggior parte delle Java
Collections API. Esempio:
int [] arrayOfInts = new int [] { 1, 2, 3, 4 };
for( int i : arrayOfInts )
System.out.println( i );
List<String> list = new ArrayList<String>( );
list.add("foo");
list.add("bar");
for( String s : list )
System.out.println( s );
List implementa l’interfaccia iterable e quindi può essere usata nel for loop enhanced.
2.10 Label (etichette)
Break e continue saranno familiari ai programmatori C. In JAVA i comandi break e
continue hanno funzionalità aggiuntive come la possibilità di specificare delle etichette per
effettuare dei salti in punti di codice specifico. Per esempio:
labelOne:
while ( condition )
{
...
labelTwo:
while ( condition )
{
... // break or continue point
}
// after labelTwo
}
// after labelOne
Nell’esempio un break o un continue senza argomenti hanno lo stesso effetto indicato negli
esempi precedenti.
Un break causa il salto al punto chiamato "after labelTwo";
Un continue causa il ritorno alla condizione di test del loop chiamato labelTwo.
Il comando break labelTwo ha lo stesso effetto di un break ordinario, ma break labelOne
interrompe entrambi i while fa saltare il processo al punto etichettato "after labelOne."
In modo simile continue labelTwo si comporta come un normale continue, ma continue
labelOne fa ritornare il processo al test del loop labelOne. Esempio:
Scanner in = new Scanner(System.in);
int n;
read_data:
while (. . .) // this loop statement is tagged with the label
{
. . .
for (. . .) // this inner loop is not labeled
{
System.out.print("Enter a number >= 0: ");
n = in.nextInt();
if (n < 0) // should never happen—can't go on
break read_data; // break out of read_data loop
. . .
}
}
// this statement is executed immediately after the labeled break
if (n < 0) // check for bad situation
{
// deal with bad situation
}
else
{
// carry out normal processing
}
3 JAVA e OOP
Abbiamo anticipato in precedenza che JAVA è un linguaggio orientato agli oggetti (Object
Oriented). Le principali caratteristiche di un OOP sono:
•
•
•
Astrazione : Un oggetto viene definito ed utilizzato come un’astrazione. Indipendente dalle
componenti interne (es. automobile = insieme di pezzi, ma questo viene trascurato quando
l’auto viene utilizzata);
Incapsulamento: Gli oggetti mostrano all’esterno solo la loro interfaccia. Si possono usare
le informazioni ed i servizi forniti da un’interfaccia senza conoscerne i dettagli
implementativi. La base dell’incapsulamento è la classe;
Ereditarietà: Permette ad on oggetto di acquisire proprietà di altri oggetti (questo facilita
l’uso di codice scritto da altri);
•
Polimorfismo : Consente a un’interfaccia di essere utilizzata per una classe generale di
azioni. L’azione specifica viene determinata dall’esatta natura delle situazioni.
3.1 Incapsulamento
L’incapsulamento è il meccanismo che lega il codice e i dati che questo manipola, assicurando
entrambi da interferenze esterne ed abusi.
Lo scopo dell’OOP è decomporre un’applicazione in un numero di oggetti, che sono componenti
dell’applicazione autocontenuta (self-contained) che lavorano assieme.
L’obiettivo è decomporre il problema in un numero di sottoproblemi più semplici da manipolare e
mantenere.
Una metodologia basata sugli oggetti è un insieme di regole create per aiutare a spezzare
l’applicazione in oggetti.
Spesso questo significa “mappare” entità e concetti reali (spesso chiamati dominio applicativo del
problema).
Una classe è un modello rispetto al quale vengono definiti gli oggetti utilizzati nei programmi.
Questa definizione porta a pensare alle classi come “delle formine per fare biscotti, tenendo conto
che i biscotti sono gli oggetti della classe”.
Quando si costruisce un oggetto di una classe si dice che è stata creata un’istanza della classe.
In Java è sempre necessario creare proprie classi per descrivere gli oggetti che riguardano il
problema elaborato dalle proprie applicazioni per adattare le classi fornite dalla libreria standard ai
propri obiettivi di progetto.
L’incapsulamento è il concetto fondamentale per lavorare con gli oggetti. Da un punto di vista
formale non è altro che la combinazione dei dati e del loro comportamento in un pacchetto che
occulta all’utente esterno l’implementazione effettiva.
I dati nell’oggetto sono chiamati campi istanza dell’oggetto e le procedure che elaborano i dati sono
i metodi dell’oggetto.
Lo stato in un oggetto può cambiare ogni volta che si richiama un metodo dell’oggetto.
L’incapsulamento permette di definire il comportamento a scatola nera di un oggetto che costituisce
il fondamento per la riusabilità e l’affidabilità dei programmi.
3.2 Ereditarietà
Un altro principio della programmazione orientata agli oggetti semplifica la definizione delle classi
in Java: è possibile scrivere le classi come estensione di altre classi.
Java nasce con una super classe chiamata Object e tutte le atre classi estendono questa classe.
Quando si estende una classe esistente la nuova classe erediterà tutte le proprietà ed i metodi della
classe che estende; in tale nuova classe è possibile aggiungere nuovi metodi e campi peculiari solo
della nuova classe. Questo concetto è chiamato ereditarietà.
Nella OOP ci sono tre caratteristiche chiave degli oggetti:
•
•
Il comportamento: Cosa posso fare con questo oggetto o quali metodi possono essere
applicati su esso.
Lo stato: Come reagisce un oggetto quando vengono applicati dei metodi su esso.
•
L’identità: Come distinguo l’oggetto da altri oggetti che hanno lo stesso comportamento o
lo stesso stato.
Tutti gli oggetti, istanze della stessa classe condividono determinate caratteristiche dato che
supportano lo stesso comportamento. Il comportamento di un oggetto è definito dai metodi che si
possono invocare.
Per esempio supponiamo di avere un sistema per la gestione di ordini. Allora avremo i seguenti
termini:
•
•
•
•
•
Articolo
Ordine
Indirizzo per la consegna
Pagamento
Account
Questi termini possono dare luogo a delle classi come per esempio Item, Order, ecc.
Osservazione:
•
•
•
Gli Articoli sono aggiunti negli Ordini;
Gli Ordini sono espletati o cancellati.
I Pagamenti sono applicati agli ordini.
Con i verbi noi possiamo identificare le relazioni che esistono tra le classi e gli oggetti. Quando un
nuovo Articolo è aggiunto in un Ordine, l’oggetto Ordine
deve sapere questo perché
memorizza gli articoli. Quindi all’interno della classe Ordine dovremmo prevedere un metodo
chiamato add che prende come parametro un oggetto Articolo.
3.2.1 Le classi
Abbiamo anticipato grosso modo nel capitolo precedente il concetto di classe per avere un’idea
degli elementi che avremmo trattato successivamente. In questo paragrafo affrontiamo meglio il
concetto tentando di scendere più nel dettaglio.
Le classi sono i mattoni (building block) di un’applicazione JAVA. Qualunque concetto si desideri
implementare in un programma Java deve essere incapsulato in un classe. La classe rappresenta il
costrutto logico su cui si basa tutto il linguaggio Java perché definisce la forma e la natura di un
oggetto.
Una classe contiene:
•
•
•
metodi (funzioni);
variabili;
codici d’inizializzazione;
e come si vedrà più avanti: altre classi! Quando si definisce una classe, si dichiarano la sua forma e
la sua natura specificando i dati che essa contiene e il codice che agisce su tali dati. Il codice di una
classe definisce l’interfaccia ai propri dati. La sintassi generale è la seguente
class NomeClasse {
tipo variabile-istanza1;
tipo variabile-istanza2;
...
tipo variabile-istanzaN;
costruttore1
costruttore2
...
metodo1
metodo2
. . .
}
Esempio:
class box{
double width;
double height;
double depth;
}
class BoxDemo{
public static void main(String args[]){
Box mybox = new Box();
double vol;
mybox.width = 10;
mybox.height = 2;
mybox.depth = 15;
vol = mybox.witdh * mybox.height * mybox.depth;
System.out.println(“Il volume è ” + vol);
}
}
Tra le classi definite per un particolare problema si vengono a creare delle relazioni. Le relazioni
più comuni tra le classi sono le seguenti:
•
•
•
Dipendenza (use-a): Definisce la relazione più generica o scontata. Per esempio la classe
Order utilizza la classe Account in quanto gli oggetti Order devono accedere agli
oggetti Account per verificare lo stato di credito.
Aggregazione (has-a): Un oggetto Order contiene oggetti Item. In generale significa che
gli oggetti della classe A contengono oggetti della classe B. Si noti che l’aggregazione può
essere interpretata con una generica relazione di associatività.
Ereditarietà (is-a): Esprime una relazione tra una classe generica ed una più specifica. Per
esempio la classe RushOrder eredita la classe Order. RushOrder è una classe
specializzata che prevede dei metodi particolari per la gestione di ordini urgenti.
L’ottenimento degli oggetti di una classe è un processo in due fasi:
1) In primo luogo occorre dichiarare una variabile del tipo di classe.
2) In secondo luogo occorre acquisire una vera e propria copia fisica dell’oggetto e assegnarla a tale
variabile.
A questo scopo occorre utilizzare l’operatore new, cha alloca dinamicamente (ossia in fase di
esecuzione) la memoria per un oggetto e restituisce un riferimento ad esso. Questo riferimento è
l’indirizzo in memoria dell’oggetto allocato da new e viene poi memorizzato nella variabile. In
JAVA tutti gli oggetti di una classe devono essere allocati dinamicamente.
Osservazione : I riferimenti agli oggetti sono simili ai puntatori. Uni riferimento a un oggetto è
infatti simile a un puntatore in memoria. La differenza principale sta nel fatto che non è possibile
gestire i riferimenti come veri e propri puntatori. Quindi non è possibile fare in modo che un
riferimento a un oggetto punti a una posizione di memoria arbitraria o gestirlo come un numero
intero.
3.2.2 I costruttori
JAVA consente agli oggetti di inizializzare se stessi quando vengono creati. Un costruttore
inizializza un oggetto immediatamente dopo che è stato creato.
Il costruttore ha lo stesso nome della classe in cui si trova ed è sintatticamente simile ad un metodo.
La sintassi di un costruttore è del tipo:
Var-classe = new NomeClasse();
Var-classe è una variabile creata del tipo di classe mentre NomeClasse è il nome della classe
istanziata. Il nome della classe seguito dalle parentesi specifica il costruttore della classe che
definisce ciò che avviene quando viene creato un oggetto di una classe. Tipicamente quando si
definisce una classe vengono definiti esplicitamente i propri costruttori all’interno della definizione
di classe. Se non viene specificato alcun costruttore esplicito JAVA fornisce automaticamente un
costruttore di default.
I costruttori hanno sempre il nome uguale a quello della classe. Le variabili di riferimento agli
oggetti si comportano diversamente da quanto ci si potrebbe aspettare quando si verifica
un’assegnazione. Per esempio:.
Box b1 = new Box();
Box b2 = b1;
Dopo questa istruzione b1 e b2 fanno riferimento allo stesso oggetto. Dopo essere stato definito, il
costruttore viene chiamato immediatamente dopo la creazione dell’oggetto prima che l’operatore
new agisca. Ricapitolando:
•
•
•
•
•
Un costruttore ha lo stesso nome della classe;
Una classe può avere più di un costruttore;
Un costruttore può avere zero o più parametri;
Un costruttore non restituisce un valore;
Un costruttore è sempre chiamato con l’operatore new;
class box{
double width;
double height;
double depth;
Box(double w, double h, double h){
width = w;
height = h;
depth = d;
}
double volume(){
return mybox.witdh * mybox.height * mybox.depth;
}
}
class BoxDemo2{
public static void main(String args[]){
Box mybox = new Box(10,2,15);
double vol;
vol = mybox.volume();
System.out.println(“Il volume è ” + vol);
}
}
e come esempio di utilizzo di costruttore di default:
class box{
double width;
double height;
double depth;
Box(double w, double h, double h){
width = w;
height = h;
depth = d;
}
double volume(){
return mybox.witdh * mybox.height * mybox.depth;
}
void setDim(double w, double h, double h){
width = w;
height = h;
depth = d;
}
}
class BoxDemo2{
public static void main(String args[]){
Box mybox1 = new Box(10,2,15);
Box mybox2 = new Box(); \\costruttore di default
double vol;
vol = mybox1.volume();
System.out.println(“Il volume è ” + vol);
mybox2.setDim(5,5,5);
vol = mybox2.volume();
System.out.println(“Il volume è ” + vol);
}
}
3.2.3 I metodi
Sintassi generale di un metodo:
tipo nome(elenco_parametri){
//corpo del metodo
}
dove tipo specifica il tipo di dati restituiti dal metodo. Se il metodo non restituisce un valore
(come abbiamo visto per main) il tipo di ritorno deve essere void.
Il nome del metodo è specificato da nome. I metodi che hanno un tipo di ritorno diverso da void
restituiscono un valore alla routine chiamante utilizzando la seguente sintassi dell’istruzione
return:
return valore;
Esempio:
class box{
double width;
double height;
double depth;
void volume(){
System.out.println(“Il volume è ”);
System.out.println(mybox.witdh * mybox.height * mybox.depth);
}
}
class BoxDemo2{
public static void main(String args[]){
Box mybox = new Box();
mybox.width = 10;
mybox.height = 2;
mybox.depth = 15;
mybox.volume();
}
}
Sebbene il codice presentato negli esempi precedenti funzioni è mal fatto ed è facile commettere
errori. Per esempio è facile dimenticarsi di impostare una variabile.
È una buona regola che nei programmi JAVA ben progettati alle variabili di istanza si dovrebbe
accedere solo mediante metodi definiti dalla loro classe.
Di conseguenza l’approccio migliore per l’impostazione delle variabili di una classe consiste nel
creare dei metodi che prendano le variabili istanza come parametri e le impostino adeguatamente.
Esempio di incapsulamento:
class box{
double width;
double height;
double depth;
double volume(){
return mybox.witdh * mybox.height * mybox.depth;
}
void setDim(double w, double h, double h){
width = w;
height = h;
depth = d;
}
}
class BoxDemo2{
public static void main(String args[]){
Box mybox = new Box();
double vol;
mybox.setDim(10,2,15);
vol = mybox.volume();
System.out.println(“Il volume è ” + vol);
}
}
3.2.4 I modificatori
L’incapsulamento di oggetti è il processo di mascheramento dei dettagli dell’implementazione ad
altri oggetti. Nella progettazione orientata agli oggetti bisogna sempre equipaggiare una classe con
un insieme di variabili e metodi che danno senso all’oggetto.
Inoltre è buona norma nascondere i dettagli assicurando a chi usa l’oggetto che questo è sempre in
uno stato consistente. Uno stato consistente è uno stato permesso dal disegno di un oggetto.
Per default le variabili e i metodi di una classe sono accessibili dai membri della classe e da altre
classi che stanno nello stesso pacchetto (dalla terminologia C++ questi membri o classi sono
comunemente detti “package friendly”). Questo livello d’accesso è chiamato livello di visibilità di
default.
Per realizzare il controllo d’accesso alle classi, JAVA fornisce dei modificatori d’accesso
public, private e protected. Questi modificatori vanno usati al momento della
dichiarazione delle variabili e dei metodi.
•
•
•
Il modificatore private fa si che variabili o metodi possano essere usati solo dai membri
della stessa classe.
Il modificatore public fa si che variabili o metodi possano essere usati da ogni altra classe
dell’applicazione.
Il modificatore protected realizza un permesso speciale d’accesso per le sottoclassi.
Contrariamente da come si possa pensare, protected è leggermente meno restrittivo del
livello di default. Al livello di accesso di defalut legato alla possibilità di accesso a tali
variabili da parte di classi dello stesso package, i membri protected sono visibili dalle
sottoclassi della classe anche se questi vengono definiti in package differenti
3.3 Campi e metodi statici
Se una variabile locale ed una variabile istanza hanno lo stesso nome, la variabile locale nasconde la
variabile istanza all’interno dello scope del metodo (processo di shadowing). In questo esempio le
variabili locali xPos e yPos nascondono le variabili istanza :
class Bird {
int xPos, yPos;
int xNest, yNest;
...
double flyToNest( ) {
int xPos = xNest;
int yPos = yNest: return ( fly( xPos, yPos ) );
}
...
}
Impostare il valore delle due variabili locali dentro il metodo flyToNest( ) non ha effetto sulle
variabili istanza.
A volte un metodo deve fare riferimento all’oggetto che lo chiama. JAVA definisce la parola chiave
this che può essere utilizzata all’interno di qualunque metodo per fare riferimento all’oggetto in
uso. NOTA: this è sempre un riferimento all’oggetto su cui il metodo è stato chiamato.
// un uso ridondante di this
Box(double w, double h, double h){
this.width = w;
this.height = h;
this.depth = d;
}
class Bird {
int xPos, yPos;
double fly ( int xPos, int yPos ) {
double distance = Math.sqrt( xPos*xPos + yPos*yPos );
flap( distance );
this.xPos = xPos; // instance var = local vra
this.yPos = yPos;
return distance;
}
...
}
In questo esempio l’espressione this.xPos si riferisce alla variabile-istanza xPos della classe
Bird a cui viene assegnato il valore della variabile locale xPos parametro del metodo fly.
Osserviamo che il metodo fly usa la funzione per il calcolo della radice quadrata della classe
java.lang.Math.
Variabili istanza e metodi sono associati e l’accesso ad essi avviene attraverso le istanze della classi.
Vi sono però dei casi in cui questo non è vero: quando una variabile viene dichiarata facendo uso
del modificatore d’accesso static.
Queste variabili sono condivise da tutte le istanze delle classi. Queste variabili vengono chiamate
static.
class Pendulum {
...
static float gravAccel = 9.80;
...
Dichiarando gravAccel come static, ogni qual volta sarà cambiato il valore di essa in una
qualsiasi istanza di della classe Pendulum, il valore cambierà in tutte le istanze della classe
Pendulum.
A tali varibili si accede come qualsiasi variabile di istanza:
class Pendulum {
...
float getWeight ( )
{
return mass * gravAccel; }
...
}
Poichè le variabili static esistono nella classe indipendentemente da qualsiasi istanza si può
accedere ad esse in modo diretto attraverso la classe:
Pendulum.gravAccel = 8.76;
Questo cambiamento avrà effetto su tutte le istanze correnti e su tutte le istanze future della classe.
Le variabili static sono inoltre utili per condividere informazioni a runtime.
Per esempio si può creare un metodo per registrare le istanze dell’oggetto allo scopo che queste
possano comunicare o solo per tener traccia di esse.
Un altro uso comune delle variabili static è associato alle costanti. In questo caso si può usare il
modificatore static in connessione al modificatore final, cosa che avevamo già incontrato:
class Pendulum {
...
static final float EARTH_G = 9.80;
...
EARTH_G è una costante e può essere usata dentro la classe Pendulum o attraverso le sue istanze,
ma il suo valore non potrà cambiare a runtime. Le variabili static sono utili per la costruzione di
istanze:
class Pendulum {
...
static int SIMPLE = 0, ONE_SPRING = 1, TWO_SPRING = 2;
...
Queste variabili static possono essere usate per impostare il tipo di Pendolum :
Pendulum pendy = new Pendulum( );
pendy.setType( Pendulum.ONE_SPRING );
Possiamo usare le variabili static direttamente all’interno senza usare il prefisso Pendulum:
class Pendulum {
...
void resetEverything( ) {
setType ( SIMPLE );
...
}
...
}
Analogamente a quanto detto per le variabili static, anche i metodi possono essere definiti
static. I metodi static sono metodi che non lavorano su oggetti. Per esempio il metodo pow
della classe Math è un metodo statico. Math.pow(x, a) calcola x^a. Osserviamo che questo non
usa nessun oggetto Math per effettuare il calcolo della potenza. In altre parole non ha parametri
impliciti.
Possiamo pensare ai metodi static come metodi che non hanno come parametro this.
Poiché i metodi static non lavorano su oggetti, non si può accedere a campi istanza attraverso il
metodi statici. Ma metodi static possono accedere alle variabili static definite all’interno della
classe. Esempio:
public static int getNextId() {
return nextId; // returns static field
}
Per chiamare questo metodo basta dare
int n = Employee.getNextId();
3.3.1 Visibilità delle dichiarazioni
La visibilità di una dichiarazione di parametri è il corpo del metodo in cui la dichiarazione è
presente.
La visibilità di una dichiarazione locale parte dal punto in cui è presente la dichiarazione e arriva
fino al termine del blocco.
La visibilità di una dichiarazione di variabile locale che si trova nella sezione di inizializzazione
dell’intestazione di un’istruzione for è il corpo dell’istruzione for e le altre espressioni
dell’intestazione
La visibilità di un metodo o campo di una classe è l’intero corpo della classe. Ciò permette ai
metodi non statici di una classe di usare semplicemente il nome per chiamare altri metodi o per
accedere ai campi dichiarati nella classe. Esempio:
// Scope class demonstrates field and local variable scopes.
public class Scope
{
// field that is accessible to all methods of this class
private int x = 1;
// method begin creates and initializes local variable x
// and calls methods useLocalVariable and useField
public void begin()
{
int x = 5; // method's local variable x shadows field x
System.out.printf( "local x in method begin is %d\n", x );
useLocalVariable(); // useLocalVariable has local x
useField(); // useField uses class Scope's field x
useLocalVariable(); // useLocalVariable reinitializes local x
useField(); // class Scope's field x retains its value
System.out.printf( "\nlocal x in method begin is %d\n", x );
} // end method begin
// create and initialize local variable x during each call
public void useLocalVariable()
{
int x = 25; // initialized each time useLocalVariable is called
System.out.printf(
"\nlocal x on entering method useLocalVariable is %d\n", x );
++x; // modifies this method's local variable x
System.out.printf(
"local x before exiting method useLocalVariable is %d\n", x );
} // end method useLocalVariable
// modify class Scope's field x during each call
public void useField()
{
System.out.printf(
"\nfield x on entering method useField is %d\n", x );
x *= 10; // modifies class Scope's field x
System.out.printf(
"field x before exiting method useField is %d\n", x );
} // end method useField
} // end class Scope
/**************************************************************************
* (C) Copyright 1992-2005 by Deitel & Associates, Inc. and
*
* Pearson Education, Inc. All Rights Reserved.
*
*
*
**************************************************************************/
3.4 Overloading
Java permette di dichiarare più metodi con lo stesso nome nella stessa classe a patto che questi
abbiano parametri diversi;
Questa tecnica è chiamata overloading o sovraccarico dei metodi. Quando viene chiamato un
metodo sovraccarico, il compilatore Java seleziona il metodo corretto esaminando il numero e il
tipo degli argomenti della chiamata.
3.5 Passaggio dei parametri
Rivediamo come avviene il passaggio dei parametri ai metodi (o alle funzioni) nei linguaggi di
programmazione. Due modi: per Valore e per Riferimento.
Il termine passaggio per valore indica che il metodo riceve solo il valore dato dal chiamante.
Il passaggio per riferimento indica che il metodo ottiene la locazione della variabile attraverso il
chiamante.
Un metodo è in grado di modificare un valore memorizzato in una variabile passata per riferimento
ma non per valore.
In JAVA il passaggio avviene SEMPRE per valore. In realtà JAVA utilizza anche il passaggio per
riferimento in modo trasparente al programmatore.
Questo significa che il metodo ottiene una copia di tutti i valori dei parametri e quindi esso non può
modificare il contenuto di un parametro. Esempio:
double percent = 10;
harry.raiseSalary(percent);
Non ha importanza cosa fa questo metodo, alla sua uscita la variabile percent varrà ancora 10.
public static void tripleValue(double x)
// non funziona
{
x = 3 * x;
}
double percent = 10;
tripleValue(percent);
Ci sono due tipi di parametri per i me todi:
•
•
Tipi primitivi (numeri, valori Boolean)
Riferimenti ad Oggetti
Come appena visto è impossibile cambiare il valore di un parametro di tipo primitivo. Per gli
oggetti passati come parametro la situazione è diversa. Esempio:
public static void tripleSalary(Employee x) // funziona
{
x.raiseSalary(200);
}
Chiamando
harry = new Employee(. . .);
tripleSalary(harry);
Cosa avviene:
Attenzione : JAVA passa i parametri sempre per valore. Facciamo attenzione al seguente metodo:
public static void swap(Employee x, Employee y)
// doesn't work
{
Employee temp = x;
x = y;
y = temp;
}
Employee a = new Employee("Alice", . . .);
Employee b = new Employee("Bob", . . .);
swap(a, b); // does a now refer to Bob, b to Alice?
Ecco cosa accade:
3.5.1 Passare gli array ai metodi
Perché un metodo riceva un array attraverso una chiamata di metodo, l’elenco dei parametri del
metodo deve specificare un parametro array. Per esempio l’intestazione del metodo mdofyArray
potrebbe essere:
void modifyArray(int b[])
Questo indica che il metodo si aspetta di ricevere un array di interi come input.
Gli array vengono passati per riferimento:
•
•
•
Quando il metodo chiamato usa il nome di array b si riferisce all’effettivo array del
chiamante;
Quando un argomento di un metodo è un intero array oppure un riferimento all’elemento
individuale dell’array il metodo chiamato riceve sempre una copia del riferimento;
Quando a essere passato come argomento è un elemento singolo di un array di tipi primitivi
il metodo chiamante riceve una copia del valore dell’elemento passato.
3.5.2 Liste di argomenti a lunghezza variabile
Le liste di argomenti a lunghezza variabile sono state introdotte in J2SE 5.0. Possono essere creati
dei metodi con un numero variabile di argomenti se all’interno della lista di parametri si usa: il
nome del tipo, tre puntini di sospensione(…), e quindi il nome della variabile:
public static double average( double... numbers ){
double total = 0.0; // initialize total
// calculate total using the enhanced for statement
for ( double d : numbers )
total += d;
return total / numbers.length;
} // end method average
3.6 Tipi Enumerativi e classi
Tra i tipi enum e le classi esistono delle relazioni:
•
•
Un tipo enum è un riferimento. Si può sempre usare un riferimento ad un oggetto enum.
Un tipo enum viene dichiarato mediante una dichiarazione enum e consiste in una serie di
costanti separate da virgola.
Ogni dichiarazione enum corrisponde ad una dichiarazione di class con le seguenti restrizioni:
•
•
•
I tipi enum sono dichiarati implicitamente con final poiché dichiarano costanti che non
devono essere modificate;
Le costanti enum sono dichiarate implicitamente come static;
Ogni tentativo di creare un oggetto enum mediante l’uso dell’operatore new produce un
errore di compilazione.
3.7 Garbage collection ed il metodo finalize
Ogni classe JAVA possiede dei metodi della classe Object (implicitamente ereditati); uno di
questi è il metodo finalize. Ogni oggetto creato usa svariate risorse di sistema. È importante
disciplinare il modo in cui tali risorse vengono restituite al sistema. La Java Virtual Machine (JVM)
esegue automaticamente un processo chiamato garbage collection della JVM.
Quando un oggetto non viene più usato viene contrassegnato per la garbage collection della
JVM.
Ogni volta che il garbage collector viene eseguito si occupa di trovare tutti gli oggetti in
memoria non più in uso in modo da liberare spazio per altri oggetti.
Il metodo finalize viene chiamato dal garbage collector per eseguire le attività di
terminazione di un oggetto appena prima che la memoria occupata da questo oggetto venga
reclamata.
NOTA: Una classe che usa delle risorse di sistema, come i file su disco, dovrebbe fornire un
metodo in grado di rilasciare tali risorse quando non servono più. Per esempio molte classi delle
API Java forniscono metodi close e dispose progettati per questo scopo.
3.8 Ereditarietà e superclassi
L’ereditarietà rappresenta una forma di riutilizzo del software. Nuove classi possono essere create a
partire da classi esistenti. Queste assorbiranno gli attributi, il comportamento ed inoltre
implementeranno nuove capacità.
Java non supporta l’ereditarietà multipla. Una sottoclasse può essere definita da
•
•
•
Gruppi di oggetti più specializzati
Comportamento ereditato dalla superclasse (Possibilità di personalizzazione)
Comportamenti addizionali
Le sottoclassi estendono le superclassi. Un oggetto di una classe è un oggetto di un’altra classe. Per
esempio: Il rettangolo è un quadrilatero. La classe Rectangle eredita dalla classe
Quadrilateral:
•
•
Quadrilateral: superclasse
Rectangle: sottoclasse
Le superclassi rappresentano tipicamente insiemi di oggetti più grandi delle sottoclassi. Esempio:
•
•
superclasse: Veicoli (Macchine, Camion, Navi, Biciclette);
sottoclasse: Macchina (Più piccolo e più specifico insieme di veicoli);
Nelle classi Java esiste una gerarchia:
•
•
•
•
Superclassi dirette: Ereditate esplicitamente
Superclassi indiretta: Ereditati due o più livelli di gerarchia
Ereditarietà singola: Si eredita da una sola superclasse
Ereditarietà Multipla: Si eredita da più superclassi
Una classe Java può essere dichiarata come sottoclasse di un’altra classe usando la parola chiave
extends:
class Animal {
float weight;
...
void eat( ) {
...
}
...
}
class Mammal extends Animal {
// inherits weight
int heartRate;
...
// inherits eat( )
void breathe( ) {
...
}
}
La sottoclasse eredita le variabili ed i metodi dalla sua superclasse e può usarli come se fossero
dichiarati all’interno della stessa sottoclasse:
class Animal {
float weight;
...
void eat( ) {
...
}
...
}
class Mammal extends Animal {
// inherits weight
int heartRate;
...
// inherits eat( )
void breathe( ) {
...
}
}
La classe Cat eredita da Mammal che a sua volta eredita da Animal:
class Cat extends Mammal {
// inherits weight and
//heartRate
boolean longHair;
...
// inherits eat( ) and
// breathe( )
void purr( ) {
...
}
}
Altro esempio può essere la gerarchia universitaria:
o delle forme:
3.8.1 Applicazione pratica
Consideriamo la classe Employee :
class Employee { // constructor
public Employee(String n, double s, int year, int month, int day){
name = n;
salary = s;
GregorianCalendar calendar =
new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
// a method
public String getName() {
return name;
}
// more methods
. . .
// instance fields
private String name;
private double salary;
private Date hireDay;
}
Supponiamo che i Manager dell’azienda sono trattati diversamente da come sono trattati gli altri
impiegati . I Manager sono impiegati. Impiegati (Employee) e Manager hanno un pagamento
mensile. Supponiamo che i manager in più degli impiegati prenderanno un bonus legato alla loro
produttività.
Questa rappresenta la tipica situazione in cui l’ereditarietà risulta particolarmente indicata.
A tale scopo bisogna introdurre una nuova classe Manager che aggiunga questa nuova
funzionalità ed inoltre continui a offrire le medesime funzionalità e variabili istanza che sono
presenti nella classe Employe.
Possiamo quindi dire che ogni Manager è un Employee e quindi tra le due classi sussiste una
relazione di tipo “is-a”.
class Manager extends Employee{
added methods and fields
}
La nostra classe avrà un nuovo campo bonus:
class Manager extends Employee {
. . .
public void setBonus(double b) {
bonus = b;
}
private double bonus;
}
Supponiamo di istanziare un oggetto Manager e di applicare il metodo setBonus.
Manager boss = . . .;
boss.setBonus(5000);
Notiamo che se abbiamo un oggetto Employee non possiamo applicare il metodo setBonus in
quanto non definito tra i metodi della sua classe. Ma per un’istanza di Manager possiamo usare i
metodi quali getName and getHireDay in quanto saranno automaticamente ereditati dalla
superclasse Employee.
In modo simile campi quali name, salary e hireDay saranno ereditati dalla superclasse.
Quindi ogni oggetto Manager ha quattro campi: name, salary, hireDay e bonus.
Sebbene possiamo accedere a tutti i metodi della superclasse alcuni di questi potrebbero essere non
appropriati per la sottoclasse Manager.
Il metodo getSalary dovrebbe tenere in considerazione il bonus maturato.
In questo caso bisogna definire un nuovo metodo che sostituisce quello della classe Employee
secondo il principio dell’override (sovraccarico).
class Manager extends Employee {
. . .
public double getSalary() {
. . .
}
. . .
}
Come implementia mo questo metodo?
public double getSalary() {
return salary + bonus;
// FUNZIONA o NO?
}
Attenzione : dichiarare le variabili istanza di una classe private aiuta nel debugging e nel testing. Se
una sottoclasse potesse accedere alle variabili istanza private in modo diretto questo accesso
violerebbe il principio dell’incapsulamento.
Il metodo getSalary della classe Manager non ha accesso diretto ai campi private della sua
supercla sse.
Solo i metodi della classe Employee possono accedere ai campi private. Come facciamo?
public double getSalary() {
double baseSalary = getSalary(); // still won't work
return baseSalary + bonus;
}
Applicando il sovraccarico dei metodi la chiamata getSalary richiamerà se stessa!
La conseguenza è un loop infinito. Abbiamo bisogno di “estendere la nostra sintassi” con una nuova
keyword chiamata super quindi chiameremo
super.getSalary()
La versione corretta sarà
public double getSalary() {
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
Il costruttore per Manager viene definito nel seguente modo:
public Manager(String n, double s, int year, int month, int day){
super(n, s, year, month, day);
bonus = 0;
}
In questo caso la parola chiave super ha un significato differente. L’istruzione
super(n, s, year, month, day);
È una sintassi speciale di super per chiamare il costruttore della superclasse Employee. Questo è
fondamentale in quanto l’accesso alle variabili istanza private non è possibile.
La chiamata al costruttore della superclasse deve essere la prima istruzione del costruttore della
sottoclasse.
Se il costruttore della sottoclasse non chiama in modo esplicito il costruttore della superclasse Java
automaticamente invocherà il costruttore di default della superclasse.
Se la supercla sse non ha costruttore di default e la sottoclasse non richiama esplicitamente il
costruttore della superclasse il compilatore Java riporta un errore.
L’uso di variabili istanza protected consente alla sottoclasse di accedere direttamente alle
variabili della superclasse. I membri protected sono ereditati da tutte le sottoclassi della
superclasse. I vantaggi:
•
•
Le sottoclassi possono modificare direttamente le variabili
Performance leggermente migliori: Evitano l’uso dei metodi set/get
Gli svantaggi:
•
•
Checking non possibile, le sottoclassi possono assegnare valori illegali
Dipendenti dall’implementazione :
o Sottoclassi dipendenti dalle superclassi;
o Modifiche nella superclasse possono scatenare cambiamenti nelle sottoclassi
o Software meno robusto
3.9 Cenni di polimorfismo
Nel nostro esempio precedente ogni Manager è un impiegato. Questo realizza correttamente il
principio “is-a”. Un altro modo per realizzare “is-a” è il principio di sostituzione; si può usare un
oggetto di una sottoclasse quando il programma si aspetta un oggetto della superclasse. Esempio:
Employee e;
e = new Employee(. . .);
// Employee object expected
e = new Manager(. . .);
// OK, Manager can be used as well
In JAVA le variabili sono polimorfiche. Una variabile di tipo Employee si può riferire a un
oggetto di tipo Employee o a un oggetto di una qualsiasi delle sue sottoclassi (Manager,
Executive, Secretary).
Manager boss = new Manager(. . .);
Employee[] staff = new Employee[3];
staff[0] = boss;
In questo caso staff[0] e boss fanno riferimento allo stesso oggetto. In ogni caso staff[0]
è considerato un oggetto Employee dal compilatore.
boss.setBonus(5000); // OK
staff[0].setBonus(5000); // ERROR
Inoltre il seguente assegnamento è illegale:
Manager m = staff[i]; // ERROR
La ragione è legata al fatto che non tutti gli impiegati sono Manager. Se questo fosse possibile con
m che punta un Employee allora la chiamata m.setBonus(...) andrebbe a buon fine
Quando un programma invoca un metodo attraverso una variabile di una superclasse la corretta
versione del metodo della sottoclasse viene chiamata in base al tipo di riferimento immagazzinato
nella variabile della superclasse. Lo stesso “metodo e segnatura” può causare differenti azioni in
base all’oggetto attraverso il quale il metodo viene invocato.
Ciò facilita la possibilità di aggiungere nuove classi al sistema con modifiche minimali al codice.
Grazie al polimorfismo il programmatore può gestire gli aspetti generali, lasciando che sia
l’ambiente di esecuzione a gestire i dettagli specifici. Il programmatore può ordinare a un’ampia
varietà di oggetti di comportarsi in modo appropriato , senza dover necessariamente conoscere il
tipo di questi oggetti (basta che appartengano alla stessa gerarchia di ereditarietà).
Un riferimento a una superclasse può essere effettuato attraverso un oggetto sottoclasse. Un
oggetto sottoclasse è un anche un oggetto delle superclasse (is-a). Quando si invoca un metodo da
tale riferimento il tipo dell’effettivo oggetto referenziato, non il tipo del riferimento, determina
quale metodo chiamare.
Un riferimento a un metodo della sottoclasse può essere effettuato dall’oggetto superclasse solo se
dell’oggetto è stato effettuato il downcasting.
3.9.1 Binding dinamico
È importante comp rendere cosa accade ad un oggetto quando si chiama un metodo. Chiamando il
metodo x.f(p) con x istanza della classe C. Se ci sono più metodi con lo stesso nome f ma con
diversi tipi di parametri il compilatore conosce tali metodi della classe C e tutti i metodi public
delle superclassi di C.
Il compilatore determina il tipo di parametro e richiama il metodo corrispondente (risoluzione
dell’overloading). La situazione può diventare più complessa se il compilatore non trova il metodo
con il corrispondente tipo di parametro o se ne trova diversi in tal caso restituirà un errore.
Prima di JDK 5.0 il tipo restituito doveva essere identico. Ora è possibile che la sottoclasse cambi il
tipo restituito. Supponiamo che Employee possiede il metodo
public Employee getBuddy() { ... }
Se la classe Manager fa l’override di questo metodo
public Manager getBuddy() { ... }
// OK in JDK 5.0
Diciamo che i due metodi getBuddy hanno tipi restituiti covarianti. Se il metodo è private,
static, final o è un costruttore il compilatore saprà esattamente qua le metodo chiamare.
Questo è chiamato binding statico. Negli altri casi il binding è dinamico.
Quando il programma è eseguito ed usa il binding dinamico per chiamare un metodo allora la JVM
chiama la versione del metodo appropriato per il tipo effettivo dell’oggetto cui x fa riferimento. Si
supponga che il tipo effettivo sia D sottoclasse di C. Se la classe D definisce il metodo f(String)
il metodo è chiamato, altrimenti il metodo viene ricercato nella superclasse di D e così via.
Questa ricerca potrebbe risultare dispendiosa quindi la JVM stabilisce preventivamente per ciascuna
classe una tabella di metodi
3.9.2 Classi e metodi finali
A volte può essere necessario evitare l’ereditarietà. Le classi che non possono essere estese si
chiamano finali e per implementarle bisogna usare il modificatore final. Esempio:
final class Executive extends Manager {
. . .
}
Inoltre si possono definire anche i metodi finali, in modo tale che questi non possano essere estesi
nelle sottoclassi. Esempio:
class Employee {
. . .
public final String getName() {
return name;
}
. . .
}
3.9.3 Casting
Per definire il cast di un oggetto si usa la stessa sintassi usata per il casting delle espressioni
numeriche. Esempio:
Manager boss = (Manager) staff[0];
L’unica situazione in cui è necessario definire un cast è quando si ha la necessità di utilizzare i
metodi specifici di una classe dopo che il suo tipo effettivo è stato temporaneamente dimenticato.
Esempio:
Manager boss = (Manager) staff[1]; // ERROR
A runtime JAVA nota l’errore e genera un ClassCastException (se non viene catturata
l’applicazione termina). Operatore istanceof:
if (staff[1] instanceof Manager) {
boss = (Manager) staff[1];
. . .
}
3.9.4 Classe Object
La classe Object è l’antenata di tutte le classi JAVA. Ogni classe JAVA è sottoclasse di Object.
È importante capire quali sono i metodi offerti da questa classe. Per esempio si può definire una
variabile di tipo Object e farla riferire ad un oggetto di qualsiasi tipo (eccetto i tipi primitivi):
Object obj = new Employee("Harry Hacker", 35000);
Una variabile Object è utile solo come contenitore generico di valori arbitrari. Per poter utilizzare
in modo specifico il valore bisogna fare il cast:
Employee e = (Employee) obj;
Ricordiamo che in Java, solo i tipi primitivi non sono oggetti.
Tutti i tipi di array sono classi che estendono Object (anche se hanno tipi primitivi). Esempio:
Employee[] staff = new Employee[10];
obj = staff; // OK
obj = new int[10]; // OK
I metodi sono:
-
clone
equals
finalize
getClass
hashCode
notify, notifyAll, wait
toString
Il metodo equals della classe Object verifica se un oggetto può essere considerato uguale ad un
altro. Il metodo equals implementato nella classe Object stabilisce se due riferimenti a oggetti
sono identici. Si può fare anche l’override del metodo.
class Employee {
// . . .
public boolean equals(Object otherObject) {
// a quick test to see if the objects are identical
if (this == otherObject)
return true;
// must return false if the explicit parameter is null
if (otherObject == null)
return false;
// if the classes don't match, they can't be equal
if (getClass() != otherObject.getClass())
return false;
// now we know otherObject is a non-null Employee
Employee other = (Employee) otherObject;
// test whether the fields have identical values
return
name.equals(other.name)
&&
salary
==
hireDay.equals(other.hireDay);
}
other.salary
&&
Il metodo getClass restituisce la classe di un oggetto. Nel momento in cui si definisce il metodo
equals per una sottoclasse prima si chiama equals definito nella superclasse. Se la condizione
non viene verificata allora gli oggetto non possono essere uguali. Se i campi della superclasse sono
uguali si possono confrontare i campi istanza della sottoclasse.
class Manager extends Employee {
. . .
public boolean equals(Object otherObject) {
if (!super.equals(otherObject))
return false;
// super.equals checked that this
//and otherObject belong to the same class
Manager other = (Manager) otherObject;
return bonus == other.bonus;
}
}
Un codice hash è un intero ricavato da un oggetto. I codici hash dovrebbero risultare diversi quando
si applicano su due oggetti diversi. Il metodo hashCode è definito in Object e quindi ogni
oggetto ha un codice hash. Esso deriva dall’indirizzo di memoria dell’oggetto.
Il metodo che restituisce un stringa che rappresenta il valore dell’oggetto è toString.
L’implementazione di default di toString restituisce il nome del package a cui l’oggetto
appartiene concatenata al nome della classe concatenata ad una rappresentazione esadecimale del
valore restituito dal metodo hashCode.
Se prendiamo per esempio il metodo toString della classe Point (reimplementato nella classe
Point) restituisce una stringa simile a:
java.awt.Point[x=10,y=31]
Quasi tutti i metodi toString seguono questo formato: il nome della classe seguito dai valori dei
campi racchiusi tra parentesi quadre. Esempio per Employee:
public String toString() {
return
"Employee[name=" + name +
",salary=" + salary +
",hireDay=" + hireDay + "]";
}
Si può fare di meglio:
public String toString() {
return
getClass().getName() +
"[name=" + name +
",salary=" + salary +
",hireDay=" + hireDay + "]";
}
Chi programma la sottoclasse deve definire un proprio metodo toString ed aggiungere i campi
della sottoclasse. Se la superclasse utilizza getClass().getName() allora la sottoclasse può
chiamare semplicemente super.toString():
class Manager extends Employee {
. . .
public String toString() {
return super.toString() +
"[bonus=" + bonus + "]";
}
}
toString è onnipresente in quanto ogni volta che un oggetto è concatenato con una stringa
mediante l’operatore + il compilatore Java richiama automaticamente il metodo toString per
poter ottenere una rappresentazione stringa dell’oggetto.
Point p = new Point(10, 20);
String message = "The current position is " + p;
// invocherà automaticamente p.toString()
Se x è un oggetto arbitrario:
System.out.println(x)
Richiama automaticamente il metodo x.toString() e stampa la stringa risultante.
3.9.5 Classi astratte
Man mano che si sale nella gerarchia dell’ereditarietà, le classi diventano sempre più generiche e
spesso più astratte. Ad un certo punto la classe superiore diventa a tal punto generica che la si può
pensare come una base per le altre classi piuttosto che come una classe concretamente utilizzabile.
Le classi astratte sono usate come superclassi astratte per sottoclassi “concrete” e per dichiarare
riferimenti a variabili.
Diverse gerarchie di ereditarietà hanno superclassi astratte che occupano i livelli più alti.
Va usata la parola chiave abstract:
•
•
Per dichiarare la classe abstract
Per dichiarare i metodi abstract (solo definiti non implementati)
o Tipicamente le classi abstract contengono uno o più metodi abstract
o Tutte le sottoclassi concrete DEVONO fare l’override di tali metodi
Un impiegato è una persona così come lo è un uno studente. Tale osservazione ci consente di
estendere la gerarchia delle classi per includere le classi Person e Student:
abstract class Person {
public Person(String n) {
name = n;
}
public abstract String getDescription();
public String getName() {
return name;
}
private String name;
}
Le regole sono semplici. Una classe può essere dichiarata abstract anche se non ha metodi
astratti. Non è possibile creare delle istanze di una classe astratta. Esempio:
new Person(“Mario Rossi")
causa un errore. È possibile creare oggetti di sottoclassi concrete. È possibile creare una variabile
oggetto di una classe astratta anche se una variabile di questo tipo deve fare riferimento a un
oggetto di una sottoclasse concreta:
Person p = new Student(“Mario Rossi", “Fisica");
La sottoclasse concreta Student:
class Student extends Person {
public Student(String n, String m) {
super(n);
major = m;
}
public String getDescription() {
return “studente iscritto in " + major;
}
private String major;
}
Altro esempio:
interfacce polimorfiche per questa gerarchia:
public abstract class Employee
{
private String firstName;
private String lastName;
private String socialSecurityNumber;
// three-argument constructor
public Employee( String first, String last, String ssn )
{
firstName = first;
lastName = last;
socialSecurityNumber = ssn;
} // end three-argument Employee constructor
// set first name
public void setFirstName( String first )
{
firstName = first;
} // end method setFirstName
// return first name
public String getFirstName()
{
return firstName;
} // end method getFirstName
// set last name
public void setLastName( String last )
{
lastName = last;
} // end method setLastName
// return last name
public String getLastName()
{
return lastName;
} // end method getLastName
// set social security number
public void setSocialSecurityNumber( String ssn )
{
socialSecurityNumber = ssn; // should validate
} // end method setSocialSecurityNumber
// return social security number
public String getSocialSecurityNumber()
{
return socialSecurityNumber;
} // end method getSocialSecurityNumber
// return String representation of Employee object
public String toString()
{
return String.format( "%s %s\nsocial security number: %s",
getFirstName(), getLastName(), getSocialSecurityNumber() );
} // end method toString
// abstract method overridden by subclasses
public abstract double earnings(); // no implementation here
} // end abstract class Employee
3.9.6 Interfacce
Implementate dalle classi per assegnare funzionalità comuni a classi possibilmente non correlate.
In pratica un modo per descrivere cosa dovrebbero fare le classi senza specificare come farlo. Una
classe può implementare una o più interfacce. È possibile usare gli oggetti di queste classi
ogniqualvolta venga richiesto qual comportamento specificato dall’interfaccia implementata.
In JAVA un’interfaccia non è una classe ma un insieme di requisiti per le classi che si vogliono
conformare a essa.
Chi fornisce un servizio afferma: “il servizio può essere svolto se la classe è conforme a una
determinata interfaccia”:
•
•
•
•
•
•
•
•
La parola chiave è interface
Contiene solo costanti e metodi abstract
Tutti i campi sono implicitamente public, static e final
Tutti i metodi sono implicitamente public abstract
Le classi possono implementare (con la parola chiave implements) le interfacce
La classe deve dichiarare ogni metodo dell’interfaccia usando la stessa segnatura oppure la
classe deve essere dichiarata abstract
Tipicamente usate quando classi diverse devono condividere metodi comuni e vincoli
Normalmente dichiarate in file separati con lo stesso nome dell’interfaccia e con
l’estensione .java
È opportuno dichiarare i metodi delle interfacce senza la parola chiave public e abstract in
quanto sono ridondanti. In modo analogo i vincoli dovrebbero essere dichiarati senza le public,
static e final in quanto risulterebbero anche esse ridondanti.
Non implementare in una classe concreta che implementa l’interfaccia un metodo dell’interfaccia
provoca un errore di sintassi, in tal caso la classe deve essere dichiarata abstract.
Esempio: sviluppare una gerarchia Payable
•
•
Contiene il metodo getPaymentAmount
Viene implementata dalle classi Invoice ed Employee.
public interface Payable{
double getPaymentAmount();
// calculate payment; no implementation
}
// end interface Payable
public class Invoice implements Payable
{
private String partNumber;
private String partDescription;
private int quantity;
private double pricePerItem;
// four-argument constructor
public Invoice( String part, String description, int count,
double price )
{
partNumber = part;
partDescription = description;
setQuantity( count ); // validate and store quantity
setPricePerItem( price ); // validate and store price per item
} // end four-argument Invoice constructor
...
...
// method required to carry out contract with interface Payable
public double getPaymentAmount()
{
return getQuantity() * getPricePerItem(); // calculate total cost
} // end method getPaymentAmount
}
public abstract class Employee implements Payable
{
private String firstName;
private String lastName;
private String socialSecurityNumber;
// three-argument constructor
public Employee( String first, String last, String ssn )
{
firstName = first;
lastName = last;
socialSecurityNumber = ssn;
} // end three-argument Employee constructor
...
// Note: We do not implement Payable method getPaymentAmount here so
// this class must be declared abstract to avoid a compilation error.
} // end abstract class Employee
public class SalariedEmployee extends Employee
{
private double weeklySalary;
// four-argument constructor
public SalariedEmployee( String first, String last, String ssn,
double salary )
{
super( first, last, ssn ); // pass to Employee constructor
setWeeklySalary( salary ); // validate and store salary
} // end four-argument SalariedEmployee constructor
// set salary
public void setWeeklySalary( double salary )
{
weeklySalary = salary < 0.0 ? 0.0 : salary;
} // end method setWeeklySalary
// return salary
public double getWeeklySalary()
{
return weeklySalary;
} // end method getWeeklySalary
// calculate earnings; implement interface Payable method that was
// abstract in superclass Employee
public double getPaymentAmount()
{
return getWeeklySalary();
} // end method getPaymentAmount
// return String representation of SalariedEmployee object
public String toString()
{
return String.format( "salaried employee: %s\n%s: $%,.2f",
super.toString(), "weekly salary", getWeeklySalary() );
} // end method toString
} // end class SalariedEmployee
Una classe può implementare tutte interfacce che ha bisogno. Si utilizza la virgola per elencare le
interfacce. Esempio:
public
class
ClassName
extends
FirstInterface, SecondInterface, …
SuperclassName
implements
Esempi di interfaccia: Comparable<T>
Il metodo sort della classe Arrays afferma di ordinare un array di oggetti a condizione che gli
“oggetti appartengano a classi che implementano l’interfaccia Comparable”
public interface Comparable {
int compareTo(Object other);
}
Per fare in modo che la classe implementi l’interfaccia comparable bisogna dire alla classe di
implementare l’interfaccia:
class Employee implements Comparable
e implementare il metodo dell’interfaccia
public int compareTo(Object
(Employee) otherObject;
otherObject)
{
Employee
other
=
if (salary < other.salary)
return -1;
if (salary > other.salary)
return 1;
return 0;
}
A partire da JDK 5.0 l’ interfaccia Comparable è stata migliorata prevedendo che possa essere di
tipo generico:
public interface Comparable<T> {
int compareTo(T other); // parameter has type T
}
3.9.7 Interfacce API
Interfacce della Java API:
•
•
•
•
•
Comparable
Serializable
Runnable
GUI event-listener
Swinghconstants
3.10 Gestione delle eccezioni
La gestione degli errori che possono presentarsi nel flusso del codice va effettuata nel posto dove
questi possono presentarsi. La gestione delle Eccezioni rende i programmi robusti e fault-tolerant.
Problemi tipici:
•
•
•
•
Esaurimento memoria
Array index “out of bounds”
Divisioni per zero
Parametri dei metodi invalidi
La gestione delle eccezioni avviene:
•
•
Catturarando gli errori prima che questi si presentino
Gestendo gli errori sincroni (es., divisione per zero)
Le eccezioni devono essere gestite per fare il recovery da un errore. Il gestore delle eccezioni
implementa la procedura di recovery.
Uso della gestione delle eccezioni:
•
•
•
Processare le eccezioni nei moduli del programma
Gestire le eccezioni in un modo uniforme
Consente di rimuovere il codice della gestione degli errori dalla “main line” dell’esecuzione
Un metodo trova un errore e scatena (throws) un’eccezione :
•
•
Il gestore dell’eccezione processa l’errore
Eccezioni non catturate possono avere effetti diversi
o Possono causare la terminazione del programma
Non dovrebbe essere usata per il controllo di flusso del programma:
•
•
Non è ottimizzata, può avere effetti sulle performance
Nasce esclusivamente per migliorare la fault-tolerance del programma
Dovrebbe essere usata invece per:
•
•
•
3.10.1
Gestire situazioni eccezionali
Processare eccezioni scatenate da componenti che non controllano in maniera diretta
l’eccezione
Gestire le eccezioni per componenti ampiamente usate (librerie, classi, metodi) che non le
gestiscono direttamente
o Grossi progetti richiedono una gestione uniforme delle eccezioni
Eccezioni in JAVA
La gestione può essere suddivisa in:
•
•
•
Il metodo identifica un’eccezione che non può gestire
o Scatena un’eccezione (throws)
Il gestore dell’eccezione
o Codice per catturare (catch) l’eccezione e gestirla
L’eccezione è catturata solo se esiste il gestore
o Se l’eccezione non è catturata il blocco di codice termina
La sintassi può essere riassunta così:
o Racchiudere il codice che può avere un errore in un blocco try
o Farlo seguire da uno o più blocchi catch
o Ogni blocco catch contiene un gestore dell’eccezione (exception handler)
o Se l’eccezione si presenta ed essa coincide con una di quelle specificate nel blocco catch
o Il codice del blocco catch è eseguito
o Se non viene generata nessuna eccezione
o Il codice di gestione è saltato
o Il controllo ritorna dopo il blocco catch
try{
code that may throw exceptions
}
catch (ExceptionType ref) {
exception handling code
}
Modello di terminazione :
o throw point
o Posizione dove l’eccezione si verifica
o Il controllo non può tornare nel throw point
o Il blocco che causa l’eccezione termina
o Ha la possibilità di dare delle informazioni al gestore dell’eccezione
Esempio (Divisione per zero):
o L’utente inserisce due interi che devono essere divisi
o Desideriamo catturare l’errore dovuto alla divisione per zero
o Eccezioni
o Oggetti derivati dalla classe Exception
o Classi Exception in java.lang
o Nulla di appropriato per la divisione per zero
o La più vicina è ArithmeticException
o Estendiamo la classe ArithmeticException e creiamo una nostra classe
public class DivideByZeroException
extends ArithmeticException {
public DivideByZeroException()
public DivideByZeroException( String message )
o Due costruttori per la maggior parte delle classi exception
o Uno senza argomenti (default), con un messaggio di default
o Uno che riceve un messaggio
o Chiama il costruttore della superclasse
o Codice che può scatenare l’eccezione deve essere inserito in un blocco try
o Codice di gestione dell’errore nel blocco catch
o Se non si verifica l’eccezione il blocco catch viene saltato
// Fig. 4.1: DivideByZeroException.java
// Definition of class DivideByZeroException.
// Used to throw an exception when a
// divide-by-zero is attempted.
public class DivideByZeroException
extends ArithmeticException {
\\Definiamo la nostra classe (le eccezioni sono oggetti catturati).
\\Costruttore di default (messaggio di default) e costruttore con
\\messaggio personalizzato
public DivideByZeroException()
{
super( "Attempted to divide by zero" );
}
public DivideByZeroException( String message )
{
super( message );
}
}
Test di divisione per zero:
// Fig. 4.1: DivideByZeroTest.java
// An exception-handling example that checks for divide-by-zero.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DivideByZeroTest extends JFrame
implements ActionListener {
private JTextField inputField1, inputField2, outputField;
private int number1, number2, result;
// set up GUI
public DivideByZeroTest()
{
super( "Demonstrating Exceptions" );
// get content pane and set its layout
Container container = getContentPane();
container.setLayout( new GridLayout( 3, 2 ) );
// set up label and inputField1
container.add(
new JLabel( "Enter numerator ", SwingConstants.RIGHT ) );
inputField1 = new JTextField();
container.add( inputField1 );
// set up label and inputField2; register listener
container.add( new JLabel( "Enter denominator and press Enter ",
SwingConstants.RIGHT ) );
inputField2 = new JTextField();
container.add( inputField2 );
inputField2.addActionListener( this );
// set up label and outputField
container.add( new JLabel( "RESULT ", SwingConstants.RIGHT ) );
outputField = new JTextField();
container.add( outputField );
setSize( 425, 100 );
setVisible( true );
} // end DivideByZeroTest constructor
// process GUI events
public void actionPerformed( ActionEvent event )
{
outputField.setText( "" );
// clear outputField
// read two numbers and calculate quotient
try { \\Il blocco try Se viene scatenata un’eccezione l’intero blocco
è terminato.
number1 = Integer.parseInt( inputField1.getText() );
number2 = Integer.parseInt( inputField2.getText() );
public void actionPerformed( ActionEvent e )
{
DecimalFormat precision3 = new DecimalFormat( "0.000" );
output.setText( "" ); // empty the output JTextField
try {
number1 = Integer.parseInt( input1.getText() );
number2 = Integer.parseInt( input2.getText() );
result = quotient( number1, number2 );
output.setText( precision3.format( result ) );
}
catch ( NumberFormatException nfe ) {
JOptionPane.showMessageDialog( this,
"You must enter two integers",
"Invalid Number Format",
JOptionPane.ERROR_MESSAGE );
}
catch ( DivideByZeroException dbze ) {
JOptionPane.showMessageDialog( this, dbze.toString(),
"Attempted to Divide by Zero",
JOptionPane.ERROR_MESSAGE );
}
Esempio di divisione per zero:
public double quotient( int numerator, int denominator )
throws DivideByZeroException
{
if ( denominator == 0 )
throw new DivideByZeroException();
return ( double ) numerator / denominator;
}
Il metodo quotient “throws” l’eccezione DivideByZeroException se denominator
== 0.
Per catturare un’eccezione bisogna usare i blocchi try/catch.
o try contiene il codice che può causare l’errore;
o catch contiene il codice per la gestione dell’eccezione;
Esiste un terzo blocco chiamato finally.
Modello di terminazione :
o Il blocco dove si verifica l’eccezione termina
o La clausola throws (scatena) consente di specificare l’eccezione che il metodo deve
scatenare.
3.10.2
Il blocco try
Le eccezioni si gestiscono dentro un blocco try
Usualmente catturano una specifica eccezione nel blocco catch
try{
code that may throw exceptions
}
catch ( ExceptionType ref ) {
exception handling code
}
Si possono avere diversi blocchi catch. Se non viene scatenata nessuna eccezione il blocco catch
viene saltato.
3.10.3
Scatenare eccezioni: throw
Indica che l’eccezione è accaduta e scatena un’eccezione. E’ un operatore unario, l’operando è
oggetto di una classe derivata da Throwable (la maggior parte delle eccezioni con cui i
programmatori interagiscono):
if ( denominator == 0 )
throw new DivideByZeroException();
Quando un eccezione viene scatenata:
o Si esce dal blocco corrente e
o Si procede con il corrispondente blocco catch (se esiste)
Le eccezioni possono essere scatenata anche senza l’esplicito comando throw. Per esempio
ArrayIndexOutOfBoundsException termina il blocco e non richiede di terminare il
programma.
Throws specifica la lista delle eccezioni che devono essere scatenata dal metodo
int g( float h ) throws a, b, c
{
// method body
}
public double quotient( int numerator, int denominator )
throws DivideByZeroException
Il metodo può scatenare le eccezioni elencate o quelle di tipo derivato:
o Eccezioni a Run-time
o Derivate da RunTimeException
o Alcune eccezioni si possono presentare in qua lsiasi punto
o ArrayIndexOutOfBoundsException
o NullPointerException
§ Creare un oggetto riferimento senza collegare il riferimento
o ClassCastException
§ Cast non valido
o Sono evitabili (nella maggior parte dei casi) scrivendo il codice appropriato
Eccezioni contro llate:
o Devono essere elencate nella clausola throws del metodo
o Tutte quelle che non sono RuntimeException
Eccezioni non controllate :
o Possono essere scatenate da quasi tutti i metodi
o Scrivere la clausola throws è molto noioso
o Non è necessario scrivere la clausola throws
o Errori e RunTimeException:
Catch-or-declare requirement:
o Se un metodo chiama un altro metodo che in modo esplicito scatena un’eccezione
controllata
o Le eccezioni devono essere scatenate nel metodo che contiene la clausola throws
o Altriment i il metodo originale deve catturare l’eccezione
o Il metodo deve quindi o catturare l’eccezione o dichiarare la clausola throws
3.10.4
Catturare l’eccezione: catch
Codice di gestione eccezione. Formato:
catch( ExceptionType ref ) {
error handling code
}
catch ( DivideByZeroException dbze ) {
JOptionPane.showMessageDialog( this, dbze.toString(),
"Attempted to Divide by Zero",
JOptionPane.ERROR_MESSAGE );
}
Per catturare tutti i tipi di eccezione bisogna catturare l’oggetto exception:
catch( Exception e )
Cosa succede? Il primo gestore cattura l’eccezione, tutti gli altri sono saltati e se l’eccezione non è
catturata cerca ne nei blocchi try più esterni il gestore appropriato:
try{
try{
throw Exception2
}
catch ( Exception1 ){...}
}
catch( Exception2 ){...}
Se non riesce a catturarla, l’applicazione (non basata su GUI) termina. L’informazione può essere
passata nell’oggetto thrown e nelle variabili istanza : se un blocco catch scatena un’eccezione,
l’eccezione deve essere processata in un apposito blocco try
3.10.5
Costruttori, Finalizzatori
Cosa fare con un errore in un costruttore? Come informare l’utente? Si scatenano eccezioni per
informare il programma che il costruttore ha fallito (oggetto marcato per il garbage collector
=> metodo finalize)
3.10.6
Ereditarietà
Le classi Exception hanno una superclasse comune:
catch ( Superclass ref )
o Cattura le eccezioni delle sottoclassi
o Relazione "Is a"
Il processing è polimorfico ed è più semplice catturare la superclasse che ogni sottoclasse.
3.10.7
Il blocco finally
Viene inserito dopo l’ultimo blocco catch e può essere usato per rilasciare le risorse allocate nel
blocco try; esso viene sempre eseguito e se un’eccezione viene scatenata nel blocco finally
deve essere processata con il relativo try/catch:
// Fig. 4.9: UsingExceptions.java
// Demonstration of stack unwinding.
public class UsingExceptions {
public static void main( String args[] )
{
try {
throwException();
}
catch ( Exception e ) {
System.err.println( "Exception handled in main" );
}
}
public static void throwException() throws Exception
{
// Throw an exception and catch it in main.
try {
System.out.println( "Method throwException" );
throw new Exception();
// generate exception
}
catch( RuntimeException e ) { // nothing caught here
System.err.println( "Exception handled in " +
"method throwException" );
}
finally {
System.err.println( "Finally is always executed" );
}
}
}
/**************************************************************************
* (C) Copyright 1999 by Deitel & Associates, Inc. and Prentice Hall.
*
* All Rights Reserved.
*
*
*
**************************************************************************/
3.10.8
printStackTrace e getMessage
La classe Throwable è la superclasse di tutte le eccezioni.
printStackTrace stampa lo stack per l’eccezione Exception ed è un metodo più recente nel
top dello stack, molto utile per test e debugging.
Costrut tori:
Exception()
Exception( String informationString )
L’informazione si può ottenere con il metodo getMessage.
Gerarchia di ereditarietà per la classe Throwable:
Throwable
Exception
RuntimeException
Error
IOException
AWTError
ThreadDeath
Gerarchia delle eccezioni in Java:
Superclasse Throwable
o Sottoclasse Exception
o Devono essere catturate dal programma
o sottoclasse Error
o Tipicamente non catturate da programma
// Fig. 4.10: UsingExceptions.java
// Demonstrating the getMessage and printStackTrace
// methods inherited into all exception classes.
public class UsingExceptions {
public static void main( String args[] )
{
OutOfMemoryError
try {
method1();
}
catch ( Exception e ) {
System.err.println( e.getMessage() + "\n" );
e.printStackTrace();
}
}
public static void method1() throws Exception
{
method2();
}
public static void method2() throws Exception
{
method3();
}
public static void method3() throws Exception
{
throw new Exception( "Exception thrown in method3" );
}
}
/**************************************************************************
* (C) Copyright 1999 by Deitel & Associates, Inc. and Prentice Hall.
*
* All Rights Reserved.
*
*
*
**************************************************************************/
3.11 Esercizi
Esercizio A
Creare una classe Rectangle. Questa classe ha gli attributi length e width inizializzati ad 1 per
default. La classe ha due metodi che calcolano il perimetro e l’area. Possiede inoltre i metodi set e
get per length e width. I metodi set e get devono verificare che length e width abbiano valore in
virgola mobile compreso tra 0.0 e 20.0.
Creare anche una classe di test per Rectangle.
Esercizio B
Creare una classe Rational per effettuare operazioni matematiche su frazioni. Rappresentare i dati
private della classe con variabili intere (numerator e denominator). Il costruttore deve inizializzare
ogni oggetto della classe quando viene dichiarato e deve contenere dei valori di default, nel caso
non siano forniti inizializzatori. Il costruttore deve memorizzare i valori delle frazioni in forma
ridotta (2/4 deve essere rappresentato come 1/2). Fornire dei metodi public per le seguenti
operazioni:
•
•
•
•
•
Addizione di due numeri Rational;
Sottrazione di due numeri Rational;
Moltiplicazione di due numeri Rational;
Divisione di due numeri Rational;
Vusualizzazione dei numeri rational (a/b);
•
•
Visualizzazione in virgola mobile;
Creare una classe di test per Rational.
Esercizio C
Implementare la classe Sorter. Questa classe deve contenere algoritmi di ordinamento che operano
sui numeri interi e sui numeri a virgola mobile.
Utilizzare il concetto di overloading nella definizione dei metodi. Inoltre definire i metodi come
static. Creare una classe di test per Sorter.
4 JAVA: Package
Java contiene molte classi predefinite, che vengono raggruppate in categorie di classi correlate e
prendono il nome di package.
L’insieme di questi package è chiamato Java API (Java Application Programmin Interface).
Per specificare le classi necessarie per compilare un programma Java all’interno del testo vengono
utilizzate le dichiarazioni import, la cui sintassi generale è
import <package>.<Classe>;
Per esempio per specificare che un programma usa la classe Scanner del package java.util
viene usata la dichiarazione
import java.util.Scanner;
mentre per specificare tutte le classi del package java.util viene usata la seguente dichiarazione
import java.util.*;
come abbiamo potuto notare negli esempi trattati nel capitolo precedente.
Uno dei principali punti di forza di Java è l’enorme quantità di classi e package che i programmatori
possono riutilizzare e che ormai camminano come corredo base dell’ambiente di sviluppo.
L’insieme dei package disponibili nel J2SE Development Kit (JDK) è piuttosto vasto; i package più
utilizzati e noti sono
•
•
•
•
•
•
java.lang: Questo package contiene le classi e le interfacce richieste a tutti i programmi
Java. Questo viene automaticamente importato dal compilatore in tutti i programmi quindi
non è necessario importarlo esplicitamente;
java.util: Questo package contiene classi ed interfacce di utilità ad esempio per la
manipolazione di data ed ora, per la generazione casuale di numeri (Random), ecc.;
java.io: Il package contiene classi per la gestione degli inpu e output dei programmi;
java.net: Contiene le classi che permettono ai programmi Java di comunicare attraverso il
networking;
java.applet: Contiene la classe Applet e varie interfacce che consentono l’interazione delle
applet con il browser e la riproduzione di clip audio;
java.text: Classi ed interfacce per manipolare numeri, date, caratteri e stringhe. Questo
package fornisce molte delle capacità di internazionalizzazione di Java, funzionalità che
permettono ai programmi di essere configurati in base a una specifica localizzazione;
•
•
•
•
java.math: Classi per la gestione di gradi numeri ;
java.awt: Absract Windows Toolkit, contiene le classi e le interfacce necessarie per creare e
manipolare le interfacce utente grafiche (GUI);
javax.swing : Contiene le classi e le interfacce per i componenti GUI Java Swing.
java.swing.event : Contiene classi e interfacce che permettono la gestione degli eventi con i
componenti GUI del package javax.swing
Oltre ai package elencati, JDK contiene dei package per la gestione grafica complessa, interfacce
utente avanzate, stampa, rete, sicurezza, database, multimedia, etc. Per una descrizione completa dei
package: http://java.sun.com/j2se/1.5.0/docs/api/overview-summary.html.
4.1.1 Importazione statica
L’importazione statica permette di riferire i membri statici della classe, consentendo di evitare la
notazione puntata. Sintassi:
Import static nomePackage.NomeClasse.membroStatic;
4.1.2 Creazione di package
Ogni classe e ogni interfaccia della Java API appartiene a un package specifico contenente classi ed
interfacce legate tra loro. I package aiutano nel processo di riutilizzo del software. Inoltre
attribuiscono un nome univoco alle classi.
Prima che una classe possa essere importata in diverse applicazioni questa deve essere messa in un
package. I passi sono i seguenti:
•
•
•
•
Dichiarare una classe public, se non è public potrà essere usata soltanto dalle altre
classi di uno stesso package;
Scegliere un nome per il package e aggiungere una dichiarazione package al file del codice
sorgente della dichiarazione della classe riutilizzabile;
Compilare in modo opportuno affinché venga inserita nella appropriata struttura di directory
del package: javac –d . nomefile.java
Importare la classe in un programma ed utilizzarla.
I nomi dei package dovrebbero essere significativi. Per esempio nel caso di un package Java
sviluppato presso il corso di laurea in Fisica il nome del package dovrebbe partire dal sito web
www.ct.infn.it/csdaf, il package dovrebbe essere it.infn.ct.csdaf.nomePackage.
4.1.3 Rintracciare le classi: classpath
Il caricatore delle classi inizia cercando le classi standard del JDK e quindi i package facoltativi.
JAVA fornisce un meccanismo di estensione che permette di aggiungere nuovi package come
supporto allo sviluppo e all’esecuzione.
Se la classe non viene trovata tra le classi standard il caricatore cerca nel classpath contenente
una lista di percorsi in cui le classi possono essere memorizzate.
Il classpath contiene una lista di percorsi di directory o di file archivio (jar o zip) separati da un
separatore di directory.
Per default il classpath consiste solo nella directory corrente ma può essere impostato in modo
opportuno:
•
•
Con l’opzione –classpath al compilatore javac (impostazione consigliata);
Impostando la variabile d’ambiente CLASSPATH;
Quando si esegue un’applicazione bisogna garantire a JAVA il modo di localizzare i package.
Anche a runtime possono essere usati i due modi di localizzazione:
•
•
-classpath o –cp
La variabile d’ambiente CLASSPATH
4.2 Package math
Se la precisione dei tipi di base integer e floating-point types non è sufficiente, possono essere usati
due classi del package java.math: BigInteger e BigDecimal, che consentono di
manipolare numeri con sequenze arbitrarie di elementi.
La classe BigInteger implementa un’aritmetica di interi a precisione arbitraria.
La classe BigDecimal fa la stessa cosa per i numeri di tipo floating-point.
L’uso del metodo static valueOf trasforma un numero normale in un grande numero:
BigInteger a = BigInteger.valueOf(100);
Sfortunatamente non possono essere usati gli operatori di base dell’aritmetica per lavorare con i
grandi numeri. Bisogna utilizzare dei metodi specifici presenti nelle rispettive classi:
BigInteger
BigInteger
BigInteger
BigInteger
BigInteger
add(BigInteger other)
subtract(BigInteger other)
multiply(BigInteger other)
divide(BigInteger other)
mod(BigInteger other)
ritornano somma, differenza, prodotto, quoziente, resto; invece
int compareTo(BigInteger other)
ritorna 0 se il grande numero è uguale al grande numero other, un valore negativo se il grande
numero è minore di other, un numero positivo altrimenti.
static BigInteger valueOf(long x)
Per esempio:
BigInteger c = a.add(b); // c = a + b
BigInteger d = c.multiply(b.add(BigInteger.valueOf(2))); // d = c
* (b + 2)
Per quanto riguarda la classe BigDecimal invece:
BigDecimal
BigDecimal
BigDecimal
BigDecimal
add(BigDecimal other)
subtract(BigDecimal other)
multiply(BigDecimal other)
divide(BigDecimal other, RoundingMode mode) 5.0
ritornano somma, differenza, prodotto, quoziente, resto. Per calcolare il resto bisogna dare il metodo
di arrotondamento. Il RoundingMode.HALF_UP è il metodo classico di arrotondamento
(vedere le API per la documentazione sui metodi di arrotondamento). Inoltre
int compareTo(BigDecimal other)
Ritorna 0 se il grande numero è uguale al grande numero other, un valore negativo se il grande
numero è minore di other, un numero prositivo altrimenti.
static BigDecimal valueOf(long x)
static BigDecimal valueOf(long x, int scale)
ritornano un grande numero il cui valore è uguale rispettivamente a x o a x/10 scale.
4.3 Package IO
Il package java.io fornisce classi, interfacce ed eccezioni per gestire l’input e l’output dei
programmi. Il package mette a disposizione una serie di classi per trattare i file dal punto di vista
del S.O. e un’altra serie per gestire la lettura e la scrittura di dati sui file.
Uno stream è un’astrazione che produce o consuma informazioni. Uno stream è collegato a un
device fisico (monitor, tastiera…). Tutti gli stream si comportano allo stesso modo, anche se il
device fisico a cui sono collegati è diverso. In questo modo, le stesse classe I/O e metodi possono
essere applicate ad ogni tipo di device.
Per esempio, gli stessi metodi possono essere usati per scrivere nella console o un su un file di
disco.
Gli Stream rappresentano flussi sequenziali di byte e vengono suddivisi in base all’utilizzo che si
intende farne. In Java alla base troviamo quattro classi astratte:
1.
2.
3.
4.
InputStream
OutputStream
Reader
Write
Queste fanno capo al package java.io. Esso distingue due gerarchie di classi per la gestione
degli stream:
•
byte stream (8 bit - byte)
•
character stream (16bit - char)
Gerarchie di stream:
Gerarchie Reader e Writer:
In Java è possibile gestire i dati in UNICODE (16 bit). Per leggere e scrivere testo UNICODE
vanno utilizzate classi derivate da Reader e Writer.
4.3.1 Classi Byte Stream
Segue l’elenco delle classi byte stream:
BufferedInputStream
BufferedOutputStream
ByteArrayInputStream
ByteArrayOutputStream
DataInputStream
DataOutputStream
FileInputStream
FileOutputStream
FilterInputStream
FilterOutputStream
InputStream
ObjectInputStream
ObjectOutputStream
Buffered input stream
Buffered output stream
Legge da un array di byte
Scrive su un array di byte
Leggere i tipi standard di Java
Scrive tipi standard di Java
Legge da un File
Scrive su un File
Implementa InputStream
Implementa OutputStream
Classe astratta che descrive uno stream in input
Input stream per oggetti
Output stream per oggetti
OutputStream
PipedInputStream
PipedOutputStream
PrintStream
PushbackInputStream
RandomAccessFile
SequenceInputStream
Tipo di I/O
Memoria
Pipe
File
Data
Conversion
Buffering
Filtering
Conversione
Byte - Char
Classe astratta che descrive stream in output
Input pipe
Output pipe
Output stream che contiene print( ) e println( )
Input stream che permette al byte di tornare nello stream
Supporta random access file I/O
Input stream che è la combinazione di due o più input stream
che saranno letti in modo sequenziale
Classe
Descrizione
CharArrayReader
Lo stream coincide con un array
CharArrayWriter
ByteArrayInputStream
ByteArrayOutputStream
StringReader
StringWriter
PipedReader
PipedWriter
PipedInputStream
PipedOutputStream
Lo stream è una stringa
FileReader
FileWriter
FileInputStream
FileOutputStream
DataInputStream
DataOutputStream
Lo stream è un File
Lo stream coincide con una pipe. Le pipes sono
canali di collegamento tra l’output di un thread e
l’input di un altro thread
Convertono i dati da/in un formato indipendente
dalla macchina
BufferedReader
La lettura (scrittura) è bufferizzata, migliorando
BufferedWriter
le prestazioni.
BufferedInputStream
BufferedOutputStream
FilterReader
I dati vengono filtrati in base ad un filtro
FilterWriter
FilterInputStream
FilterOutputStream
InputStreamReader
OutputStreamWriter
4.3.2 Input e Output su byte stream
InputStream e OutputStream consentono la lettura e scrittura di stream di byte. I metodi
definiti da queste due classi astratte sono disponibili a tutte le loro sottoclassi. Essi formano un
insieme minimale di funzioni I/O che tutti gli stream di byte hanno.
I metodi in InputStream e OutputStream possono generare eccezioni di tipo
IOException. La classe InputStream consente di accedere ad uno stream di input in modo
astratto. Questa classe non ha metodi per leggere numeri o stringhe, ma può leggere soltanto singoli
byte. I metodi disponibili sono:
public
public
public
public
public
public
void close()
int available()
long skip(long n)
abstract int read()
int read(byte buffer[])
int read(byte buffer[], int offset, int length)
La classe Reader si comporta analogamente. I metodi disponibili sono:
public
public
public
public
public
public
void close()
int available()
long skip(long n)
abstract int read()
int read(char buffer[])
int read(char buffer[], int offset, int length)
Analogamente le classi OutputStream e Writer consentono di accedere ad uno stream di
output in modo astratto. I metodi disponibili per OutputStream sono:
public void close()
public int flush()
public abstract void write(int)//scrive un byte non c’e’ //bisogno
di fare un cast.
public void write(byte buffer[])
public void write(byte buffer[], int offset, int length)
I metodi disponibili per Writer sono:
public void close()
public int flush()
public abstract void write(int)
public void write(char c[]) //scrive un array di caratteri
public void write(char c[], int offset, int length)
//scrive lenght caratteri di c[] iniziando da offeset
public void write(String str)
public void write(String str, int offset, int length)
4.3.3 Classe System
Il Sistema dispone di alcune risorse, che vengono messe a disposizione dell’utente tramite
l’ambiente. Java fornisce la classe System per accedere a tali risorse.
Programma Java
Classe System
Ambiente Run-Time
Tutti i programmi Java automaticamente importano java.lang package. Questo pacchetto
definisce una classe System, la quale contiene diversi aspetti dell’ambiente in run-time. Per
esempio essa contiene 3 variabili di stream predefiniti: in, out, e err.
System.in e’ un oggetto di tipo InputStream;
System.out e System.err sono oggetti di tipo PrintStream.
public static final InputStream in
public static final PrintStream out
public static final PrintStream err
Questi sono byte stream, anche se sono tipicamente usati per leggere e scrivere caratteri da e nella
console. Essi sono byte e no n character stream perché gli stream predefiniti erano parti delle
specifiche originali di Java, che non conteneva ancora character streams. E’ possibile fare il wrap
di questi in stream di caratteri.
System.out e System.err si comportano allo stesso modo. I metodi più comuni sono:
print()
println()
write()
I primi due consentono di scrivere stringhe, mentre il terzo viene utilizzato per l’output di byte. Allo
standard input si accede tramite System.in. Diversamente da System.out, lo standard input
non prevede la gestione dei vari formati dei dati. Le conversioni devono essere fatte in modo
esplicito.
System.in è un’istanza di InputStream, quindi si ha accesso automaticamente a tutti i
metodi definiti da InputStream, che definisce un solo metodo di input read(). Ci sono 3
versioni di read():
int read( ) throws IOException
//legge un singolo carattere. Ritorna -1 quando incontra la fine dello stream.
int read(byte data[ ]) throws IOException
// Legge byte dal input stream e li mette in data fino a quando o l’array e’
pieno o si e’ raggiunto la fine dello stream, o vi e’ stato un errore. Torna il
num di bytes letti o -1 se si e’ alla fine dello stream.
int read(byte data[ ], int start, int max) throws IOException
//Legge input in data iniziando da start. Vengono immagazzinati fino a
byte. Torna il num di bytes letti o -1 se si e’ alla fine dello stream.
max
Tutti i metodi generano IOException se si verifica un errore. Quando legge da System.in,
premendo ENTER si genera una condizione di fine stream. Esempio:
// Read an array of bytes from the keyboard.
import java.io.*;
class ReadBytes {
public static void main(String args[])
throws IOException {
byte data[] = new byte[10];
System.out.println("Enter some characters.");
System.in.read(data);
System.out.print("You entered: ");
for(int i=0; i < data.length; i++)
System.out.print((char) data[i]);
}
}
System.out e’ un byte stream. Console output è attuata con print() and println().
Come avevamo detto questo metodi sono definiti dalla classe PrintStream che è un output
stream derivato da OutputStream, quindi implementa metodo write().
void
write(int byteval) throws IOException
Non si usa spesso write() ma si preferiscono print() and println().
4.3.4 File
I file sono sequenze ordinate di byte memorizzati su unità di memorizzazione di massa. In generale,
le funzionalità di accesso ai file prevedono due meccanismi di base:
•
•
accesso sequenziale (stream)
accesso diretto (random)
Al primo tipo appartengono anche oggetti in grado di gestire flussi di dati non legati direttamente ai
file, come nel caso degli standard I/O. I file sono suddivisibili in due categorie:
•
•
File di Testo
File Binari
I File di Testo sono costituiti da caratteri dell’alfabeto, raggruppati in parole e righe. Sono
visualmente interpretabili dall’utente.
I File Binari sono costituiti da byte di qualunque valore. La struttura interna è interpretabile solo
tramite un apposito programma di gestione.
Gli stream di Standard Input e Standard Output prima descritti sono due casi particolari di file. Il
Sistema Operativo tratta tutti gli stream come casi particolari di File. Java fornisce un’astrazione del
concetto di File, semplificandone l’utilizzo. In Java i file, come gli stream, sono gestiti tramite le
classi presenti nella libreria standard java.io.
La prima operazione da fare consiste nell’aprire (open) il file. Questa operazione informa il S.O.
che vogliamo accedere a quel determinato file. Al termine di tutte le operazioni il file deve essere
chiuso (close) per rilasciare le risorse impegnate e svuotare eventuali buffer.
4.3.4.1 Leggere un File usando un byte stream
Per creare un byte stream collegato a un file, si usa FileInputStream o
FileOutputStream. Per aprire un file si crea un oggetto di queste classi specificando il nome
del file come argomento del costruttore. Dopo aver aperto con successo il file allora si può leggere o
scrivere nel file.
FileInputStream(String fileName) throws FileNotFoundException
Per leggere da un file si usa read():
int read( ) throws IOException
read() legge un singolo byte dal file e lo ritorna come un intero. Ritorna –1 quando si incontra la
fine del file; dà IOException quando si verifica un errore. Quando il file non serve più bisogna
chiuderlo close():
void close( ) throws IOException
Esempio:
import java.io.*;
class ShowFile {
public static void main(String args[])throws IOException{
int i;
FileInputStream fin;
try {
fin = new FileInputStream(args[0]);
} catch(FileNotFoundException exc) {
System.out.println("File Not Found");
return;
} catch(ArrayIndexOutOfBoundsException exc) {
System.out.println("Usage: ShowFile File");
return;
}
// read bytes until EOF is encountered
do {
i = fin.read();
if(i != -1) System.out.print((char) i);
} while(i != -1);
fin.close();
}
}
Notiamo che read non ha un valore speciale di ritorno nel caso si verifichi un errore. Questo è
dovuto alla manipolazione degli errori in Java usando le eccezioni.
4.3.4.2 Scrivere su un File usando un byte stream
Si apre un file di output creando un oggetto FileOutputStream.
FileOutputStream(String fileName) throws FileNotFoundException
Ogni file con lo stesso nome viene distrutto.
FileOutputStream(String fileName, boolean append) throws
FileNotFoundException
Appende, se append è true, i dati alla fine del file. Altrimenti il file è sovrascritto.
Per scrivere su un file si usa write( )
void write(int byteval) throws IOException
Si scrive byteval nel file. Anche se e’ definito come un intero, nel file vengono scritti i low-order 8
bit. Se si verifica un errore si genera un IOException.
Quando un file di output viene creato non è detto che l’output sia immediatamente scritto nel file.
Output può essere bufferizzato (buffered) fino a quando una dimensione opportuna è raggiunta e
può essere scritta in una sola volta. Questo migliora l’efficienza del sistema. Per esempio i file
vengono scritti quando un intero settore puo’ essere scritto in una sola volta.
Comunque si può forzare che i dati vengono scritti nel divice fisico usando flush():
void flush( ) throws IOException
Alla fine il file di output va chiuso close( )
void close( ) throws IOException
4.3.4.3 Input sequenziale da file
Come abbiamo detto la classe FileInputStream estende la classe InputStream per
l’accesso sequenziale ai file. In particolare vengono definiti i seguenti 3 costruttori:
public
FileInputStream(String
filename)
FileNotFoundException
public FileInputStream(File fp)throws FileNotFoundException
public FileInputStream(FileDescriptor fd)
throws
I metodi a disposizione sono simili a quelli di InputStream.
4.3.4.4 Output sequenziale da file
La classe FileOutputStream estende la classe OutputStream per l’accesso sequenziale ai
file.
public
public
public
public
public
FileOutputStream(String filename) throws IOException
FileOutputStream(String filename, boolean append) throws IOException
FileOutputStream(File file) throws IOException
FileOutputStream(File file, boolean append) throws IOException
FileOutputStream(FileDescriptor fd)
I metodi a disposizione sono simili a quelli di OutputStream.
4.3.4.5 Leggere e Scrivere Dati binari
Abbiamo scritto e letto byte contenenti valori ASCII. Possiamo leggere e scrivere altri tipi di dati.
Per esempio si possono creare file contenenti ints, doubles, o shorts. Per leggere o scrivere
valori binari di tipi primitivi si usano DataInputStream e DataOutputStream.
DataOutputStream implementa l’interfaccia DataOutput: essa definisce i metodi che
scrivono tutti i tipi primitivi di Java in un file. Questi dati sono scritti usando il formato binario a
noi “non comprensibile”. Il costruttore:
DataOutputStream(OutputStream outputStream)
outputStream è lo stream dove i dati verranno scritti. Per scrivere in un file, si usa l’oggetto
creato da FileOutputStream per questo parametro.
Metodi di Output definiti in DataOutputStream:
void
void
void
void
void
void
void
void
writeBoolean(boolean val )
writeByte(int val )
writeChar(int val )
writeDouble(double val )
writeFloat(float val )
writeInt(int val )
writeLong(long val )
writeShort(int val )
DataInputStream implementa l’interfaccia DataInput, la quale definisce i metodi per
leggere i tipi primitivi di Java. DataInputStream usa un’istanza di InputStream:
DataInputStream(InputStream inputStream)
inputStream e’ lo stream che e’ legato all’istanza di DataInputStream che si sta creando.
Per leggere da un file si usa l’oggetto creato da FileInputStream per questo parametro.
Metodi di Input definiti da DataInput Stream:
boolean readBoolean( )
byte readByte( )
char readChar( )
double readDouble( )
float readFloat( )
int readInt( )
long readLong( )
short readShort( )
4.3.4.6 Stream basati sui caratteri
In cima alla gerarchia dei character stream ci sono le classi Reader e Writer. Tutti i metodi
generano IOException in presenza di errori. I metodi definiti da queste due classi sono
disponibili per tutte le sottoclassi. Quindi, sono un insieme minimale di funzioni di I/O per gli
stream di caratteri.
I metodi della classe Reader:
abstract void close( )
int read( )
int read(char buffer[ ])
abstract int read(char buffer[ ],int offset,int numChars)
int read(CharBuffer buffer)
long skip(long numChars)
//salta numChars caratteri dell’input, e ritorna il numero di caratteri
//effettivamente saltati
I metodi della classe Writer:
Writer append(char ch) throws IOException
//appende ch alla //fine dell’output stream
abstract void close( )
abstract void flush( )
void write(int ch)
void write(char buffer[ ])
//scrive l’array di caratteri sull’output stream
abstract void write(char buffer[ ],int offset,int numChars)
void write(String str)
void write(String str, int offset,int numChars)
//scrive una sottostinga di str iniziando da offeset lunga numChars nello
//stream di output
4.3.4.7 Input da console usando uno stream di caratteri
Quindi, si usa l’oggetto prodotto da InputStreamReader, per costruire un BufferedReader
nel seguente modo:
BufferedReader(Reader inputReader)
inputReader e’ lo stream collegato ad un’istanza di BufferedReader che si sta creando.
Esempio:
BufferedReader br = new BufferedReader(new
InputStreamReader(System.in));
br è uno stream basato sui caratteri collegato alla console attraverso System.in.
4.3.4.8 File I/O usando uno stream di caratteri
Per costruire file I/O basati su caratteri si usano le classi FileReader e FileWriter.
4.3.4.9 Input da File
La classe FileReader estende la classe InputStreamReader. Viene usata per leggere in
modo sequenziale da un file. I costruttori sono:
public FileReader(String filename)throws FileNotFoundException
public FileReader(File fp)
public FileReader(FileDescriptor fd)throws FileNotFoundException
Normalmente si utilizza il seguente costruttore, al quale si passa il nome del file:
FileReader reader = new FileReader(“file.txt”);
Se il file non esiste viene generata un’eccezione FileNotFoundException. Il metodo read
restituisce un carattere del file per ogni sua invocazione. Raggiunto il termine del file viene
restituito il valore -1:
Public int read() throws IOException
FileReader reader = new FileReader(“file.txt”);
while(true)
{ int i = reader.read();
if (i == -1) break;
char c= (char) i;
…
}
Esempio:
import java.io.*;
public class esempio02
{ public static void main(String[] args)
{ int n;
boolean esci=false;
ConsoleReader console = new ConsoleReader();
while(!esci) {
System.out.print("Inserisci il nome del file da leggere:");
System.out.print(“ (‘FINE’ per uscire) :”);
String nomefile = console.readLine();
if ( nomefile.compareToIgnoreCase(“FINE”)==0)
esci=true;
else
{
try
{ FileReader filetxt = new FileReader(nomefile);
while ((n=filetxt.read()) !=-1)
System.out.print( (char)n);
filetxt.close();
esci=true;
}
catch(FileNotFoundException e)
{ System.out.println(e);
}
catch(IOException e)
{ System.out.println(e);
System.exit(1);
}
}
}
}
}
4.3.4.10
Output su File
La classe FileWriter estende la classe OutputStreamWriter. Viene usata per scrivere in
modo sequenziale su un file. I costruttori sono:
public
public
public
public
public
FileWriter(String filename) throws IOException
FileWriter(String filename, boolean append) throws IOException
FileWriter(File file) throws IOException
FileWriter(File file, boolean append) throws IOException
FileWriter(FileDescriptor fd)
Esempio:
import java.io.*;
public class esempio03
{ public static void main(String[] args)
{ int n;
boolean esci=false
ConsoleReader console = new ConsoleReader();
while(!esci)
{ System.out.print("Inserisci il nome del file di input :");
System.out.print(“ (\”FINE\” per uscire) :”);
String FileIn=console.readLine();
System.out.print("Inserisci il nome del file di output:");
String FileOut=console.readLine();
if(nomefile.comparetoIgnoreCase(“FINE”)==0) esci=true;
else
{ try
{ FileReader filein = new FileReader(FileIn);
FileWriter fileout = new FileWriter(FileOut);
while ( (n=filein.read()) !=-1 )
fileout.write( (char)n);
filein.close();
fileout.close();
esci=true; }
catch(FileNotFoundException e)
{ System.out.println(e); }
catch(IOException e)
{ System.out.println(e);
System.exit(1);}
}
}
}
}
4.3.4.11
La classe FileWriter
FileWriter crea un Writer che si può usare per scrivere un file.
FileWriter(String fileName) throws IOException
FileWriter(String fileName, boolean append) throws IOException
fileName: path name del file. Se append è true, allora l’output è scritto alla fine del file.
Altrimenti il file viene sovrascritto.
FileWriter e’ derivato da OutputStreamWriter e Writer, così ha accesso ai metodi
definiti in queste classi. Esempio:
/* A simple key-to-disk utility that
demonstrates a FileWriter. */
import java.io.*;
class KtoD {
public static void main(String args[])
throws IOException {
String str;
FileWriter fw;
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));
try {
fw = new FileWriter("test.txt");
}
catch(IOException exc) {
System.out.println("Cannot open file.");
return ;
}
System.out.println("Enter text ('stop' to quit).");
do {
System.out.print(": ");
str = br.readLine();
if(str.compareTo("stop") == 0) break;
str = str + "\r\n"; // add newline
fw.write(str);
} while(str.compareTo("stop") != 0);
fw.close();
}
}
4.3.4.12
La classe FileReader
La classe FileReader crea un Reader per leggere il contenuto di un file.
FileReader(String fileName) throws FileNotFoundException
fileName: path name del file. Genera un FileNotFoundException se il file non esiste.
FileReader è derivato da InputStreamReader e Reader. Quindi, ha accesso a tutti i
metodi definiti da queste classi. Esempio:
/* A simple disk-to-screen utility that demonstrates a FileReader. */
import java.io.*;
class DtoS {
public static void main(String args[]) throws Exception {
FileReader fr = new FileReader("test.txt");
BufferedReader br = new BufferedReader(fr);
String s;
while((s = br.readLine()) != null) {
System.out.println(s);
}
fr.close();
}
}
FileReader e’ incorporato in un BufferedReader. Questo permette di usare readLine().
4.3.4.13
Riassumendo…
FileInputStream e FileOutputStream mettono a disposizione stream di inp ut e di output
collegati a un file di byte sul disco.
FileInputStream fin=new FileInputStream(“employee.dat”);
Si può anche usare un oggetto File:
File f = new File("employee.dat");
FileInputStream fin = new FileInputStream(f);
Per leggere o scrivere dati corrispondenti ai tipi primitivi di Java si usano le classi
DataInputStream e DataOutputStream.
FileInputStream non ha metodi per gestire tipi numerici, DataInputStream non ha un
metodo per acquisire i dati da un file. Alcuni stream servono per ricavare i byte dai file e da sorgenti
differenti, altri stream, per esempio DataInputStream o PrintWriter possono assemblare i
byte in dati più utili.
Il programmatore deve combinare questi due elementi in quello che viene chiamato stream filtrato,
impostando uno stream esistente nel costruttore dell’altro stream. Esempio:
FileInputStream fin = new FileInputStream("employee.dat");
DataInputStream din = new DataInputStream(fin);
double s = din.readDouble();
Gli stream non hanno un buffer. Ogni chiamata di read contatta il sistema operativo per chiedere un
altro byte. Per costruire uno stream bufferizzato si devono combinare più elementi usando uno
stream filtrato, come per esempio:
DataInputStream din = new DataInputStream( new
BufferedInputStream( new
FileInputStream("employee.dat")));
Quando si legge l’input a volte è necessario leggere il byte successivo per vedere se si tratta di un
valore corrispondente a quello desiderato. In JAVA si usa PushbackInputStream:
PushbackInputStream pbin = new PushbackInputStream( new BufferedInputStream( new
FileInputStream("employee.dat")));
Per leggere il byte successivo: int b = pbin.read();
Per rifiutarlo se non corrisponde al byte voluto: if (b != '<') pbin.unread(b);
Per leggere sia il byte successivo sia leggere tipi numerici:
DataInputStream din = new DataInputStream( pbin = new
PushbackInputStream(
new BufferedInputStream( new FileInputStream("employee.dat"))));
4.3.5 Random Access file
La classe RandomAccessFile consente di accedere direttame nte in un qualunque punto di un
File.
Il file viene visto come sequenza di byte, con un indice (file pointer) che identifica la posizione per
la successiva operazione di I/O.
Dopo una operazione di I/O, la posizione del file pointer viene aggiornata. Il File pointer può essere
spostato dall’utente in una qualsiasi posizione all’interno del file.
RandomAccessFile non è derivata da InputStream o OutputStream. Essa implementa
l’interfaccia DataInput e DataOutput, la quale definisce i metodi di I/O. I costruttori sono:
public RandomAccessFile(String name, String mode) throws FileNotFoundException
public RandomAccessFile(File fp, String mode) throws FileNotFoundException
I modi possibili sono:
•
•
•
•
“r”: aperto solo in lettura;
“rw”: aperto in lettura e scrittura;
“rws ”: rw con operazioni di update di contenuto e metadata sincrone;
“rwd”:rw con operazioni di update di contenuto sincrone;
La modalità “rwd” riduce le effettive operazioni di I/O. Se il modo è errato viene lanciato
l’eccezione IllegalArgumentException. Metodi:
void close() //Chiude il radom access file e rilascia
//ogni risorsa associata ad esso
long length() //Ritorna la lunghezza del file
long getFilePointer() //Ritorna l’offset attuale del file
void seek(long pos) //Set il file-pointer offset, misurando
//dall’inizio del file al quale la prossima lettura o
//scrittura deve avvenire
int skipBytes(int n) //Salta n n bytes di input
//butta via (discarding) i byte saltati
int read()//Legge un byte dal file
int read(byte b[])
//Legge b.lenght byte da questo file e e li mette in un array di byte
int read(byte b[], int off, int len)
//Legge len byte dal file e li mette in un array iniziando da off
boolean readBoolean()
byte readByte()
char readChar() //Legge un Unicode carattere
double readDouble()
float readFloat()
int readInt()
String readLine() //Legge la prossima linea di testo dal file
short readShort() // Legge un 16-bit con segno numero
void write(byte b[]) //Scrive b.lenght bytes dall’array
//al file iniziando dalla posizione corrente del file
//pointer.
void write(byte b[], int off, int len)
//Scrive len byte dall’array iniziando da offset di questo file
void write(int b) // Scrive il byte b nel file
void writeBoolean(boolean v)
void writeByte(int v)
void writeBytes(String s) //scrive la stringa in un file come sequenza di bytes
void writeChar(int v)
void writeDouble(double v)
void writeFloat(float v)
void writeInt(int v)
void writeLong(long v)
void writeShort(int v)
4.3.6 Stream di Oggetti
Se si desidera salvare file che contengono dei dati strutturati si utilizza il meccanismo di
serializzazione degli oggetti che ne automatizza quasi completamente la gestione. La classe che si
desidera leggere/scrivere come uno stream di oggetti deve implementare l’interfaccia
Serializable:
Class Employee implements Serializable { … }
L’interfaccia Serializable non dispone di alcun metodo, per cui non serve modificare la
classe in alcun modo.
4.3.6.1 Scrittura di oggetti
Per salvare i dati di un oggetto è necessario aprire un oggetto ObjectOutputStream:
ObjectOutputStream out = new ObjectOutputStream (new FileOutputStream(nomefile,
true/false));
Il valore del parametro booleano indica o meno la condizione di append. Per salvare un oggetto si
utilizza il metodo WriteObject:
Employee p1 = new Employee (…);
Out.WriteObject(p1);
4.3.6.2 Lettura di oggetti
Per rileggere gli oggetti bisogna innanzitutto avere un oggetto ObjectInputStream:
ObjectInputStream in = new ObjectInputStream (new FileInputStream(nomefile);
Per recuperare gli oggetti nello stesso ordine in cui sono stati scritti si utilizza il metodo
ReadObject():
Employee p1 = (Employee) in.ReadObject()
La chiusura di uno stream sia di input che di output si realizza mediante il metodo close().
4.3.7 Gestione dei file
Abbiamo visto come leggere e scrivere i dati in un file. Le classi Stream riguardano appunto il
contenuto dei file. La classe File:
•
•
Dà informazioni utili riguardo file o directory.
Non apre file e non consente l’elaborazione di file
La classe File e gli attributi di directory e file, consentono di accedere alle directory, verificare e
acquisire le caratteristiche dei file presenti, cambiarne gli attributi, cancellare e rinominare file.
Il metodo costruttore File(String nome) fornisce un oggetto file con il nome assegnato
posto nella directory corrente. Una chiamata di questo costruttore non crea un file con il nome
assegnato, se il file non esiste. Se si vuole creare un nuovo file si deve usare createNewFile
della classe File.
Il metodo costruttore File(String pathname, String name) fornisce un oggetto file
con il nome assegnato posto nella directory specificata.
Il metodo costruttore File(File dir, String name) fornisce un oggetto file con il no me
assegnato posto nella directory specificata. Se dir è null allora lo crea nella directory corrente.
Metodi:
boolean exists()
Verifica l’esistenza del file con cui è invocata.
boolean canRead()
Verifica se il file è di sola lettura.
boolean canWrite()
Verifica se il file ha i permessi di lettura\scrittura.
boolean isDirectory()
Verifica se si tratta di Directory
boolean isFile()
Verifica se si tratta di File
boolean isHidden()
Verifica se il File è nascosto.
String getAbsolutePath() Restituisce una stringa con il path completo del file.
String getName()
Restituisce una stringa con il nome del File.
String getParent()
Restituisce una stringa con il solo path del file.
String[] list()
Restituisce un array di stringhe con tutti i file e le
sottodirectory se invocato con un nome di directory.
Metodi di settaggio:
boolean createNewFile() Crea un file vuoto. Il boolean è true se l’operazione ha avuto
successo.
boolean delete()
Cancella il file.
void deleteOnExit()
Setta il file per la sua cancellazione all’uscita dal programma.
boolean mkdir()
Crea una directory
boolean renameTo(File)
Rinomina il file invocante con il nome del parametro.
boolean setReadOnly()
Setta il file in sola lettura. Il boolean è true se l’operazione è
riuscita.
4.3.8 Esercizi
Creazione di un file
“Realizzare un programma che generi in un ciclo 150 numeri da 1000 a 1149 e dopo averli
trasformati in stringa li scriva su una singola riga del file.”
Appendere righe a un file
“Realizzare un programma che aggiunga al precedente file di testo altri 50 numeri da 1150 a 1199 e
li scriva in coda al file esistente.”
In entrambi i casi prevedere un metodo stampa() a video per la verifica.
4.4 Package net
Il package per il networking in Java è java.net, contiene primitive per:
•
•
Comnunicazione Socket-based:
o Le applicazioni vedono il networking come uno strem di dati
o Connessioni basate su un protocollo
o Usa TCP (Transmission Control Protocol)
Comunicazioni Packet-based:
o Pacchetti indiduali trasmessi
o Servizi Connectionless
o Usa UDP (User Datagram Protocol)
Definiamo porta non un device fisico ma un’astrazione per semplificare la comunicazione tra client
(chi avvia il processo di connessione) e server (chi riceve).
Quando si vuole stabilire una connessione è sempre necessario definire l’indirizzo IP e la porta del
destinatario; è poi possibile gestire automaticamente o manualmente il proprio IP e la propria porta
da comunicare al destinatario per ricevere la sua risposta.
In breve possiamo immaginare che Alice (client) voglia inviare una lettera a Bob (server):
ovviamente dovrà definire l’indirizzo di Bob (IP) sulla busta e il numero civico di Bob (porta) sul
retro, e deve mettere in condizione Bob di conoscere il suo indirizzo e il suo numero civico
riportando queste informazioni come intestazione della lettera (che possiamo assumere come un
qualunque pacchetto di dati). Con questa metafora dovrebbe essere più chiaro adesso il sistema di
comunicazione client \server e la sua necessità di indirizzi e porte.
Gli oggetti che gestiscono la creazione, la manutenzione e la chiusura di una connessione sono i
socket.
4.4.1 Un semplice Server Stream Socket
Cinque passi per creare un semplice server in Java:
•
•
•
•
•
ServerSocket object
o Registra una porta ed un unmero massimo di client che possono connettersi
Ogni connessione client viene gestita con l’oggetto Socket object
o Server blocks until client connects
Scambio di dati (Send/Receive)
o OutputStream per spedire ed InputStream per ricevere
o Metodi getInputStream e getOutputstream (usati dall’oggetto Socket)
Fase di processing
o Server e Client comunicano tramite stream
Chiusura degli stream e delle connessioni
// Fig. 11.3: Server.java
// Set up a Server that will receive a connection
// from a client, send a string to the client,
// and close the connection.
import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Server extends JFrame {
private JTextField enter;
private JTextArea display;
ObjectOutputStream output;
ObjectInputStream input;
public Server()
{
super( "Server" );
Container c = getContentPane();
enter = new JTextField();
enter.setEnabled( false );
enter.addActionListener(
new ActionListener() {
public void actionPerformed( ActionEvent e )
{
sendData( e.getActionCommand() );
}
}
);
c.add( enter, BorderLayout.NORTH );
display = new JTextArea();
c.add( new JScrollPane( display ),
BorderLayout.CENTER );
setSize( 300, 150 );
show();
}
public void runServer()
{
ServerSocket server;
Socket connection;
int counter = 1;
try {
// Step 1: Create a ServerSocket.
server = new ServerSocket( 5000, 100 );
while ( true ) {
// Step 2: Wait for a connection.
display.setText( "Waiting for connection\n" );
connection = server.accept();
display.append( "Connection " + counter +
" received from: " +
connection.getInetAddress().getHostName() );
// Step 3: Get input and output streams.
output = new ObjectOutputStream(
connection.getOutputStream() );
output.flush();
input = new ObjectInputStream(
connection.getInputStream() );
display.append( "\nGot I/O streams\n" );
// Step 4: Process connection.
String message =
"SERVER>>> Connection successful";
output.writeObject( message );
output.flush();
enter.setEnabled( true );
do {
try {
message = (String) input.readObject();
display.append( "\n" + message );
display.setCaretPosition(
display.getText().length() );
}
catch ( ClassNotFoundException cnfex ) {
display.append(
"\nUnknown object type received" );
}
} while ( !message.equals( "CLIENT>>> TERMINATE" ) );
// Step 5: Close connection.
display.append( "\nUser terminated connection" );
enter.setEnabled( false );
output.close();
input.close();
connection.close();
++counter;
}
}
catch ( EOFException eof ) {
System.out.println( "Client terminated connection" );
}
catch ( IOException io ) {
io.printStackTrace();
}
}
private void sendData( String s )
{
try {
output.writeObject( "SERVER>>> " + s );
output.flush();
display.append( "\nSERVER>>>" + s );
}
catch ( IOException cnfex ) {
display.append(
"\nError writing object" );
}
}
public static void main( String args[] )
{
Server app = new Server();
app.addWindowListener(
new WindowAdapter() {
public void windowClosing( WindowEvent e )
{
System.exit( 0 );
}
}
);
app.runServer();
}
}
/**************************************************************************
* (C) Copyright 1999 by Deitel & Associates, Inc. and Prentice Hall.
*
* All Rights Reserved.
*
*
*
**************************************************************************/
4.4.2 Un semplice Client Stream Socket
Quattro passi per creare un semplice client Java :
•
•
•
•
Creare un oggetto Socket per il client
Ottenere Socket InputStream ed Outputstream
Processare le informazioni scambiate
Chiudere stream e socket
// Fig. 11.4: Client.java
// Set up a Client that will read information sent
// from a Server and display the information.
import java.io.*;
import java.net.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Client extends JFrame {
private JTextField enter;
private JTextArea display;
ObjectOutputStream output;
ObjectInputStream input;
String message = "";
public Client()
{
super( "Client" );
Container c = getContentPane();
enter = new JTextField();
enter.setEnabled( false );
enter.addActionListener(
new ActionListener() {
public void actionPerformed( ActionEvent e )
{
sendData( e.getActionCommand() );
}
}
);
c.add( enter, BorderLayout.NORTH );
display = new JTextArea();
c.add( new JScrollPane( display ),
BorderLayout.CENTER );
setSize( 300, 150 );
show();
}
public void runClient()
{
Socket client;
try {
// Step 1: Create a Socket to make connection.
display.setText( "Attempting connection\n" );
client = new Socket(
InetAddress.getByName( "127.0.0.1" ), 5000 );
display.append( "Connected to: " +
client.getInetAddress().getHostName() );
// Step 2: Get the input and output streams.
output = new ObjectOutputStream(
client.getOutputStream() );
output.flush();
input = new ObjectInputStream(
client.getInputStream() );
display.append( "\nGot I/O streams\n" );
// Step 3: Process connection.
enter.setEnabled( true );
do {
try {
message = (String) input.readObject();
display.append( "\n" + message );
display.setCaretPosition(
display.getText().length() );
}
catch ( ClassNotFoundException cnfex ) {
display.append(
"\nUnknown object type received" );
}
} while ( !message.equals( "SERVER>>> TERMINATE" ) );
// Step 4: Close connection.
display.append( "Closing connection.\n" );
output.close();
input.close();
client.close();
}
catch ( EOFException eof ) {
System.out.println( "Server terminated connection" );
}
catch ( IOException e ) {
e.printStackTrace();
}
}
private void sendData( String s )
{
try {
message = s;
output.writeObject( "CLIENT>>> " + s );
output.flush();
display.append( "\nCLIENT>>>" + s );
}
catch ( IOException cnfex ) {
display.append(
"\nError writing object" );
}
}
public static void main( String args[] )
{
Client app = new Client();
app.addWindowListener(
new WindowAdapter() {
public void windowClosing( WindowEvent e )
{
System.exit( 0 );
}
}
);
app.runClient();
}
}
/**************************************************************************
* (C) Copyright 1999 by Deitel & Associates, Inc. and Prentice Hall.
*
* All Rights Reserved.
*
*
*
**************************************************************************/
4.5 Package awt e swing
Questi package sono necessari per gestire la sezione grafica di una applicazione. In Java una
finestra (non contenuta in un’altra finestra) è chiamata frame. La libreria AWT possiede una classe
chiamata called Frame, per queste finestre top level.
La versione Swing di questa classe è chiamata JFrame ed estende la classe Frame. JFrame è
uno dei pochi componenti Swing che non sono disegnati in un canvas.
Quindi le decorazioni (bottoni, title bar, icone, ecc.) sono disegnate dal sistema grafico e non dalle
Swing. Esempio:
import javax.swing.*;
public class SimpleFrameTest {
public static void main(String[] args){
SimpleFrame frame = new SimpleFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class SimpleFrame extends JFrame{
public SimpleFrame(){
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}
4.5.1 Frame
Definiamo cose deve accedere quando l’utente deve chiudere il frame. Per questo programma
desideriamo che l’utente esca.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Nell’esempio precedente abbiamo scritto due classi. Spesso si vedranno classi del tipo:
class SimpleFrame extends JFrame {
public static void main(String[] args) {
SimpleFrame frame = new SimpleFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
public SimpleFrame() {
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}
La classe JFrame possiede pochi metodi per cambiare l’aspetto del frame. Attraverso
l’ereditarietà però le cose cambiano. Per esempio si possono impostere dimensioni e posizione
attraverso metodi di superclassi. Tra i più importanti metodi troviamo:
•
•
•
•
Il metodo dispose che chiude la finestra e constente di rilaciare le risorse richieste da
questa;
Il mettodo setIconImage che prende un oggetto Image e lo usa come icona quando
questa viene minimizzata;
Il metodo setTitle per cambiare il titolo nelle title bar;
Il metodo setResizable che prende un booleano per determinare se un frame è
ridimensionabile o meno dall’utente.
La gerarchia di ereditarietà è:
Esempio CenteredFrame:
class CenteredFrame extends JFrame
{
public CenteredFrame()
{
// get screen dimensions
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int screenHeight = screenSize.height;
int screenWidth = screenSize.width;
// center frame in screen
setSize(screenWidth / 2, screenHeight / 2);
setLocation(screenWidth / 4, screenHeight / 4);
// set frame icon and title
Image img = kit.getImage("icon.gif");
setIconImage(img);
setTitle("CenteredFrame");
}
}
dove abbiamo utilizzato Toolkit che ottiene informazioni riguardo il sistema a finestre
sottostante.
4.5.1.1 Visualizzare informazioni in un Frame
La struttura di un JFrame è particolarmente complessa!
In un JFrame si distinguono QUATTRO pannelli (pane):
•
•
•
root pane,
layered pane ,
glass pane ;
Necessari per organizzare la barra dei menu, il content pane e per implementare il look and feel.
La parte più “antipatica” per i programmatori Swing è il content pane . Quando si disegna un
frame si aggiungono dei componenti nel content pane:
Container contentPane = frame.getContentPane();
Component c = . . .;
contentPane.add(c);
In JDK 5.0, si può usare la chiamata
frame.add(c);
4.5.1.2 Aggiungere un JPanel
Desideriamo aggiungere un panel al frame che consenta di scrivere un messaggio. I pannelli
sono implementati nella classe JPanel. Hanno una superficie dove disegnare. Sono dei container.
Per creare un pannello utile, usare l’ereditarietà e creare una nuova classe.
•
•
•
Definire una classe che estende JPanel.
Fare l’overloading del metodo paintComponent.
Il metodo paintComponent si trova in JComponent, prende un parametro di tipo
Graphics.
Un oggetto Graphics possiede una collezione di impostazioni per disegnare immagini e testo, ad
esempio font, colori ecc.
Tutte le operazioni di disegno in JAVA passano attraverso l’oggetto Graphics. Possiede metodi
per disegnare pattern, immagini e testo. Esempio:
class NotHelloWorldPanel extends JPanel
{
public void paintComponent(Graphics g)
{
. . .
g.drawString("Not a Hello, World program", MESSAGE_X, MESSAGE_Y);
}
public static final int MESSAGE_X = 75;
public static final int MESSAGE_Y = 100;
}
La classe non è ancora completa. NotHelloWorldPanel estende JPanel class, che sa come
disegnare nel panel. Per “garantire” che la superclasse fa il suo lavoro bisogna invocare
super.paintComponent prima di ogni operazione di disegno!!!
class NotHelloWorldPanel extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
. . . // code for drawing will go here
}
}
4.5.2 Oggetti Graphics
Contesto Graphics:
•
•
•
Consente di disegnare sullo schermo
Graphics gestisce il constesto grafico (Controlla come le informazioni vengono
visualizzate)
La classe Graphics è astratta
o Non può essere istanziata
o Contribuisce alla portabilità di Java
La classe Component possiede il metodo paint che prende come parametro un oggetto
Graphics, public void paint (Graphics g) che viene chiamata tramite il metodo
repaint.
Il sistema di coordinate in JAVA segue lo schema
+x
(0, 0)
X axis
( x, y )
+y
Y a x is
4.5.3 Classe Color
Controllo dei colori: definisce metodi e vincoli per manipolare colori. I colori vengono creati dal
rosso, verde e blu (Valori RGB). I vincoli per i colori e valori RGB:
Color constant
public final static Color ORANGE
Metodi:
Color
RGB value
public final static Color PINK
orange
pink
255, 200, 0
255, 175, 175
public final static Color CYAN
cyan
0, 255, 255
public final static Color MAGENTA
magenta
255, 0, 255
public final static Color YELLOW
yellow
255, 255, 0
public final static Color BLACK
black
0, 0, 0
public final static Color WHITE
white
255, 255, 255
public final static Color GRAY
gray
128, 128, 128
public final static Color LIGHT_GRAY
light gray
192, 192, 192
publi c final static Color DARK_GRAY
dark gray
64, 64, 64
public final static Color RED
red
255, 0, 0
public final static Color GREEN
green
0, 255, 0
public final static Color BLUE
blue
0, 0, 255
Method
Description
Color constructors and methods
public Color( int r, int g, int b )
Creates a color based on red, green and blue components expressed as integers
from 0 to 255.
public Color( float r, float g, float b )
Creates a color based on red, green and blue components expressed as
floating-point values from 0.0 to 1.0.
public int getRed()
Returns a value between 0 and 255 representing the red content.
public int getGreen()
Returns a value between 0 and 255 representing the green content.
public int getBlue()
Returns a value between 0 and 255 representing the blue content.
Graphics methods for manipulating Colors
public Color getColor()
Returns a Color object representing the current color for the graphics
context.
public void setColor( Color c )
Sets the current color for drawing with the graphics context.
4.5.4 Forme
java.awt.geom.RectangularShape 1.2
double
double
double
double
double
double
getCenterX()
getCenterY()
getMinX()
getMinY()
getMaxX()
getMaxY()
Restituiscono il centro, minimo, massimo X o Y del rettangolo.
double getWidth()
double getHeight()
Ampiezza ed altezza del rettangolo.
double getX()
double getY()
Ritornano la coordinata x o y del angolo alto-sinistro del rettangolo.
java.awt.geom.Rectangle2D.Double 1.2
Rectangle2D.Double(double x, double y, double w, double h)
java.awt.geom.Rectangle2D.Float 1.2
Rectangle2D.Float(float x, float y, float w, float h)
java.awt.geom.Ellipse2D.Double 1.2
Ellipse2D.Double(double x, double y, double w, double h)
(x,y) angolo alto sinistro.
java.awt.geom.Point2D.Double 1.2
Point2D.Double(double x, double y)
java.awt.geom.Line2D.Double 1.2
Line2D.Double(Point2D start, Point2D end)
Line2D.Double(double startX, double startY, double endX, double endY)
Invece
javax.swing.ImageIO 1.4
static BufferedImage read(File f)
static BufferedImage read(URL u)
legge un’immagine dal corrispondente File o URL.
java.awt.Image 1.0
Graphics getGraphics()
Ottiene il contesto grafico per disegnare dentro il corrispondente image buffer.
void flush()
Rilascia tutte le risorse tenute dall’oggetto immagine.
java.awt.Graphics 1.0
boolean drawImage(Image img, int x, int y, ImageObserver observer)
Disegna un’immagine non scalata. Notiamo che la chiamata può ritornare prima che l’immagine sia
visualizzata. Parametri: img immagine da visualizzare; x, y coordinate dell’angolo alto sinistro;
observer oggetto per notificare lo stato del processo di rendering dell’immagine, può essere
null.
boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver
observer)
Disegna un’immagine scalata. Parametri: width-height, ampiezza ed altezza dell’immagine.
void copyArea(int x, int y, int width, int height, int dx, int dy)
Copia un’area dello schermo. Parametri: x, y coordinate dell’angolo in alto a sinistra
dell’immagine sorgente; width e height ampiezza ed altezza dell’area sorgente; dx e dy
distanza orizzontale e verticale dall’area sorgente all’area target.
void dispose()
Rilascia il contesto grafico e rilascia le risorse del sistema operativo.
java.awt.Component 1.0
Image createImage(int width, int height)
Crea un buffer per un immagine. Viene utilizzato per il doppio buffering.
4.5.5 Gestione degli eventi
Un evento è quello che viene causato dall’applicazione di un metodo o da un’azione compiuta
dall’utilizzatore del programma su un suo oggetto. Il modello per la gestione degli eventi è diviso in
tre parti:
1. Sorgente evento: componente GUI con il quale l’utente interagisce;
2. Oggetto Event : incapsula informazioni riguardo l’evento accaduto;
3. Event listener: riceve l’oggetto evento quando notificato quindi risponde.
Il programmatore deve effettuare due task:
•
•
Registrare l’event listener per l’evento sorgente
Implementare il metodo gestore dell’evento (event handler)
In Java l’informazione riguardo un evento è incapsulata in un oggetto event. Tutti gli oggetti
evento sono derivati dalla superclasse java.util.EventObject. Esistono sottoclassi quali:
•
•
ActionEvent
WindowEvent
Diverse sorgenti evento possono produrre diversi tipi di eventi. Per esempio un bottone può spedire
un oggetto ActionEvent, mentre una finestra può spedire un oggetto WindowEvent.
4.5.5.1 Alcune classi evento del package java.awt.event
Object
ActionEvent
EventObject
AdjustmentEvent
AWTEvent
ItemEvent
FocusEvent
TextEvent
PaintEvent
ComponentEvent
WindowEvent
InputEvent
KeyEvent
MouseEvent
MouseWheelEvent
4.5.5.2 Interfaccie Event-listener del package java.awt.event
interface
ActionListener
interface
AdjustmentListener
interface
ComponentListener
interface
ContainerListener
interface
FocusListener
interface
EventListener
interface
ItemListener
interface
KeyListener
interface
MouseListener
interface
MouseMotionListener
interface
TextListener
interface
WindowListener
Un oggetto listener è un’istanza di una classe che implementa una speciale interfaccia chiamata
“listener interface”. Un evento sorgente è un oggetto che ha la possibilità di registrare
oggetti listener e spedire ad essi oggetti event.
L’evento sorgente spedisce eventi a tutti i listener registrati quando si verifica un evento. Il
listener quindi userà le informazioni spedite dall’oggetto per determinare la relativa reazione
(risposta) all’evento. L’oggetto listener viene registrato usando delle linee di codice come
questa (che seguono il modello):
eventSourceObject.addEventListener(eventListenerObject);
Esempio:
ActionListener listener = . . .;
JButton button = new JButton("Ok");
button.addActionListener(listener);
L’oggetto listener sarà quindi notificato ogni qual volta si verifica un "action event“ nel
bottone. Per un bottone un action event è un click. La classe quindi dovrà implementare
l’appropriata Listener interface. In questo caso sarà la ActionListener interface. Per
implementare la ActionListener interface la classe listener deve avere un metodo
chiamato actionPerformed il quale riceva un oggetto di tipo ActionEvent come parametro.
Esempio:
class MyListener implements ActionListener {
. . .
public void actionPerformed(ActionEvent event) {
// reaction to button click goes here
. . .
}
}
Costruiamo un pannello popolato con tre bottoni. Tre oggetti listener sono aggiunti come
action listener ai bottoni. Un bottone viene creato con una etichetta, un icona, o entrambe.
Esempio:
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton(new ImageIcon("blue-ball.gif"));
Per aggiungere i bottoni al pannello usiamo il metodo add. Add prende il nome l’oggetto che deve
essere aggiunto al container.
class ButtonPanel extends JPanel {
public ButtonPanel() {
JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton("Blue");
JButton redButton = new JButton("Red");
add(yellowButton);
add(blueButton);
add(redButton);
}
}
class ColorAction implements ActionListener {
public ColorAction(Color c) {
backgroundColor = c;
}
public void actionPerformed(ActionEvent event) { // set panel background
// color
. . .
}
private Color backgroundColor;
}
Quindi costruiamo un oggetto per ogni colore ed impostiamo gli oggetti come button
listener.
ColorAction yellowAction = new ColorAction(Color.YELLOW);
ColorAction blueAction = new ColorAction(Color.BLUE);
ColorAction redAction = new ColorAction(Color.RED);
yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);
Semplificando:
void makeButton(String name, Color backgroundColor) {
JButton button = new JButton(name);
add(button);
ColorAction action = new ColorAction(backgroundColor);
button.addActionListener(action);
}
Button panel diventa:
public ButtonPanel(){
makeButton("yellow", Color.YELLOW);
makeButton("blue", Color.BLUE);
makeButton("red", Color.RED);
}
La classe class ColorAction è necessaria solo una volta quindi la possiamo integrare dentro il
metodo makeButton:
void makeButton(String name, final Color backgroundColor) {
JButton button = new JButton(name);
add(button);
button.addActionListener(new
ActionListener() {
public void actionPerformed(ActionEvent event) {
setBackground(backgroundColor);
}
});
}
Implementiamo tutto dentro la classe ButtonPanel:
class ButtonPanel extends JPanel implements ActionListener
{
. . .
public void actionPerformed(ActionEvent event) {
//set background color
. . .
}
}
4.5.6 Gerarchia bottoni Swing
JComponent
AbstractButton
JButton
JToggleButton
JCheckBox
JRadioButton
JCheckBox e JRadioButton
Bottoni di stato: On/Off o true/false
Tre tipi:
•
•
•
JToggleButton
JCheckBox
JRadioButton
Campi testo:
•
•
JTextField: Area a linea singola dove l’utente può inserire testo;
JPasswordField: Estende JTextField, nasconde i caratteri che l’utente digita.
Campi lista:
JComboBox: Sequenza di item che l’utente può selezionare (chiamata pure drop-down list);
List: Sequenza di item di cui l’utente può selezionarne uno o più JList;
4.5.7 Eventi del mouse
Interfaccie Event-listener per eventi del mouse:
•
•
•
MouseListener
MouseMotionListener
Listen per MouseEvents
MouseListener and MouseMotionListener interface methods
Methods of interface MouseListener
public void mousePressed( MouseEvent event )
Called when a mouse button is pressed while the mouse cursor is on a component.
public void mouseClicked( MouseEvent event )
Called when a mouse button is pressed and released while the mouse cursor remains
stationary on a component.
public void mouseReleased( MouseEvent event )
Called when a mouse button is released after being pressed. This event is always
preceded by a mousePressed event.
public void mouseEntered( MouseEvent event )
Called when the mouse cursor enters the bounds of a component.
public void mouseExited( MouseEvent event )
Called when the mouse cursor leaves the bounds of a component.
Methods of interface MouseMotionListener
public void mouseDragged( MouseEvent event )
Called when the mouse button is pressed while the mouse cursor is on a component
and the mouse is moved while the mouse button remains pressed. This event is al ways
preceded by a call to mousePressed . All drag events are sent to the component on
which the drag began.
public void mouseMoved( MouseEvent event )
Called when the mouse is moved when the mouse cursor on a component. All move
events are sent to the component over which the mouse is currently positioned.
4.5.8 Classi Adapter
Implementano interfacce; sono usate per evitare di riscrivere metodi superflui di una o più
interfacce necessarie per l’applicazione.
Classi Event-adapter ed interfacce implementate nel package java.awt.event:
Event- adapter class
ComponentAdapter
Implements interface
ComponentListener
ContainerAdapter
ContainerListener
FocusAdapter
FocusListener
KeyAdapter
KeyListener
MouseAdapter
MouseListener
MouseMotionAdapter
MouseMotionListener
WindowAdapter
WindowListener
L’interfaccia KeyListener gestisce “key events” che sono generati quanto i tasti della tastiera
sono premuti e rilasciati.
KeyEvent contiene codice virtual key che rappresenta i tasti.
4.5.9 Layout manager
•
•
•
•
•
Servono per distribuire i compone nti GUI (a interfaccia grafica)
Danno delle capacità di base di layout
Gestiscono i dettagli del layout
Il programmatore si può concentrare sul “look and feel” di base
Interfaccia LayoutManager
In breve un Layout Manager permette di gestire in maniera molto più semplice e rapida gli elementi
che il programmatore vuole inserire all’interno della finestra (o delle finestre) dell’applicazione.
Layout manager
FlowLayout
Description
BorderLayout
Default per content pane di JFrame s (ed altre finestre) e JApplets.
Distribuisce i componenti in cinque aree: NORTH, SOUTH , EAST, WEST
and CENTER.
GridLayout
Distribuisce I componeneti in righe e colonne.
Default per java.awt.Applet, java.awt.Panel e
javax.swing.JPanel. I componenti vengono inseriti
sequenzialmente da sinistra verso destra nell’ordine in cui questi sono
aggiunti. E’ possibile specificare l’ordine dei componeneti usando il
medoto add di Container , questo prende un componente ed un
indice intero relativo alla posizione.
4.5.9.1 FlowLayout
Il più basilare layout manager, dove i componenti GUI vengono distribuiti sequenzialmente da
sinistra verso destra.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Fig. 2.24: FlowLayoutDemo.java
// Demonstrating FlowLayout alignments.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class FlowLayoutDemo extends JFrame {
private JButton leftButton, centerButton, rightButton;
private Container container;
private FlowLayout layout;
// set up GUI and register button listeners
public FlowLayoutDemo()
{
super( "FlowLayout Demo" );
layout = new FlowLayout();
// get content pane and set its layout
container = getContentPane();
container.setLayout( layout );
// set up leftButton and register listener
leftButton = new JButton( "Left" );
container.add( leftButton );
leftButton.addActionListener(
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
new ActionListener() {
// anonymous inner class
// process leftButton event
public void actionPerformed( ActionEvent event )
{
layout.setAlignment( FlowLayout.LEFT );
// realign attached components
layout.layoutContainer( container );
}
} // end anonymous inner class
); // end call to addActionListener
// set up centerButton and register listener
centerButton = new JButton( "Center" );
container.add( centerButton );
centerButton.addActionListener(
new ActionListener() {
// anonymous inner class
// process centerButton event
public void actionPerformed( ActionEvent event )
{
layout.setAlignment( FlowLayout.CENTER );
// realign attached components
layout.layoutContainer( container );
}
}
);
// set up rightButton and register listener
rightButton = new JButton( "Right" );
container.add( rightButton );
rightButton.addActionListener(
new ActionListener() {
// anonymous inner class
// process rightButton event
public void actionPerformed( ActionEvent event )
{
layout.setAlignment( FlowLayout.RIGHT );
// realign attached components
layout.layoutContainer( container );
}
}
);
setSize( 300, 75 );
setVisible( true );
Nelle righe 17 e 21: si imposta il layout come FlowLayout.
Nella riga 33: quando l’utente seleziona left JButton, tutto viene rialineato a sinistra.
Nella riga 53: quando l’utent e seleziona center JButton, tutto viene realineato al centro.
4.5.9.2 BorderLayout
Distribuisce i componenti in cinque regioni:
•
•
•
•
•
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
NORTH (top del container)
SOUTH (bottom del container)
EAST (left del container)
WEST (right del container)
CENTER (center del container)
// Fig. 2.25: BorderLayoutDemo.java
// Demonstrating BorderLayout.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class BorderLayoutDemo extends JFrame implements ActionListener {
private JButton buttons[];
private final String names[] = { "Hide North", "Hide South",
"Hide East", "Hide West", "Hide Center" };
private BorderLayout layout;
// set up GUI and event handling
public BorderLayoutDemo()
{
super( "BorderLayout Demo" );
layout = new BorderLayout( 5, 5 ); // 5 pixel gaps
// get content pane and set its layout
Container container = getContentPane();
container.setLayout( layout );
// instantiate button objects
buttons = new JButton[ names.length ];
for ( int count = 0; count < names.length; count++ ) {
buttons[ count ] = new JButton( names[ count ] );
buttons[ count ].addActionListener( this );
}
// place buttons in BorderLayout; order not important
container.add( buttons[ 0 ], BorderLayout.NORTH );
container.add( buttons[ 1 ], BorderLayout.SOUTH );
container.add( buttons[ 2 ], BorderLayout.EAST );
container.add( buttons[ 3 ], BorderLayout.WEST );
container.add( buttons[ 4 ], BorderLayout.CENTER );
setSize( 300, 200 );
setVisible( true );
} // end constructor BorderLayoutDemo
// handle button events
public void actionPerformed( ActionEvent event )
{
for ( int count = 0; count < buttons.length; count++ )
if ( event.getSource() == buttons[ count ] )
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
buttons[ count ].setVisible( false );
else
buttons[ count ].setVisible( true );
// re-layout the content pane
layout.layoutContainer( getContentPane() );
}
public static void main( String args[] )
{
BorderLayoutDemo application = new BorderLayoutDemo();
application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
}
} // end class BorderLayoutDemo
Righe 18 e 22: imposta come layout BorderLayout con 5-pixel orizzontali e verticali di spazio.
Rihe 33-37: inserisce i JButtons nelle regioni specificate dal BorderLayout.
Righe 45 e 47: quando I JButtons sono nascosti BorderLayout ridistribuisce il tutto.
4.5.9.3 GridLayout
Divide il container in una griglia contenente un certo numero di rige e colonne. I componenti
vengono aggiunti a partire da dalla cella in alto a sinistra. Procede left-to-fight fino a quando
l’ultima cella è piena.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Fig. 2.26: GridLayoutDemo.java
// Demonstrating GridLayout.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class GridLayoutDemo extends JFrame implements ActionListener {
private JButton buttons[];
private final String names[] =
{ "one", "two", "three", "four", "five", "six" };
private boolean toggle = true;
private Container container;
private GridLayout grid1, grid2;
// set up GUI
public GridLayoutDemo()
{
super( "GridLayout Demo" );
// set up layouts
grid1 = new GridLayout( 2, 3, 5, 5 );
grid2 = new GridLayout( 3, 2 );
// get content pane and set its layout
container = getContentPane();
container.setLayout( grid1 );
// create and add buttons
buttons = new JButton[ names.length ];
for ( int count = 0; count < names.length; count++ ) {
buttons[ count ] = new JButton( names[ count ] );
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
buttons[ count ].addActionListener( this );
container.add( buttons[ count ] );
}
setSize( 300, 150 );
setVisible( true );
} // end constructor GridLayoutDemo
// handle button events by toggling between layouts
public void actionPerformed( ActionEvent event )
{
if ( toggle )
container.setLayout( grid2 );
else
container.setLayout( grid1 );
toggle = !toggle;
// set toggle to opposite value
container.validate();
}
Riga 21: crea GridLayout grid1 con 2 righe e 3 colonne.
Riga 22: crea GridLayout grid2 con 3 righe e 2 colonne.
Righe 46 e 48: modifica il GridLayout corrennte quando l’utente preme il JButton.
4.5.10
JFrame
Finestre con title bar e border. È sottoclasse di java.awt.Frame e di
java.awt.Window. I componenti sono pesanti. L’utente può specificare tre operazioni alla
chiusura:
DISPOSE_ON_CLOSE
DO_NOTHING_ON_CLOSE
HIDE_ON_CLOSE
4.5.11
Menu
Consente all’utente di creare finestre con menu e contenuti nella barra dei menu (JMenuBar)
come insieme di JMenuItem.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Fig. 3.9: MenuTest.java
// Demonstrating menus
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MenuTest extends JFrame {
private final Color colorValues[] =
{ Color.BLACK, Color.BLUE, Color.RED, Color.GREEN };
private JRadioButtonMenuItem colorItems[], fonts[];
private JCheckBoxMenuItem styleItems[];
private JLabel displayLabel;
private ButtonGroup fontGroup, colorGroup;
private int style;
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// set up GUI
public MenuTest()
{
super( "Using JMenus" );
// set up File menu and its menu items
JMenu fileMenu = new JMenu( "File" );
fileMenu.setMnemonic( 'F' );
// set up About... menu item
JMenuItem aboutItem = new JMenuItem( "About..." );
aboutItem.setMnemonic( 'A' );
fileMenu.add( aboutItem );
aboutItem.addActionListener(
new ActionListener() {
// anonymous inner class
// display message dialog when user selects About...
public void actionPerformed( ActionEvent event )
{
JOptionPane.showMessageDialog( MenuTest.this,
"This is an example\nof using menus",
"About", JOptionPane.PLAIN_MESSAGE );
}
}
// end anonymous inner class
); // end call to addActionListener
// set up Exit menu item
JMenuItem exitItem = new JMenuItem( "Exit" );
exitItem.setMnemonic( 'x' );
fileMenu.add( exitItem );
exitItem.addActionListener(
new ActionListener() {
// anonymous inner class
// terminate application when user clicks exitItem
public void actionPerformed( ActionEvent event )
{
System.exit( 0 );
}
}
// end anonymous inner class
); // end call to addActionListener
// create menu bar and attach it to MenuTest window
JMenuBar bar = new JMenuBar();
setJMenuBar( bar );
bar.add( fileMenu );
// create Format menu, its submenus and menu items
JMenu formatMenu = new JMenu( "Format" );
formatMenu.setMnemonic( 'r' );
// create Color submenu
String colors[] = { "Black", "Blue", "Red", "Green" };
JMenu colorMenu = new JMenu( "Color" );
colorMenu.setMnemonic( 'C' );
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
colorItems = new JRadioButtonMenuItem[ colors.length ];
colorGroup = new ButtonGroup();
ItemHandler itemHandler = new ItemHandler();
// create color radio button menu items
for ( int count = 0; count < colors.length; count++ ) {
colorItems[ count ] =
new JRadioButtonMenuItem( colors[ count ] );
colorMenu.add( colorItems[ count ] );
colorGroup.add( colorItems[ count ] );
colorItems[ count ].addActionListener( itemHandler );
}
// select first Color menu item
colorItems[ 0 ].setSelected( true );
// add format menu to menu bar
formatMenu.add( colorMenu );
formatMenu.addSeparator();
// create Font submenu
String fontNames[] = { "Serif", "Monospaced", "SansSerif" };
JMenu fontMenu = new JMenu( "Font" );
fontMenu.setMnemonic( 'n' );
fonts = new JRadioButtonMenuItem[ fontNames.length ];
fontGroup = new ButtonGroup();
// create Font radio button menu items
for ( int count = 0; count < fonts.length; count++ ) {
fonts[ count ] = new JRadioButtonMenuItem( fontNames[ count ] );
fontMenu.add( fonts[ count ] );
fontGroup.add( fonts[ count ] );
fonts[ count ].addActionListener( itemHandler );
}
// select first Font menu item
fonts[ 0 ].setSelected( true );
fontMenu.addSeparator();
// set up style menu items
String styleNames[] = { "Bold", "Italic" };
styleItems = new JCheckBoxMenuItem[ styleNames.length ];
StyleHandler styleHandler = new StyleHandler();
// create style checkbox menu items
for ( int count = 0; count < styleNames.length; count++ ) {
styleItems[ count ] =
new JCheckBoxMenuItem( styleNames[ count ] );
fontMenu.add( styleItems[ count ] );
styleItems[ count ].addItemListener( styleHandler );
}
// put Font menu in Format menu
formatMenu.add( fontMenu );
// add Format menu to menu bar
bar.add( formatMenu );
// set up label to display text
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
displayLabel = new JLabel( "Sample Text", SwingConstants.CENTER );
displayLabel.setForeground( colorValues[ 0 ] );
displayLabel.setFont( new Font( "Serif", Font.PLAIN, 72 ) );
getContentPane().setBackground( Color.CYAN );
getContentPane().add( displayLabel, BorderLayout.CENTER );
setSize( 500, 200 );
setVisible( true );
} // end constructor
public static void main( String args[] )
{
MenuTest application = new MenuTest();
application.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
}
// inner class to handle action events from menu items
private class ItemHandler implements ActionListener {
// process color and font selections
public void actionPerformed( ActionEvent event )
{
// process color selection
for ( int count = 0; count < colorItems.length; count++ )
if ( colorItems[ count ].isSelected() ) {
displayLabel.setForeground( colorValues[ count ] );
break;
}
// process font selection
for ( int count = 0; count < fonts.length; count++ )
if ( event.getSource() == fonts[ count ] ) {
displayLabel.setFont(
new Font( fonts[ count ].getText(), style, 72 ) );
break;
}
repaint();
} // end method actionPerformed
} // end class ItemHandler
// inner class to handle item events from check box menu items
private class StyleHandler implements ItemListener {
// process font style selections
public void itemStateChanged( ItemEvent e )
{
style = 0;
// check for bold selection
if ( styleItems[ 0 ].isSelected() )
style += Font.BOLD;
// check for italic selection
if ( styleItems[ 1 ].isSelected() )
style += Font.ITALIC;
204
205
206
207
208
209
210
211
212
displayLabel.setFont(
new Font( displayLabel.getFont().getName(), style, 72 ) );
repaint();
}
} // end class StyleHandler
} // end class MenuTest
Riga 22: istanzia File JMenu.
Riga 26: istanzia "About..." JMenuItem in fileMenu.
Riga 34, 37, 38: quando l’utente seleziona About… JMenuItem, visualizza il messaggio nella
message dialog.
Riga 64: istanzia JMenuBar contenente un JMenus.
Riga 69: istanzia Format JMenu.
Righe 78-80: istanzia JRadioButtonMenuItems per Color JMenu.
Riga 96: separatore tra JMenuItems.
4.6 Manipolazione delle stringhe
Il metodo length calcola il numero di caratteri (unità codice) richiesti per una data stringa UTF16. Esempio:
String greeting = "Hello";
int n = greeting.length(); //
risultato 5.
Per ottenere la lunghezza effettiva in (punti codice) è necessario chiamare la funzione
codePointCount. Esempio:
int cpCount = greeting.codePointCount(0, greeting.length());
La chiamata a s.charAt(n) ritorna l’unità codice alla posizione n dove n varia tra 0 e
s.length() – 1. Esempio:
char first = greeting.charAt(0); // first is 'H'
char last = greeting.charAt(4); // last is 'o‘
Per prendere l’i-esimo punto codice usare il comando:
int index = greeting.offsetByCodePoints(0, i);
int cp = greeting.codePointAt(index);
Si può estrarre una sottostringa da una stringa più grande con il metodo:
substring(indice_partenza,numero_caratteri)
Esempio:
String greeting = "Hello";
String s = greeting.substring(0, 3);
// viene creata al stringa s che contiene "Hel".;
Il secondo parametro è il numero di caratteri che si desidera estrarre a partire dalla posizione che
viene passata attraverso il primo parametro.
Si può trasformare una stringa in un’altra. Esempio:
greeting = greeting.substring(0, 3) + "p!";
Questo comando trasforma Hello in Help! :
String a = “Hello";
String b = “world";
String message = a+b; // conterrà “Helloworld”
int eta = 13;
String v = "PG" + eta;
Questo imposta la stringa v al valore "PG13".
Per testare se due stringhe sono uguali s.equals(t):
"Hello".equals(greeting)
"Hello".equalsIgnoreCase("hello")
Non usare l’oparatore == per testare se due stringhe sono uguali! L’operatore determina solo se le
due stringhe sono memorizzate nella stessa locazione.
String greeting = "Hello"; //inizializa greeting
if (greeting == "Hello") . . . // forse vero
if (greeting.substring(0, 3) == "Hel") . . . // forse falso
Restituisce il carattere all’indice index:
char charAt(int index)
Restituisce un vaolre negativo se la stringa segue (nell’ordinamento lessicografico) la stringa altra.
Restituisce un valore positivo se la stringa segue la stringa altra in ordine lessicografico. Restituisce
0 se le due stringhe sono uguali:
int compareTo(String altra)
Restituisce true se la stringa termina con suffix:
boolean endsWith(String suffix)
Restituisce l’inizio della prima sottostringa uguale alla stringa str a partire dall’indice 0 o a partire
dall’indice fromIndex, se str non esiste restituisce -1:
int indexOf(String str)
int indexOf(String str, int fromIndex)
Restituisce l’inizio dell’ultima sottostringa uguale alla stringa str a partire dall’indice 0 o a partire
dall’indice fromIndex, se str non esiste restituisce -1:
int lastIndexOf(String str)
int lastIndexOf(String str, int fromIndex)
Restituisce la lunghezza della stringa:
int length()
Restituisce una nuova stringa che viene ottenuta rimpiazzando ogni valore di oldString in
newString Si può passare String o l’oggetto StringBuilder objects:
String replace(CharSequence oldString, CharSequence newString)
Restituisce true se la stringa inizia con il prefisso prefix.
boolean startsWith(String prefix)
Restituisce una nuova stringa che consiste di tutti i caratteri a partire da beginIndex fino alla fine
della stringa oppure fino ad exnIndex-1:
String substring(int beginIndex)
String substring(int beginIndex, int endIndex)
Meodi che trasformano rispettivamente in caratteri minuscoli e maiuscoli una stringa:
String toLowerCase()
String toUpperCase()
Restituisce una nuova stringa eliminando tutti gli spazi iniziali e finali dalla stringa originale.
String trim()
Indice
1
Fondamenti................................................................................................................................... 3
1.1
Linguaggi di programmazione ............................................................................................. 3
1.2
Teoria della programmazione .............................................................................................. 4
1.3
Elementi di programmazione ............................................................................................... 6
1.3.1
Tipizzazione ................................................................................................................. 6
1.3.2
Operatori ...................................................................................................................... 6
1.3.3
Espressioni condizionali .............................................................................................. 6
1.3.4
Cicli e espressioni iterative .......................................................................................... 8
1.3.5
Funzioni ....................................................................................................................... 9
1.3.6
SubProcedure ............................................................................................................... 9
1.3.7
Commenti al codice ................................................................................................... 10
1.4
Cenni di programmazione ad oggetti................................................................................. 10
1.5
Cenni sulle librerie ............................................................................................................. 12
2
Linguaggio di programmazione JAVA...................................................................................... 13
2.1
Struttura della piattaforma ................................................................................................. 13
2.2
Struttura del codice ............................................................................................................ 14
2.3
Alfabeto e dizionario .......................................................................................................... 16
2.4
Tipi di dati.......................................................................................................................... 16
2.4.1
Interi........................................................................................................................... 17
2.4.2
Virgola mobile ........................................................................................................... 17
2.4.3
Char ............................................................................................................................ 18
2.4.4
Boolean ...................................................................................................................... 18
2.4.5
Tipi enumerati............................................................................................................ 18
2.4.6
Stringhe ...................................................................................................................... 19
2.4.7
Dichiarazione di variabili, costanti e inizializzazione ............................................... 19
2.5
Operatori ............................................................................................................................ 21
2.5.1
Algebrici..................................................................................................................... 21
2.5.2
Relazionali ................................................................................................................. 23
2.5.3
Logici ......................................................................................................................... 23
2.5.4
Bitwise ....................................................................................................................... 24
2.5.5
Precedenze operazionali............................................................................................. 24
2.6
Array .................................................................................................................................. 25
2.6.1
Parametri del metodo main....................................................................................... 30
2.6.2
Array anonimi ............................................................................................................ 31
2.6.3
Array multidimensionali ............................................................................................ 31
2.7
Input\Output ....................................................................................................................... 34
2.7.1
Input ........................................................................................................................... 34
2.7.2
Output......................................................................................................................... 35
2.8
Espressioni condizionali .................................................................................................... 36
2.9
Espressioni iterative ........................................................................................................... 37
2.10 Label (etichette) ................................................................................................................. 39
3
JAVA e OOP.............................................................................................................................. 40
3.1
Incapsulamento .................................................................................................................. 41
3.2
Ereditarietà ......................................................................................................................... 41
3.2.1
Le classi...................................................................................................................... 42
3.2.2
I costruttori................................................................................................................. 44
3.2.3
I metodi ...................................................................................................................... 46
3.2.4
I modificatori.............................................................................................................. 47
3.3
Campi e metodi statici........................................................................................................ 48
3.3.1
Visibilità delle dichiarazioni ...................................................................................... 50
3.4
Overloading........................................................................................................................ 52
3.5
Passaggio dei parametri ..................................................................................................... 52
3.5.1
Passare gli array ai metodi ......................................................................................... 54
3.5.2
Liste di argomenti a lunghezza variabile ................................................................... 55
3.6
Tipi Enumerativi e classi.................................................................................................... 55
3.7
Garbage collection ed il metodo finalize......................................................... 55
3.8
Ereditarietà e superclassi.................................................................................................... 56
3.8.1
Applicazione pratica .................................................................................................. 58
3.9
Cenni di polimorfismo ....................................................................................................... 61
3.9.1
Binding dinamico ....................................................................................................... 62
3.9.2
Classi e metodi finali.................................................................................................. 63
3.9.3
Casting ....................................................................................................................... 63
3.9.4
Classe Object.............................................................................................................. 64
3.9.5
Classi astratte ............................................................................................................. 66
3.9.6
Interfacce.................................................................................................................... 69
3.9.7
Interfacce API ............................................................................................................ 73
3.10 Gestione delle eccezioni..................................................................................................... 73
3.10.1
Eccezioni in JAVA..................................................................................................... 74
3.10.2
Il blocco try ............................................................................................................. 77
3.10.3
Scatenare eccezioni: throw...................................................................................... 78
3.10.4
Catturare l’eccezione: catch ................................................................................... 79
3.10.5
Costruttori, Finalizzatori............................................................................................ 79
3.10.6
Ereditarietà ................................................................................................................. 80
3.10.7
Il blocco finally.................................................................................................... 80
3.10.8
printStackTrace e getMessage.................................................................... 81
3.11 Esercizi............................................................................................................................... 82
4
JAVA: Package .......................................................................................................................... 83
4.1.1
Importazione statica ................................................................................................... 84
4.1.2
Creazione di package ................................................................................................. 84
4.1.3
Rintracciare le classi: classpath.......................................................................... 84
4.2
Package math.................................................................................................................... 85
4.3
Package IO ........................................................................................................................ 86
4.3.1
Classi Byte Stream..................................................................................................... 88
4.3.2
Input e Output su byte stream .................................................................................... 89
4.3.3
Classe System ............................................................................................................ 90
4.3.4
File ............................................................................................................................. 92
4.3.4.1
Leggere un File usando un byte stream ............................................................ 93
4.3.4.2
Scrivere su un File usando un byte stream......................................................... 94
4.3.4.3
Input sequenziale da file .................................................................................... 94
4.3.4.4
Output sequenziale da file .................................................................................. 95
4.3.4.5
Leggere e Scrivere Dati binari ........................................................................... 95
4.3.4.6
Stream basati sui caratteri .................................................................................. 96
4.3.4.7
Input da console usando uno stream di caratteri................................................ 96
4.3.4.8
File I/O usando uno stream di caratteri.............................................................. 97
4.3.4.9
Input da File ....................................................................................................... 97
4.3.4.10 Output su File..................................................................................................... 98
4.3.4.11 La classe FileWriter.................................................................................... 99
4.3.4.12 La classe FileReader.................................................................................. 100
4.3.4.13 Riassumendo… ................................................................................................ 100
4.3.5
Random Access file ................................................................................................. 101
4.3.6
Stream di Oggetti ..................................................................................................... 103
4.3.6.1
Scrittura di oggetti............................................................................................ 103
4.3.6.2
Lettura di oggetti.............................................................................................. 104
4.3.7
Gestione dei file ....................................................................................................... 104
4.3.8
Esercizi..................................................................................................................... 106
4.4
Package net .................................................................................................................... 106
4.4.1
Un semplice Server Stream Socket .......................................................................... 107
4.4.2
Un semp lice Client Stream Socket .......................................................................... 110
4.5
Package awt e swing .................................................................................................... 112
4.5.1
Frame ....................................................................................................................... 113
4.5.1.1
Visualizzare informazioni in un Frame............................................................ 115
4.5.1.2
Aggiungere un JPanel ...................................................................................... 115
4.5.2
Oggetti Graphics ...................................................................................................... 116
4.5.3
Classe Color.......................................................................................................... 117
4.5.4
Forme ....................................................................................................................... 118
4.5.5
Gestione degli eventi................................................................................................ 120
4.5.5.1
Alcune classi evento del package java.awt.event.................................. 120
4.5.5.2
Interfaccie Event- listener del package java.awt.event .......................... 121
4.5.6
Gerarchia bottoni Swing .......................................................................................... 125
4.5.7
Eventi del mouse ...................................................................................................... 126
4.5.8
Classi Adapter.......................................................................................................... 126
4.5.9
Layout manager........................................................................................................ 127
4.5.9.1
FlowLayout ...................................................................................................... 127
4.5.9.2
BorderLayout............................................................................................. 129
4.5.9.3
GridLayout.................................................................................................. 130
4.5.10
JFrame ...................................................................................................................... 131
4.5.11
Menu ........................................................................................................................ 131
4.6
Manipolazione delle stringhe ........................................................................................... 135

Documenti analoghi

Alcune note sul linguaggio Java - Dipartimento di Matematica e

Alcune note sul linguaggio Java - Dipartimento di Matematica e Gli spazi bianchi (spazi, caratteri di tabulazione, caratteri di nuova riga) sono ignorati dal compilatore. Attenzione però che un identificatore non può contenere spazi, ovvero studentestraniero...

Dettagli