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