Caricare documenti e articoli online 
INFtub.com è un sito progettato per cercare i documenti in vari tipi di file e il caricamento di articoli online.


 
Non ricordi la password?  ››  Iscriviti gratis
 

Cosa è Java (what)

informatica



Cosa è Java (what)

Java è un linguaggio di programmazione nato negli anni novanta, e destinato a diventare in breve tempo, il linguaggio più utilizzato in assoluto. Nato per puri scopi di ricerca in ambito universitario, e non come in molti altri casi, per volere di una multinazionale per conquistare nuove fette di mercato, Java si pone come un "super-linguaggio" che supera i limiti e i difetti che hanno altri linguaggi. La Sun Microsystems ha poi fatto in modo che dal linguaggio, si sia poi evoluta una serie di famose tecnologie (J.S.P., Servlet, E.J.B., Jini, R.M.I., etc.) che si stanno diffondendo in molti ambiti del mondo della programmazione. Con il termine "Java" ci si riferisce sia al linguaggio, sia alla tecnologia che racchiude tutte le tecnologie di cui sopra. In questo manuale ci limiteremo a parlare del linguaggio.

- Breve storia di Java (who, where & when)

Nel 1992 nasce il linguaggio Oak (quercia) prodotto della Sun Microsystems e realizzato da un gruppo di esperti e navigati sviluppatori, che formavano il cosiddetto "Green Team", ed erano capitanati da James Gosling, oggigiorno uno tra i più famosi e stimati "guru informatici" del pianeta. Sembra che il nome Oak derivi dal fatto che Gosling e i suoi colleghi, nel periodo in cui svilupparono questo nuovo linguaggio, avessero avuto come unica compagnia, quella di una quercia che si trovava proprio fuori la finestra dell’ufficio in cui lavoravano.



In un primo momento la Sun decise di destinare questo nuovo prodotto, alla creazione d’applicazioni complesse per piccoli dispositivi elettronici. Ma, in realtà, i tempi non erano ancora maturi per argomenti gli "elettrodomestici intelligenti".

Nel 1993, con l’esplosione di Internet (negli Stati Uniti), nasce l’idea di trasmettere codice eseguibile attraverso pagine HTML. La nascita delle C.G.I. (Common Gateway Interface) rivoluzionò il World Wide Web.

Il 23 maggio del 1995 Oak è ribattezzato ufficialmente col nome "Java". Il nome questa volta deriva dalla tipologia indonesiana di caffè di cui ha abusato il Green Team durante il periodo di sviluppo del linguaggio: per i nomi non hanno avuto molta fantasia! La Netscape annuncia contemporaneamente la scelta di dotare il suo celeberrimo browser della Java Virtual Machine (J.V.M.), la quale, rende di fatto, Java un linguaggio multipiattaforma. Inoltre la Sun Microsystems mette a disposizione gratuitamente il kit di sviluppo. Nel giro di pochi mesi i download diventarono migliaia e Java iniziò ad essere sulla bocca di tutti. Nei primi tempi sembrava che Java fosse il linguaggio giusto per creare siti Web spettacolari, ma in realtà, Java era ed è molto di più che un semplice strumento per rendere più piacevole alla vista la navigazione. Oggigiorno abbiamo altri strumenti per ottenere certi risultati con minor sforzo. Java è invece oggi un potente linguaggio di programmazione che sta diventando sempre di più la soluzione ideale ai problemi che accomunano aziende operanti in settori diversi (banche, software house, compagnie d’assicurazioni) come la sicurezza. La Sun ha investito molto in questi anni sul progetto Java ottenendo risultati straordinari.

- Perchè Java (why)

Java è stato creato proprio per superare i limiti che gli altri linguaggi hanno. In generale si andò nella direzione di un linguaggio potente, moderno, chiaro, ma soprattutto robusto e "funzionante". In molti punti chiave del linguaggio è favorita la robustezza piuttosto che la potenza.

Gli sviluppatori che hanno realizzato a Java, hanno cercato di realizzare il linguaggio preferito dai programmatori, arrichendolo delle caratteristiche migliori degli altri linguaggi, e privandolo delle peggiori e delle più pericolose.

- Caratteristiche di Java

Java ha alcune importanti caratteristiche che permetteranno a chiunque di apprezzarne i vantaggi.

Sintassi: è simile a quella del C e del C++, e questo non può far altro che facilitare la migrazione dei programmatori da due tra i più importanti ed utilizzati linguaggi esistenti. Chi non ha familiarità con questo tipo di sintassi può inizialmente sentirsi disorientato e confuso, ma ne apprezzerà presto l’eleganza e la praticità.

Gratuito: per scrivere applicazioni commerciali non bisogna pagare licenze a nessuno. Sun ha sviluppato questo prodotto e lo ha migliorato usufruendo anche dell’aiuto della comunità "open-source".

Robustezza: essa è derivante soprattutto da una gestione delle eccezioni chiara e funzionale, e ad un meccanismo automatico della gestione della memoria (Garbage Collection) che esonera il programmatore dall’obbligo di dover deallocare memoria quando ce n’è bisogno. Inoltre il compilatore Java, è molto "severo". Il programmatore è infatti costretto a risolvere tutte le situazioni "poco chiare", garantendo al programma maggiori chance di corretto funzionamento.

Libreria e standardizzazione: Java possiede una enorme libreria di classi standard che forniscono al programmatore la possibilità di operare su funzioni comuni di sistema come la gestione delle finestre, dei collegamenti in rete e dell’input/output. Il pregio fondamentale di queste classi sta nel fatto che rappresentano un’astrazione indipendente dalle piattaforme per un’ampia gamma di interfacce di sistema comunemente utilizzate. Inoltre se utilizziamo Java non avremo problemi di standardizzazione, come per esempio, compilatori che compilano in modo differente grazie alle specifiche dettate da Sun.

Indipendenza dall’architettura: grazie al concetto di macchina virtuale ogni applicazione, una volta compilata, potrà essere eseguita su di una qualsiasi piattaforma (per esempio un PC con sistema operativo Windows o una workstation Unix). Questa è sicuramente la caratteristica più importante di Java. Infatti, nel caso in cui si debba implementare un programma destinato a diverse piattaforme, non ci sarà la necessità di doverlo convertire radicalmente a seconda della piattaforma. E’ evidente quindi che la diffusione di Internet favorirà sempre di più la diffusione di Java. Recentemente una statistica ha affermato che questo linguaggio diventerà il più utilizzato in assoluto a livello mondiale nel 2003, operando uno straordinario sorpasso al C++.

Java Virtual Machine: Ciò che rende possibile di fatto l’indipendenza dalla piattaforma è la Java Virtual Machine, un software che svolge un ruolo da interprete (ma non solo) per le applicazioni Java. Più precisamente dopo aver scritto il nostro programma Java, prima bisogna compilarlo (per i dettagli riguardante l’ambiente e il processo di sviluppo, vi rimandiamo al paragrafo successivo). Otterremo così, non direttamente un file eseguibile (ovvero la traduzione in linguaggio macchina 828e47i del file sorgente che abbiamo scritto in Java), ma un file che contiene la traduzione del nostro listato in un linguaggio molto vicino al linguaggio macchina 828e47i detto "byte code". Una volta ottenuto questo file dobbiamo interpretarlo. A questo punto la J.V.M. interpreterà il byte code ed il nostro programma andrà finalmente in esecuzione. Quindi, se piattaforme diverse posseggono una Java Virtual Machine, ciò sarà sufficiente per renderle potenziali esecutrici di byte code. Infatti, da qualche anno a questa parte, i Web Browser più diffusi implementano all’interno di essi una versione della J.V.M., capace di mandare in esecuzione le applet Java. Ecco quindi svelato il segreto dell’indipendenza della piattaforma. Si parla di "macchina virtuale" perché in pratica questo software è stato implementato per simulare un hardware. Si potrebbe affermare che il linguaggio macchina sta ad un computer come il byte code sta ad una Java Virtual Machine. Oltre che permettere l’indipendenza dalla piattaforma, la J.V.M. permette a Java di essere un linguaggio multithreaded (caratteristica di solito dei sistemi operativi), ovvero capace di mandare in esecuzione più processi in maniera parallella. Inoltre garantisce dei meccanismi di sicurezza molto potenti, e la "supervisione" del codice della Garbage Collection

Orientato agli oggetti: Java ci fornisce infatti degli strumenti che praticamente ci "obbligano" a programmare ad oggetti. I paradigmi della programmazione ad oggetti (ereditarietà, incapsulamento, polimorfismo) sono più facilmente apprezzabili e comprensibili. Java è più chiaro e schematico che qualsiasi altro linguaggio orientato agli oggetti. Sicuramente, chi impara Java, potrà in un secondo momento accedere in modo più naturale alla conoscenza di altri linguaggi orientati agli oggetti, giacché, avrà di certo una mentalità più "orientata agli oggetti".

Semplice: in realtà relativamente a questo argomento bisogna fare una precisazione. Java è un linguaggio molto complesso considerandone la potenza e tenendo presente che ci obbliga ad imparare la programmazione ad oggetti. Ma, in compenso, si possono ottenere risultati insperati in un tempo relativamente breve. Apprezzeremo sicuramente le semplificazioni che ci offre Java durante questo corso. Abbiamo già accennato al fatto che non esiste l’aritmetica dei puntatori grazie all’implementazione della Garbage Collection. Provocatoriamente Bill Joy, vice-presidente della Sun microsystems negli anni in cui Java è stato creato, propose come nome sostitutivo di Oak il nome "C + + - -", a sottolineare che il nuovo linguaggio voleva essere un nuovo C++, ma senza le sue caratteristiche peggiori (o se vogliamo, senza le caratteristiche più difficili da utilizzare e quindi pericolose).

Sicurezza: ovviamente, avendo la possibilità di scrivere applicazioni interattive in rete, Java possiede anche delle caratteristiche di sicurezza molto efficienti. Come c’insegna la vita quotidiana nulla è certo al 100%. Esistono una serie di problemi relativi alla sicurezza di Java che ricercatori dell’Università di Princeton hanno scoperto e reso pubblici su Internet. Ma di una cosa possiamo essere certi: la Sun sta dando priorità assoluta proprio alla risoluzione di questi problemi. Intanto, oggi come oggi, Java è semplicemente il linguaggio "più sicuro" in circolazione.

Ambiente di sviluppo

Per scrivere un programma Java, abbiamo bisogno, in primo luogo, di un programma che ci permetta di scrivere del testo, ovvero di un Text Editor (come ad esempio il Note Pad di Windows, l’Edit del Dos, o il Vi di Unix). Abbiamo già accennato al fatto che Java è un linguaggio che si può considerare sia compilato che interpretato. Abbiamo quindi bisogno di un compilatore e di una Java Virtual Machine capace di interpretare il byte code generato dalla compilazione del codice Java. In questo corso utilizzeremo il famoso Java Development Kit, scaricabile gratuitamente dal sito www.java.sun.com con le relative note di installazione e documentazione, che ci offre sia un compilatore, che un ambiente interpretato per permetterci di lavorare in modo completo. Infatti il J.D.K. implementa una suite di applicazioni, come un compilatore, una J.V.M., un formattatore di documentazione, una J.V.M. per interpretare applet e così via. Si possono scaricare diverse versioni di questo software. Nel periodo in cui è stato scritto questo manuale (gennaio 2001), la versione più recente è la 1.3, ma, per questo corso, potremmo usare anche una versione qualsiasi dalla 1.2 in poi.

Esistono anche ambienti di sviluppo visuali che integrano Text Editor, compilatore, ed interprete come JBuilder della Borland, Visual Cafè della Symantec, Visual Age for Java della IBM o Forte for Java della stessa Sun. Ognuno di questi strumenti favorisce di sicuro una velocità di sviluppo maggiore, ma per quanto riguarda il periodo di apprendimento del linguaggio, è preferibile di certo scrivere tutto il codice senza aiuti da parte di questi strumenti, per non correre il rischio di non raggiungere una conoscenza "seria" di Java. C’è capitato spesso di conoscere persone che programmavano con questi strumenti da anni, senza avere chiari concetti di base come la gestione dei reference. Quindi, abituiamoci da subito ad avere a che fare con più finestre aperte contemporaneamente. Infatti, dopo aver installato correttamente il J.D.K. e settato opportunamente le eventuali variabili di ambiente (consultare la note di installazione), scriveremo il nostro codice sorgente su di un Text Editor, il Note Pad (blocco note) di Windows va benissimo. Salveremo il nostro file con suffisso ".java" (attenzione: se utilizziamo il Note Pad dobbiamo salvare il nostro file chiamandolo nomeFile.java ed includendo il nome tra virgolette in questa maniera "nomeFile.java"). Una volta ottenuto il nostro file Java dobbiamo aprire una Prompt di Dos. A questo punto dobbiamo spostarci nella cartella in cui è stato salvato il nostro file sorgente e compilarlo tramite il comando "javac nomeFile.java". Se la compilazione ha esito positivo verrà creato un file chiamato "nomeFile.class". In questo file, come abbiamo già detto, ci sarà la traduzione in byte code del file sorgente. A questo punto potrò mandare in esecuzione il programma invocando l’interpretazione della Java Virtual Machine tramite il comando "java nomeFile".

Primo approccio al codice

Diamo subito uno sguardo alla classica applicazione "Hello World". In questo modo inizieremo a familiarizzare con la sintassi e con qualche concetto fondamentale come quello di classe e di metodo. Vedremo anche come compilare e come mandare in esecuzione il nostro mini programma che stamperà a video il famigerato messaggio "Hello World!".

Ecco il listato:

class HelloWorld
2

7
}

N.B. I numeri non fanno parte dell’applicazione ma ci saranno utili per la sua analisi.

Riga 1: class HelloWorld

Dichiarazione della classe HelloWorld. Come vedremo ogni programma Java utilizza il concetto fondamentale di classe, che sarà trattato in dettaglio nel prossimo capitolo. Tutti i listati Java fanno uso di classi: l’intero codice, a parte le importazioni di librerie e le dichiarazioni d’appartenenza ad un package, è sempre incapsulato all’interno di classi.

Questo programma deve essere salvato esattamente col nome della classe, prestando attenzione anche alle lettere maiuscole o minuscole. Questa condizione è necessaria per mandare in esecuzione il nostro programma. 

Riga 2:

Questa parentesi graffa chiusa (ottenuta tenendo premuto il tasto ALT e scrivendo 125 con i tasti numerici che si trovano sulla destra della vostra tastiera, e poi rilasciando l’ALT) chiude l’ultima che è stata aperta, ovvero chiude il blocco di codice che definisce il metodo main.

Riga 7: }

Questa parentesi graffa invece chiude il blocco di codice che definisce la classe HelloWorld.

Unità didattica 1.5)

- Compilazione ed esecuzione del programma HelloWorld

Una volta riscritto il listato appena presentato sfruttando un Text Editor (supponiamo il Note Pad di Windows), dobbiamo salvare il nostro file in una cartella di lavoro, chiamata ad esempio "CorsoJava". Se si decide di utilizzare il Note Pad, il lettore presti attenzione al momento del salvataggio, ad includere il nome del file (che ricordiamo deve essere obbligatoriamente HelloWorld.java) tra virgolette in questo modo "HelloWorld.java". Tutto ciò si rende necessario, essendo Note Pad un editor di testo generico, e non un editor per Java, e quindi tende a salvare i file con il suffisso".txt".

A questo punto possiamo iniziare ad aprire una prompt di Dos ed a spostarci all’interno della nostra cartella di lavoro. Dopo essersi accertati che esista il file "HelloWorld.java" possiamo passare alla fase di compilazione. Se lanciamo il comando:

    javac HelloWorld.java

mandiamo il nostro file sorgente in input al compilatore che ci mette a disposizione il J.D.K.. Se al termine della compilazione non ci viene fornito nessun messaggio di errore, vuol dire che la compilazione ha avuto successo. A questo punto possiamo notare che nella nostra cartella di lavoro è stata creato un file di nome "HelloWorld.class". Questo è appunto il file sorgente tradotto in byte code, pronto per essere interpretato dalla J.V.M.: Se lanciamo il comando:

java HelloWorld

il nostro programma, se non sono lanciate eccezioni dalla J.V.M., dopo qualche attimo d’attesa relativo all’interpretazione del byte code compilato, verrà mandato in esecuzione stampando il tanto sospirato messaggio.

Java: Componenti fondamentali di un programma Java

Ci sono alcuni concetti che sono alla base della conoscenza di Java:

Classi

Oggetti

Membri:

    - Attributi (dati)

    - Metodi

Package

Di seguito sono fornite al lettore queste fondamentali nozioni, allo scopo di approcciare nel modo migliore alle caratteristiche del linguaggio che saranno presentate a partire dal prossimo modulo. Anteponiamo però una convenzione ausiliare.

- Convenzione per la programmazione Java:

L’apprendimento di un linguaggio orientato agli oggetti, può spesso essere molto travagliato, specialmente si hanno solide "radici procedurali". Abbiamo già accennato al fatto che Java, a differenza del C++, praticamente ci obbliga a programmare ad oggetti. Non ha senso imparare il linguaggio senza sfruttare il supporto che esso da alla programmazione ad oggetti. Chi impara il C++, ha un illusorio vantaggio. Può infatti permettersi di continuare ad utilizzare il paradigma procedurale della programmazione, e quindi imparare il linguaggio insieme ai concetti object oriented, comunque mettendoli inizialmente da parte.

Ci si può sempre esercitare creando funzioni e programmi chiamanti. In Java lo scenario si presenta più complesso. Inizieremo direttamente a creare applicazioni che saranno costituite da un certo numero di classi. Non basterà dare per scontato che "Java funziona così". Bisognerà invece cercare di chiarire ogni singolo punto poco chiaro, in modo tale da non creare troppa confusione, che a lungo andare, potrebbe scoraggiare il lettore. Per fortuna, con la sua chiarezza, Java ci aiuterà non poco, ed alla fine la soddisfazione di aver appreso in modo profondo determinati concetti, ci darà maggiore soddisfazione.

Proviamo da subito ad approcciare al codice in maniera schematica, introducendo una convenzione.

In questo testo distingueremo due ambiti quando scriviamo un’applicazione Java:

1) Ambito del compilatore (o compilativo, o delle classi)

2) Ambito della Java Virtual Machine (o esecutivo, o degli oggetti)

All’interno dell’ambito del compilatore, codificheremo la parte strutturale della nostra applicazione. L’ambito esecutivo ci servirà per definire il flusso di lavoro che dovrà essere eseguito.

Distinguere questi due ambiti ci risulterà utile per comprendere i ruoli che dovrebbero avere all’interno delle nostre applicazioni, i concetti che saranno introdotti in questo modulo.


Java: Le basi della programmazione object oriented: classi ed oggetti

I concetti di classe ed oggetto, sono strettamente legati. Esistono infatti, delle definizioni ufficiali derivanti dalla teoria della programmazione orientata agli oggetti, che di seguito vi presentiamo:

Definizione 1:

una classe è un’astrazione per un gruppo di oggetti che condividono le stesse caratteristiche e le stesse funzionalità.

Definizione 2:

un oggetto è un’istanza (ovvero, una creazione fisica) di una classe.

A questo punto, il lettore più "matematico" sicuramente avrà le idee un po’ confuse. Effettivamente definire il concetto di classe tramite la definizione di oggetto, e il concetto di oggetto tramite la definizione di classe, non è di certo il massimo della chiarezza! Ma la situazione è molto meno complicata di quella che può sembrare. Passiamo subito ad un esempio:

    class Punto

Sopra abbiamo definito una classe Punto. Evidentemente, la funzione di questa classe, è quella di astrarre il concetto di punto (a due dimensioni), tramite la definizione delle sue coordinate su di un piano cartesiano. Potremmo salvare questo listato in un file chiamato "Punto.java", compilarlo, ma non mandarlo in esecuzione (otterremmo un’eccezione). Infatti, in questa classe, è assente il metodo main, e quindi non sarà eseguita nessun’azione inizialmente.

Il concetto di classe è servito per definire come è fatto un generico punto, ma in questo modo, abbiamo solo definito il concetto di punto, in realtà non abbiamo ancora nessun punto da poter utilizzare.

Nel contesto della programmazione ad oggetti, una classe dovrebbe limitarsi a definire che struttura avranno gli oggetti che da essa saranno istanziati. "Istanziare", come abbiamo già visto, è il termine object oriented che sta per "creare fisicamente", ed il risultato di un’istanza deve essere un oggetto.

Istanzieremo allora oggetti dalla classe Punto, nel modo seguente:

1 class Principale

La classe Principale, dichiara alla riga 5, un oggetto di tipo Punto e lo chiama punto1 (così come nella classe Punto avevo dichiarato un intero e lo avevo chiamato x). Ma è solamente alla riga 6 che avviene l’istanza della classe Punto. La parola chiave new, di fatto, istanzia la classe Punto. Dalla riga 6 in poi possiamo utilizzare l’oggetto punto1. Precisamente, alle righe 7 e 8, settiamo le coordinate x ed y del punto1, rispettivamente ai valori interi 2 e 6. In pratica, sfruttando la definizione di punto che mi ha fornito la classe Punto, ho creato un oggetto di tipo Punto, che è individuato dal nome punto1. Notiamo anche, l’utilizzo dell’operatore "dot" (che in inglese significa "punto", ma nel senso del simbolo di punteggiatura ".") per accedere alle variabili x e y.

Alla riga 9, però, abbiamo dichiarato ed istanziato in una sola riga di codice un altro oggetto dalla classe Punto chiamandolo punto2. Abbiamo poi settato le coordinate di quest’ultimo rispettivamente a 0 e 1. Abbiamo infine stampato le coordinate di entrambi i punti.

Ora, le definizioni di classi ed oggetto dovrebbero risultare un po’ più chiare: la classe serve per definire come sarà fatto un oggetto, l’oggetto è la realizzazione fisica della classe. In questo esempio, abbiamo istanziato due oggetti diversi da una stessa classe. Entrambi questi oggetti sono punti, ma evidentemente sono punti diversi.

Questi concetti, come d’altronde tutti quelli relativi alla programmazione ad oggetti, sono già familiari a chiunque, giacchè derivanti dalla realtà che ci circonda. L’essere umano, per superare la complessità della realtà, raggruppa gli oggetti in classi. Per esempio nella nostra mente esiste il modello definito dalla classe Persona. Ma nella realtà esistono miliardi di oggetti di tipo Persona, ognuno dei quali ha caratteristiche uniche. Comprendere le definizioni 1 e 2 ora, non dovrebbe essere più un problema.

N.B.: tutti i concetti dell’object orientation, derivano dal mondo reale. Il concetto di classe nella programmazione, può essere paragonato al concetto di "idea" del mondo reale. Per esempio, volendo definire l’idea (astrazione) di un’auto,dovremmo parlare delle caratteristiche e delle funzionalità comuni ad ogni auto. Quindi, banalizzando, potremmo definire un’auto come"un mezzo di trasporto (quindi che si può muovere), con quattro ruote". Questa definizione può essere portata nella programmazione sotto forma di classe dichiarante come attributo un intero chiamato numeroRuote inizializzato a 4, ed un metodo che potremmo chiamare per semplicità muoviti(). Il lettore è rimandato alle prossime pagine per la comprensione e l’utilizzo del concetto di metodo.

class Auto

Ogni auto ha 4 ruote, e si muove. Una Ferrari Testarossa ed una Fiat Cinquecento, hanno entrambe 4 ruote e si muovono, anche se in modo diverso. La Testarossa e la Cinquecento sono da considerarsi oggetti della classe Auto, e nella realtà esisterebbero come oggetti concreti.

- Osservazione importante

Nel primo esempio abbiamo potuto commentare la definizione di due classi. Per la prima (la classe Punto), abbiamo sottolineato la sua caratteristica di essere un dato. E’ da considerarsi una parte strutturale dell’applicazione, e quindi svolge un ruolo essenziale nell’ambito compilativo. Nell’ambito esecutivo la classe Punto non ha un ruolo. Infatti, sono gli oggetti istanziati da essa che influenzano il flusso di lavoro del programma. Questo può definirsi come il caso standard. In un’applicazione object oriented, una classe dovrebbe limitarsi definire la struttura comune di un gruppo oggetti, non dovrebbe mai possedere né variabili né metodi. Infatti, la classe Punto, non possiede le variabili x e y, bensì, dichiarando le due variabili, definisce gli oggetti che da essa saranno istanziati, come possessori di quelle variabili. Notare che mai all’interno del codice è presente un’istruzione come:

    Punto.x

ovvero

    nomeClasse.nomeVariabile

(che tra l’altro produrrebbe un errore in compilazione) bensì:

    punto1.x

ovvero

    nomeOggetto.nomeVariabile

L’operatore ".", come spesso accade nell’informatica, è sinonimo di appartenenza, e quindi sono gli oggetti a possedere le variabili dichiarate nella classe (che tra l’altro verranno dette "variabili d’istanza"). Infatti, i due oggetti istanziati avevano valori diversi per x e y, il che significa che l’oggetto punto1 ha la sua variabile x e la sua variabile y, mentre l’oggetto punto2 ha la sua variabile x e la sua variabile y. Le variabili di punto1 sono assolutamente indipendenti dalle variabili di punto2. Giacché le classi non hanno membri (variabili e metodi), non eseguono codice e non hanno un ruolo nell’ambito esecutivo, e per quanto visto sono gli oggetti che sono i protagonisti assoluti di quest’ambito. Come spesso accade quando si approccia a Java, appena definita una nuova regola, subito si presenta l’eccezione (che se vogliamo dovrebbe confermarla!). Puntiamo infatti l’attenzione sulla classe Principale. Essa è invece una classe che esegue del codice contenuto all’interno dell’unico metodo main, che per quanto detto, è assunto per default come punto di partenza di un’applicazione Java. In qualche modo i creatori di Java, dovevano stabilire un modo per far partire il runtime di un programma. La scelta è stata fatta in base ad una questione pratica: un’applicazione scritta in C o C++, ha come punto di partenza per default un programma chiamante che si chiama proprio main. In Java i programmi chiamanti non esistono, ma esistono i metodi, ed in particolare i metodi statici, ovvero, dichiarati con un modificatore static. Questo modificatore molto potente sarà trattato in dettaglio nei prossimi moduli. Per ora limitiamoci a sapere che un membro dichiarato statico, appartiene alla classe, e tutti gli oggetti istanziati da essa condivideranno gli stessi membri. Giacché la classe Principale contiene un metodo, può eseguire del codice.

E’ bene che il lettore eviti di utilizzare il modificatore static nei primi tempi ed aspetti che "i tempi maturino", per non buttare via tempo prezioso. L’accenno fatto ad un argomento complesso come static, ha solamente lo scopo di chiarire un punto oscuro del nostro discorso.

Java: I metodi in Java

Nella definizione di classe, quando si parla di caratteristiche, in pratica ci si riferisce ai dati (variabili e costanti), mentre col termine funzionalità ci si riferisce ai metodi. Abbiamo già detto che metodo è sinonimo di azione. Quindi, affinché un programma esegua qualche istruzione, deve contenere metodi. Infatti è il metodo main, che per default, è il punto di partenza di ogni applicazione Java. Una classe senza metodo main, come la classe Punto, non può essere mandata in esecuzione, ma solo istanziata all’interno di un metodo di un’altra classe (nell’esempio precedente, nel metodo main della classe Principale). Il concetto di metodo è quindi anch’esso alla base della programmazione ad oggetti. Senza metodi, gli oggetti non potrebbero comunicare tra loro. Essi possono essere considerati messaggi che gli oggetti si scambiano. Inoltre rendono i programmi più leggibili e di più facile manutenzione, lo sviluppo più veloce e stabile, evitano le duplicazioni, e favoriscono il riuso del software.

Il programmatore che si avvicina a Java dopo esperienze nel campo della programmazione strutturata, spesso tende a confrontare il concetto di funzione con il concetto di metodo. Sebbene simili nella forma e nella sostanza, bisognerà tener presente, al momento dell’implementazione, che un metodo ha un utilizzo differente dalla funzione. Nella programmazione strutturata infatti, il concetto di funzione era alla base. Tutti i programmi erano formati da un cosiddetto programma chiamante e da un certo numero di funzioni. Queste avevano di fatto il compito di risolvere determinati "sotto-problemi" generati da un analisi di tipo Top-Down, allo scopo di risolvere il problema generale. Come vedremo più avanti, nella programmazione orientata agli oggetti, i "sotto-problemi" saranno invece risolti tramite i concetti di classe ed oggetto, che a loro volta, faranno uso di metodi.

È bene che il lettore cominci a distinguere nettamente due fasi per quanto riguarda i metodi: dichiarazione e chiamata.

Dichiarazione di un metodo:

La dichiarazione definisce un metodo, ecco la sintassi:

[modificatori] tipo_di_ritorno nome_del_metodo ([parametri])

dove:

  • modificatori: parole chiavi di Java che possono essere usate per modificare in qualche modo le funzionalità e le caratteristiche di un metodo.
  • tipo di ritorno: il tipo di dato che un metodo potrà restituire dopo essere stato chiamato.
  • nome del metodo: identificatore che sarà utilizzato al momento della chiamata del metodo.
  • parametri: dichiarazioni di variabili che potranno essere passate al metodo, e di conseguenza essere sfruttate nel corpo del metodo, al momento della chiamata.
  • corpo del metodo: insieme di comandi (statement) che verranno eseguiti quando il metodo sarà chiamato.

Per esempio viene presentata una classe chiamata Aritmetica che dichiara un banale metodo che somma due numeri :

class Aritmetica

Notare che: come modificatore il metodo presenta la parola chiave public. Si tratta di uno specificatore d’accesso che di fatto rende il metodo somma accessibile da altre classi. Precisamente i metodi di altre classi potranno, dopo aver istanziato un oggetto della classe Aritmetica, chiamare il metodo somma. Anche per gli specificatori d’accesso approfondiremo il discorso più avanti.

Il tipo di ritorno è un int, ovvero, un intero. Ciò significa che questo metodo avrà come ultima istruzione, un comando (return) che restituirà un numero intero.

Per quanto riguarda i parametri che verranno passati a questo metodo, sono due interi che saranno sommati all’interno del blocco di codice, e la loro somma restituita come risultato finale.

Concludendo, la dichiarazione di un metodo definisce quali azioni deve compiere il metodo stesso quando sarà chiamato.

- Chiamata di un metodo:

Presentiamo un’altra classe "eseguibile" (ovvero contenente il metodo main), che istanzia la classe Aritmetica e chiama il metodo somma:

class Uno

In questo caso notiamo subito che l’accesso al metodo somma è avvenuto sempre tramite l’operatore dot come nel caso dell’accesso alle variabili. Quindi, tutti i membri (attributi e metodi) pubblici definiti all’interno di una classe, saranno accessibili tramite un’istanza della classe stessa che sfrutta l’operatore dot, proprio con questo tipo di sintassi:

    nomeOggetto.nomeMetodo;

oppure

    nomeOggetto.nomeAttributo;

L’accesso al metodo di un oggetto provoca quindi l’esecuzione del relativo blocco di codice. In questo esempio quindi, abbiamo definito una variabile intera risultato che ha immagazzinato il risultato della somma. Se non avessimo fatto ciò, non avrebbe avuto senso definire un metodo che restituisse un valore, dal momento che non l’avremmo utilizzato in qualche modo! Da notare che avremmo anche potuto sostituire la riga 6, con:

    System.out.println(oggetto1.somma(5,6));

In questo modo, stampando a video il risultato, avremmo potuto verificare in fase di runtime del programma la reale esecuzione della somma.

Esistono anche metodi che non hanno parametri in input per esempio un metodo che somma sempre gli stessi numeri:

class AritmeticaFissa

oppure metodi che non hanno tipo di ritorno, ovvero che dichiarano come tipo di ritorno void (vuoto) come il metodo main. Infatti il metodo main, è il punto di partenza del runtime di un’applicazione Java, quindi non deve restituire niente non essendo chiamato esplicitamente da un altro metodo.

Esistono ovviamente anche metodi che non hanno né parametri in input né in output (tipo di ritorno void), come per esempio un ipotetico metodo stampa che visualizza a video sempre lo stesso messaggio:

class Saluti

Java: Stile di codifica

Il linguaggio Java:

è a schema libero

è case sensitive

supporta i commenti

supporta le parole chiave

ha delle regole per i tipi di dati e convenzioni per i nomi

Schema Libero:

Potremmo scrivere un intero programma in Java tutto su di un’unica riga, oppure andando a capo dopo ogni parola scritta: il compilatore compilerà ugualmente il nostro codice se esso è corretto. Ovviamente però, il lettore avrà difficoltà a capire il significato del codice! Esistono dei metodi standard di formattazione del codice, che facilitano la lettura un programma Java. Vi presentiamo di seguito i due più usati metodi di formattazione:

class Classe

Con questo stile (che viene utilizzato anche dai programmatori C), il lettore può capire subito dove la classe ha inizio e dove ha fine, dato che le parentesi graffe che delimitano un blocco di codice si trovano incolonnate. Stesso discorso per il metodo: risultano evidenti l’inizio, la fine e la funzionalità del metodo.

Un altro stile molto utilizzato (ma solo dai "javisti")è il seguente:

class Classe

per il quale valgono circa le stesse osservazioni fatte per il primo metodo.

Si raccomanda, per una buona riuscita del lavoro che sarà svolto in seguito dal lettore, una rigorosa applicazione di uno dei due stili appena presentati.

- Case sensitive:

Java è un linguaggio case sensitive, il che significa che fa distinzione tra lettere maiuscole e minuscole. Il programmatore alle prime armi tende a digerire poco questa caratteristica del linguaggio. Bisogna ricordarsi di non scrivere ad esempio class con lettera maiuscola, perché per il compilatore non significa niente. L’identificatore numeroLati è diverso dall’identificatore numerolati, quindi bisogna fare un po’ d’attenzione, e, specialmente nei primi tempi, avere un po’ di pazienza.

- Commenti:

Commentare opportunamente il codice implementato, è una pratica che dovrebbe essere considerata obbligatoria dal programmatore. Solo così infatti, le correzioni da apportare ad un programma saranno enormemente facilitate. Java supporta tre tipi diversi di commenti al codice:

1) // Commento su una sola riga

* Commento su

* più righe

* Commento per produrre

* la documentazione del codice

* in formato HTML

Nel primo caso tutto ciò che scriveremo su di una riga dopo aver scritto "//" non sarà preso in considerazione dal compilatore. Questa sintassi ci permetterà di commentare brevemente alcune parti di codice.

Il commento su più righe potrebbe essere utile ad esempio per la descrizione delle funzionalità di un programma. In pratica il compilatore non prende in considerazione tutto ciò che scriviamo tra "/*" ed "*/". Entrambi questi due primi tipi di commenti sono ereditati dal linguaggio C++. Ciò che invece rappresenta una grande novità, è il terzo tipo di commento. L’utilizzo in pratica è lo stesso del secondo tipo, e ci permette di fare commenti su più righe. In più, ci offre la possibilità di produrre in modo standard, in formato HTML, la documentazione tecnica del programma, sfruttando un comando chiamato "javadoc". Per esempi pratici d’utilizzo di questo strumento il lettore è rimandato alla fine di questo modulo.

- Regole per gli identificatori:

Gli identificatori (nomi) dei metodi, delle classi, degli oggetti, delle variabili, delle costanti e delle interfacce (concetto che avremo modo di apprezzare più avanti), hanno delle regole da rispettare.

Un identificatore non può coincidere con una parola chiave (keyword) di Java. Una parola chiave, è una parola che ha un certo significato per il linguaggio di programmazione. Nella seguente tabella sono riportate tutte le parole chiave di Java (versione 1.3 del JDK):

abstract   boolean  break    byte    case

catch  char class    const continue   

default    do   double   else  extends

false  final    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  true try  void  volatile 

while

Possiamo notare in questa tabella, alcune parole chiave che abbiamo già incontrato come per esempio int, public, void, return e class, e di cui già conosciamo il significato. Ovviamente non potremo chiamare una variabile class, oppure un metodo void. Possiamo notare anche le parole riservate goto e const, che non hanno nessun significato in Java, ma che non possono essere utilizzate come identificatori. Esse sono dette parole riservate (reserved words).

2)In un identificatore:

il primo carattere può essere A-Z, a-z, _, $

il secondo ed i successivi possono essere A-Z, a-z, _, $, 0-9

Quindi: "a2" è un identificatore valido, mentre "2a" non lo è.

N.B.: in realtà, è possibile che alcuni compilatori, compresi quelli dei J.D.K., accettino altri simboli per comporre gli identificatori. Per esempio i simboli "£", e "€", sono tollerati. Tuttavia, il lettore troverà difficilmente altri testi che accenneranno a ciò che è stato appena fatto osservare. E’ in ogni caso sconsigliabile non seguire le direttive Sun. Ma il dovere di cronaca .

- Regole facoltative per gli identificatori e convenzioni per i nomi

Utilizzando gli identificatori tenendo presenti le due regole che abbiamo appena descritto, non otterremo mai errori in compilazione relativi. Ma esistono delle direttive standard fornite dalla Sun, per raggiungere uno standard anche nello stile di implementazione. E’ importantissimo utilizzare queste direttive in un linguaggio tanto standardizzato quanto Java.

Gli identificatori è bene che siano significativi. Infatti, se scriviamo un programma utilizzando la classe "a", che definisce le variabili "b", "c", "d" e il metodo "q", sicuramente ridurremo la comprensibilità del programma stesso. Di solito l’identificatore di una variabile, è composto da uno o più sostantivi, per esempio "numeroLati", o "larghezza". Gli identificatori dei metodi di solito conterranno dei verbi, ad esempio "stampaNumero", o "somma". Questa è una direttiva che è dettata più che da Sun dal buon senso.

Esistono delle convenzioni per gli identificatori, così come in molti altri linguaggi. In Java sono semplicissime:

Un identificatore di una classe (o di un’interfaccia) deve sempre iniziare per lettera maiuscola. Se composto di più parole, queste non si possono separare perché il compilatore non può intuire le nostre intenzioni. Come abbiamo notato in precedenza, bisogna invece unire le parole in modo tale da formare un unico identificatore, e fare iniziare ognuna di esse con lettera maiuscola. Esempi di identificatori per una classe potrebbero essere:

- Persona

- MacchinaDaCorsa

- FiguraGeometrica

Un identificatore di una variabile deve sempre iniziare per lettera minuscola. Se l’identificatore di una variabile deve essere composto di più parole, valgono le stesse regole che valgono per gli identificatori delle classi. Quindi, esempi di identificatori per una variabile potrebbero essere:

- pesoSpecifico

- numeroDiMinutiComplessivi

- x

Per un identificatore di un metodo valgono le stesse regole che valgono per gli identificatori delle variabili. Potremo in ogni caso sempre distinguere un identificatore di una variabile da un identificatore di un metodo, giacché quest’ultimo è sempre seguito da parentesi tonde. Inoltre, per quanto già affermato, il nome di un metodo dovrebbe contenere almeno un verbo. Quindi, esempi di identificatori per un metodo potrebbero essere:

- sommaDueNumeri(int a, int b)

- cercaUnaParola(String parola)

- stampa()

Gli identificatori delle costanti invece, si devono distinguere nettamente dagli altri, e tutte le lettere dovranno essere maiuscole. Se l’identificatore è composto di più parole, queste si separano con underscore (simbolo di sottolineatura). Per esempio:

- NUMERO_LATI_DI_UN_QUADRATO

- PI_GRECO

Java: Tipi di dati primitivi

Java definisce otto tipi di dati primitivi:

tipi interi: byte, short, int, long

tipi floating point (o a virgola mobile): float e double

tipo testuale: char

tipo logico-booleano: boolean

- Tipi di dati interi, casting e promotion

I tipi di dati interi sono ben quattro: byte, short, int, long. Essi condividono la stessa funzionalità (tutti possono immagazzinare numeri interi positivi o negativi), ma differiscono per quanto riguarda il proprio intervallo di rappresentazione.

Infatti, un byte può immagazzinare un intero utilizzando un byte (otto bit), uno short due byte, un int quattro byte ed un long otto byte. Lo schema seguente riassume dettagliatamente i vari intervalli di rappresentazione:

byte 8 bit -128,..,+127

    short 16 bit -32.768,..,+32.767

    int 32 bit -2.147.483.648,..,+2.147.483.647

long 64 bit -9.223.372.036.854.775.808,..,9.223.372.036.854.775.807

Per immagazzinare un intero, si possono utilizzare tre forme: decimale, ottale ed esadecimale. Per la notazione ottale basta anteporre al numero intero uno 0 (zero), mentre per la notazione esadecimale, zero e x (indifferentemente maiuscola o minuscola). Ecco qualche esempio d’utilizzo di tipi interi:

- byte b=10; //notazione decimale

- short s=022; //notazione ottale

- int i=1000000000; //notazione decimale

- long l=0x12acd; //notazione esadecimale

C’è da fare però una precisazione. Facciamo un esempio:

    byte b=50;

Questo statement è corretto. Il numero intero 50 è tranquillamente compreso nell’intervallo di rappresentazione di un byte che va da –128 a +127. Il compilatore determina la grandezza del valore numerico, e controlla se è compatibile con il tipo di dato dichiarato. Allora, consideriamo quest’altro statement:

    b=b*2;

Ciò darà luogo ad un errore in compilazione! Infatti, il compilatore non eseguirà l’operazione di moltiplicazione per controllare la compatibilità con il tipo di dato dichiarato, bensì, promuoverà automaticamente la cifra che si trova alla destra dell’espressione di assegnazione ad int, dato che di default assumerà che 2 sia un int. Quindi, se 50*2 è un int, non può essere immagazzinato in b che è un byte. Il fenomeno appena descritto è conosciuto sotto il nome di "promozione automatica nelle espressioni". Ma, evidentemente, 50*2 è immagazzinabile tranquillamente in un byte. Esiste una tecnica per forzare una certa quantità, ad essere immagazzinata in un certo tipo di dato. Questa tecnica è nota con il nome di "cast" (o "casting"). La sintassi da utilizzare per risolvere il nostro problema è:

    b=(byte)b*2;

In questo modo, il compilatore sarà avvertito che un’eventuale perdita di precisione è calcolata e sotto controllo.

Bisogna essere però molto prudenti nell’utilizzare il casting in modo corretto. Infatti se scrivessimo:

    b=(byte)128;

il compilatore non segnalerà nessun tipo di errore. In realtà, siccome il cast agisce troncando i bit in eccedenza (nel nostro caso, siccome un int utilizza 32 bit, mentre un byte solo 8, saranno troncati i primi 24 bit dell’int), la variabile b avrà il valore di –128 e non di 128!

Notare che, se utilizziamo una variabile long, a meno di cast espliciti, sarà sempre inizializzata con un intero. Quindi se scriviamo:

    long l=2000;

Dobbiamo tener ben presente che 2000 è un int per default, ma il compilatore non ci segnalerà errori perché un int in un long può essere tranquillamente immagazzinato. Per la precisione dovremmo scrivere:

    long l=(long)2000;

oppure con una sintassi equivalente:

    long l=2000L;

Quindi, un cast a long, è possibile anche posponendo una "elle" maiuscola o minuscola dopo il valore intero assegnato. Si preferisce utilizzare la notazione maiuscola, dato che una "elle" minuscola si può confondere con un numero "uno" in alcuni ambienti di sviluppo. Notare che saremo obbligati ad un cast a long, nel caso in cui volessimo assegnare alla variabile l un valore fuori dell’intervallo di rappresentazione di un int.

- Tipi di dati a virgola mobile, casting e promotion

Java per i valori floating point utilizza lo standard di decodifica IEEE-754. I due tipi che possiamo utilizzare sono:

    float 32 bit

    double 64 bit

E’ possibile utilizzare la notazione esponenziale o ingegneristica (la "e" può essere sia maiuscola sia minuscola). Per quanto riguarda cast e promotion, la situazione cambia rispetto al caso dei tipi interi. Il default è double e non float. Ciò implica che se vogliamo assegnare un valore a virgola mobile ad un float non possiamo fare a meno di un cast. Per esempio la seguente riga di codice, porterebbe ad un errore in compilazione:

    float f=3.14;

Anche in questo caso, il linguaggio ci viene incontro permettendoci il cast con la sintassi breve:

    float f=3.14F;

La "effe" può essere sia maiuscola sia minuscola.

Esiste, per quanto ridondante, anche la forma contratta per i double, infatti:

    double d=10.12E24;

è equivalente a scrivere:

    double d=10.12E24D;

N.B. : ciò che sembra evidente è che il nostro non sia il linguaggio ideale per fare dei calcoli! Un altro classico problema che potrebbe presentarsi, è quando si dividono due numeri interi. Il risultato infatti sarà sicuramente un numero intero! Consiglio saggio: in caso di espressioni aritmetiche, utilizzare solamente double. In altri tempi questo consiglio sarebbe stato considerato folle .

- Tipo di dato logico - booleano

Il tipo di dato boolean, utilizza solo un bit per memorizzare un valore, e gli unici valori che può immagazzinare sono true e false. Per esempio:

    boolean b=true;

- Tipo di dato primitivo letterale

Il tipo char ci permette di immagazzinare caratteri (uno per volta). Utilizza l’insieme Unicode per la decodifica. Unicode è uno standard a 16 bit, che contiene tutti i caratteri della maggior parte degli alfabeti del mondo. Fortunatamente l’insieme ASCII, che probabilmente è più familiare al lettore, è un sottoinsieme dello Unicode. I primi 256 caratteri dei due insiemi coincidono. Per maggiori informazioni, visitare il sito www.unicode.org .

Possiamo assegnare ad un char un qualsiasi carattere che si trova sulla nostra tastiera, oppure passargli direttamente una cifra Unicode in esadecimale che identifica univocamente un determinato carattere, anteponendo ad essa il prefisso "\u". In qualsiasi caso, dobbiamo comprendere tra apici singoli, il valore da assegnare. Ecco qualche esempio:

    char primoCarattere=’a’;

    char car=’@’;

    char carattereUnicodeNonIdentificato=’\uABC8’;

Esiste anche la possibilità di immagazzinare caratteri di escape come:

    ‘\n’ che equivale ad andare a capo

    ‘\\’ che equivale ad un solo \

    ‘\t’ che equivale ad una tabulazione

    ‘\’’ che equivale ad un apice singolo

    ‘\"’ che equivale ad un doppio apice (virgolette)

N.B.: notare che è possibile inserire un carattere in una certa espressione aritmetica, per esempio, sommare un char con un int. Infatti ad ogni carattere corrisponde un numero intero.


Java: Tipi di dati non primitivi: reference

Abbiamo già visto come istanziare oggetti da una certa classe. Dobbiamo prima dichiarare un oggetto di tale classe con una sintassi di questo tipo:

    NomeClasse nomeOggetto;

per poi istanziarlo utilizzando la parola chiave new. Dichiarare un oggetto quindi è del tutto simile a dichiarare un tipo di dato primitivo. Il "nome" che diamo ad un oggetto è detto "reference".

Infatti, non si sta parlando di una variabile tradizionale bensì di un puntatore. Possiamo definire un puntatore come una variabile che contiene un indirizzo in memoria. C’è una sottile e potente differenza tra la dichiarazione di un tipo di dato primitivo ed uno non primitivo. Consideriamo ora un esempio, partendo dalla definizione di una classe che astrae in maniera banale il concetto di data.

class Data

Data sarà quindi un tipo di dato non primitivo (astratto) per il nostro esempio. Come tipo di dato primitivo consideriamo un double. Consideriamo le seguenti righe di codice, supponendo che si trovino all’interno di un metodo main di un’altra classe:

    double unNumero=5.0;

    Data unGiorno=new Data();

Graficamente potremmo immaginare la situazione in memoria con questo tipo di schematizzazione:

La differenza pratica tra un reference ed una variabile, è evidente nelle assegnazioni. Consideriamo il seguente frammento di codice:

    double unNumero=5.0;

    double unAltroNumero=unNumero;

    Data unGiorno=new Data();

    Data unAltroGiorno=unGiorno;

La variabile unAltroNumero, assumerà lo stesso valore della variabile unNumero, ma, le due variabili, rimarranno indipendenti l’una dall’altra. Infatti, il valore della variabile unNumero, sarà copiato nella variabile unAltroNumero. Se il valore di una delle due variabili sarà successivamente modificato, l’altra variabile non apporterà modifiche al proprio valore.

Invece, il reference unAltroGiorno, semplicemente assumerà il valore (cioè l’indirizzo) del reference unGiorno. Ciò significa che unAltroGiorno punterà allo stesso oggetto cui punta unGiorno. Ecco la situazione rappresentata graficamente:

Quindi, se successivamente sarà apportata una qualche modifica tramite uno dei due reference all’oggetto comune, ovviamente questa sarà verificabile anche tramite l’altro reference. Per intenderci:

    unGiorno.anno

è sicuramente equivalente a:

    unAltroGiorno.anno

 - Passaggio di parametri per valore

Come abbiamo già accennato nel precedente capitolo il passaggio di parametri in Java avviene sempre per valore. Ciò significa che quando viene invocato un metodo che come parametro prende in input una variabile, al metodo stesso viene passato solo il valore (una copia) della variabile, che quindi rimane immutata anche dopo l'esecuzione del metodo. Per esempio consideriamo la classe:

    class CiProvo

il seguente frammento di codice:

    CiProvo ogg = new CiProvo();

    int numero = 10;

    ogg.cambiaValore(numero);

    System.out.println(“il valore del numero è ” + numero);

produrrà il seguente output:

    il valore del numero è 10

Infatti il parametro valore del metodo cambiaValore(), nel momento in cui è stato eseguito il metodo, non coincideva con la variabile numero, bensì immagazzinava solo la copia del suo valore (10). Quindi ovviamente la variabile numero non è stata modificata.

Stesso discorso vale per i tipi reference: viene sempre passato il valore del reference, ovvero, l'indirizzo in memoria. Per esempio consideriamo la seguente classe:

    class CiProvoConIReference

il seguente frammento di codice:

    CiProvoConIReference ogg = new CiProvoConIReference();

    Data dataDiNascita = new Data();

    dataDiNascita.giorno = 26;

    dataDiNascita.mese = 1;

    dataDiNascita.anno = 1974;

    ogg.cambiaReference(dataDiNascita);

    System.out.println(“Data di nascita = ” + dataDiNascita.giorno

+ “-”+ dataDiNascita.mese + “-” +dataDiNascita.anno );

produrrà il seguente output:

    Data di nascita = 26-1-1974

Quindi valgono le stesse regole anche per i reference.

N.B.: Attenzione che se il metodo cambiaReference() avesse cambiato i valori delle variabili d'istanza dell'oggetto avremmo avuto un output differente. Riscriviamo il metodo in questione:

    public void cambiaReference(Data data)

Il fatto che il passaggio avvenga sempre per valore, garantisce che un oggetto possa essere modificato, e contemporaneamente, si è certi che dopo la chiamata del metodo il reference punti sempre allo stesso oggetto.

N.B.: altri linguaggi come il C, permettono anche il passaggio di parametri “per riferimento”. In quel caso al metodo viene passato l'intero riferimento, non solo il suo indirizzo, con la conseguente possibilità di poter mutarne l'indirizzamento. Java ha scelto ancora una volta la strada della robustezza e della semplicità, favorendola alla potenza del linguaggio.

Java: Introduzione alla libreria standard

Come già accennato più volte, Java possiede un’enorme e lussuosa libreria di classi standard, che costituisce uno dei punti di forza del linguaggio. Essa è organizzata in vari package (letteralmente pacchetti, fisicamente cartelle) che raccolgono le classi a seconda del campo d’utilizzo. I principali package sono:

 java.io raccoglie classi per realizzare l’input – output in Java

 java.awt raccoglie classi per realizzare interfacce grafiche, come ad esempio la classe Button

 java.net contiene classi per realizzare connessioni come ad esempio la classe Socket

 java.applet oltre a tre interfacce, vi è contenuta l’unica classe Applet che ci permette di realizzare applet

 java.util raccoglie classi d’utilità come la classe Date

 java.lang è il package che contiene le classi nucleo del linguaggio come ad esempio la classe System e la classe String.

- Il comando import:

Per utilizzare una classe della libreria all’interno di una classe che abbiamo intenzione di scrivere, dobbiamo prima importarla. Supponiamo di voler utilizzare la classe Date del package java.util. Prima di dichiarare la classe in cui abbiamo intenzione di utilizzare Date dobbiamo scrivere:

    import java.util.Date;

oppure, per importare tutte le classi del package java.util:

    import java.util.*;

N.B.: di default, in ogni file Java è importato automaticamente tutto il package java.lang, senza il quale non potremmo utilizzare classi fondamentali quali System e String. Notiamo che questa è una delle caratteristiche che rende Java definibile come "semplice". Quindi, nel momento in cui compiliamo una classe Java, il compilatore anteporrà il comando:

    import java.lang.*;

alla dichiarazione della nostra classe.

Il lettore noti inoltre che l’asterisco non implica l’importazione anche delle classi appartenenti ai "sottopackage" (esemio import java.awt.* non implica l’import di java.awt.event.*).

- La classe String:

In Java le stringhe, a differenza della maggior parte dei linguaggi di programmazione, non sono array di caratteri (char), bensì oggetti. Le stringhe, in quanto oggetti, dovrebbero essere istanziate con la solita sintassi tramite la parola chiave new. Java però, come fa spesso, semplifica la vita del programmatore, permettendogli di utilizzare le stringhe, come un tipo di dato primitivo ("Java è semplice"). Per esempio, possiamo istanziare una stringa nel seguente modo:

    String nome="Mario Rossi";

Ciò è equivalente a scrivere:

    String nome=new String("Mario Rossi");

In questo secondo caso abbiamo anche sfruttato il concetto di costruttore introdotto nel precedente modulo.

Per assegnare un valore ad una stringa bisogna che esso sia compreso tra virgolette, a differenza dei caratteri per cui vengono utilizzati gli apici singoli.

N.B.: anche in questo caso possiamo sottolineare la semplicità di Java. Il fatto che ci venga permesso di utilizzare una classe così importante come String, come se fosse un tipo di dato primitivo, ci ha permesso di approcciare ai primi esempi di codice senza un ulteriore "trauma", che avrebbe richiesto inoltre l’introduzione del concetto di costruttore.

N.B.: il fatto che String sia una classe ci garantisce una serie di metodi di utilità, semplici da utilizzare e sempre disponibili, per compiere operazioni con le stringhe. Qualche esempio: sono i metodi: toUpperCase() che restituisce la stringa su cui viene chiamato il metodo con ogni carattere maiuscolo (ovviamente esiste un toLowerCase()), trim() che restituisce la stringa su cui viene chiamato il metodo ma senza gli spazi che precedono la prima lettera e quelli che seguono l'utlima, equals(String) che permette di comparare due stringhe etc...

N.B.: la classe String è chiaramente una classe molto particolare. Un'altra caratteristica che va sottolineata è che un oggetto String è immutabile. I metodi di cui sopra infatti non vanno a modificare l'oggetto stesso, ma, semmai, ne restituiscono un altro. Per esempio le seguenti righe di codice:

String a = “claudio”;

String b = a.toUpperCase();

System.out.println(a); // a rimane immutato

System.out.println(b); // b è la stringa maiuscola

 produrrebbero il seguente output:

claudio

CLAUDIO

- La documentazione del J.D.K.

Per conoscere la classe String, e tutte le altre classi, basta andare a consultare la documentazione. Aprire il file "index.html" che si trova nella cartella "Docs" del J.D.K., cliccare sul link "Java 2 Platform Specification". Se non trovate la cartella fate una ricerca sul vostro disco rigido. Potreste infatti averla installata in un’altra directory. Se la ricerca fallisce procuratevi la documentazione ( www.java.sun.com ), o iniziate a studiare un altro linguaggio di programmazione. È assolutamente fondamentale che il lettore inizi da subito la sua esplorazione e conoscenza della documentazione. Il vero programmatore Java, infatti, ha grande familiarità con essa, e sa sfruttare la sua facilità di consultazione nel modo migliore. In questo testo, a differenza di altri, non saranno affrontati argomenti relativi alle classi della libreria standard. Questo per cinque ragioni essenziali:

1) riteniamo la documentazione sufficiente

2) molti altri testi lo fanno già, ed alcuni egregiamente

3) il sito della Sun è fonte inesauribile di informazioni ed esempi

4) le librerie sono in continua evoluzione

5) mancanza di tempo

Java: Gli array in java

Un array è una collezione di tipi di dati primitivi, o di reference, o di altri array. Gli array permettono di utilizzare un solo nome per individuare una collezione costituita da vari elementi che saranno accessibili tramite degli indici interi. In Java gli array sono, in quanto collezioni, oggetti.

Per creare ed utilizzare un array, bisogna passare attraverso tre fasi: dichiarazione, istanza ed inizializzazione.

- Dichiarazione:

Di seguito presentiamo due dichiarazioni di array. Nella prima dichiariamo un array di char (tipo primitivo), nella seconda dichiariamo un array di istanze di Button (classe appartenente al package java.awt):

char alfabeto []; oppure char [] alfabeto;

Button bottoni []; oppure Button [] bottoni;

In pratica, per dichiarare un array, basta posporre (oppure anteporre) una coppia di parentesi quadre all’identificatore.

- Istanza:

Un array è un oggetto (speciale) in Java e, in quanto tale, va istanziato in modo particolare. La sintassi è la seguente:

alfabeto = new char[21];

bottoni = new Button[3];

Come il lettore può notare, è obbligatorio specificare al momento dell’istanza dell’array, la dimensione dell’array stesso. A questo punto però tutti gli elementi dei due array sono inizializzati automaticamente ai relativi valori nulli. Vediamo allora come inizializzare esplicitamente gli elementi dell’array.

- Inizializzazione:

Per inizializzare un array, bisogna inizializzare ogni elemento singolarmente:

alfabeto [0] = ‘a’;

alfabeto [1] = ‘b’;

alfabeto [2] = ‘c’;

alfabeto [3] = ‘d’;

. . . . . . . . . .

alfabeto [20] = ‘z’;



bottoni [0] = new Button();

bottoni [1] = new Button();

bottoni [2] = new Button();

L’indice di un array, inizia sempre da zero. Quindi un array dichiarato di 21 posti, avrà come indice minimo 0, e massimo 20.

Il lettore avrà sicuramente notato che è molto scomodo inizializzare un array in questo modo, per di più dopo averlo prima dichiarato ed istanziato. Ma Java ci viene incontro dandoci la possibilità di eseguire tutti e tre i passi principali per creare un array, tramite una particolare sintassi che di seguito presentiamo:

char alfabeto [] = ;

Button bottoni [] = ;

Un array non è ridimensionabile, e se dichiarato di un certo tipo di dato, non potrà immagazzinare elementi di altri tipi (tranne nei casi in cui si applica il polimorfismo . ). Esiste anche una variabile chiamata length che applicata ad un array, restituisce la dimensione effettiva dell’array stesso. Quindi

alfabeto.length

varrà 21.

Esistono anche array multidimensionali, che sono array di array. A differenza della maggior parte degli altri linguaggi di programmazione, in Java quindi, un array bidimensionale non deve per forza essere rettangolare. Di seguito è presentato un esempio:



int arrayNonRettangolare [][]=new int[4][];

arrayNonRettangolare [0]=new int[2];

arrayNonRettangolare [1]=new int[4];

arrayNonRettangolare [2]=new int[6];

arrayNonRettangolare [3]=new int[8];

arrayNonRettangolare [0][0]=1;

arrayNonRettangolare [0][1]=2;

arrayNonRettangolare [1][0]=1;

. . . . . . . . . . . . . . . .

arrayNonRettangolare [3][8]=10;



oppure, equivalentemente:

int arrayNonRettangolare [][]=







} ;

N.B.: Il lettore è rimandato al prossimo modulo per la conoscenza dei cicli. Essi ci permetteranno in alcuni casi di inizializzare gli array in maniera molto comoda.





Java: Lo strumento javadoc


Abbiamo prima accennato alla possibilità di generare della documentazione in formato HTML delle nostre classi, sul modello della documentazione delle classi standard di Java. Ciò è molto semplice, ma dobbiamo tenere conto di qualche particolare. Potremo generare documentazione solo per classi dichiarate public. Ovviamente, possiamo commentare classi, metodi, costruttori, variabili, costanti ed interfacce, (se dichiarate public)a patto di utilizzare commenti compresi tra "/**" e "*/". Il lettore è invitato a provare lo strumento javadoc commentando una qualsiasi classe dichiarata pubblica. Siamo sicuri che ne rimarrà entusiasta, comprendendone l’utilità. Digitando da riga di comando l’istruzione

javadoc nomeFile.java

saranno generati automaticamente tutti i file HTML che servono . provare per credere .


Java: Operatori di base

Di seguito è presentata una lista volutamente incompleta degli operatori che Java ci mette a disposizione. Per alcuni di essi, l’utilizzo è alquanto raro.

- Operatore d’assegnazione:

- Operatori aritmetici :

   somma       +

   sottrazione     -

   moltiplicazione   *

   divisione   /

   modulo         %

L’unico operatore che può risultare non familiare al lettore, è l’operatore modulo. Il risultato dell’operazione modulo tra due numeri, coincide con il resto della divisione fra essi. Per esempio

 Dalla sintassi del linguaggio C, Java ha ereditato anche altri operatori unari (con un solo operando) che, oltre che svolgere un’operazione, assegnano anche il valore del risultato ad una variabile utilizzata nell’operazione:

   somma e assegnazione          +=

   sottrazione e assegnazione    -=

   moltiplicazione e assegnazione *=

   divisione e assegnazione     /=

   modulo e assegnazione         %=

In pratica se abbiamo:

    int i=5;

scrivere:

    i=i+2;

è equivalente a scrivere:

    i+=2;

- Operatori di pre e post-incremento

   incremento di un’unità         ++ (pre e post)

   decremento di un’unità     -- (pre e post)

Se vogliamo incrementare di una sola unità una variabile numerica, posso equivalentemente scrivere:

    i=i+1;

oppure:

    i+=1;

ma anche:

    i++;

oppure:

    ++i;

ottenendo comunque lo stesso risultato. Infatti, in tutti i casi, otterremo che il valore della variabile i, è stato incrementato di un’unità, ed assegnato nuovamente alla variabile stessa. Quindi anche questi operatori svolgono due compiti (incremento ed assegnazione). Parleremo di operatore di pre-incremento nel caso in cui anteponiamo l’operatore d’incremento ++ alla variabile. Parleremo invece, di operatore di post-incremento nel caso in cui posponiamo l’operatore di incremento alla variabile. La differenza tra questi due operatori "composti", consiste essenzialmente nelle priorità che essi hanno rispetto all’operatore di assegnazione. L’operatore di pre-incremento, ha maggiore priorità dell’operatore di assegnazione =. L’operatore di post-incremento, ha minor priorità rispetto all’operatore di assegnazione =.

Il seguente esempio rende visibile la differenza tra i due operatori:

pre-incremento

    x = 5;

    y = ++x; si ha x=6 e y=6

post-incremento

    x = 5;

    y = x++; si ha x=6 e y=5

- Operatori bitwise:

   NOT                           ~

   AND                         &

   OR                       |

XOR                         ^

   shift a sinistra           <<

   shift a destra                     >>

   shift a destra senza segno                 >>>

   AND e assegnazione                 &=

   OR e assegnazione                   |=

   XOR e assegnazione                 ^=

   shift a sinistra e assegnazione              <<=

   shift a destra e assegnazione           >>=

   shift a destra con riempimento di zeri e assegnazione      >>>=

Tutti questi operatori binari, sono molto efficienti giacché agiscono direttamente sui bit, ma in Java si utilizzano raramente. L’operatore NOT ~ è un cosiddetto operatore unario, dato che si applica ad un solo operando.

Gli operatori AND &, OR |, e XOR ^, si applicano a coppie di operandi, e svolgono le relative operazioni logiche di conversioni di bit riassunte nelle seguente tabella:

Operando1

Operando2

Op1ANDOp2

Op1OROp2

Op1XOROp2























Gli operatori di shift, provocano lo scorrimento verso una direzione, dei bit della rappresentazione binaria di un certo numero. Il numero dei bit da scorrere è rappresentato dall’operando a destra dell’operazione. I bit che dopo lo scorrimento si trovano al di fuori della rappresentazione binaria del numero, vengono eliminati. I bit che invece "rimangono vuoti" vengono riempiti con i valori 0 oppure 1 a seconda del caso. In particolare, lo scorrimento a sinistra provoca un riempimento con i valori 0, per i bit lasciati vuoti sulla destra della rappresentazione binaria del numero. Anche lo scorrimento a destra senza segno riempie i bit lasciati vuoti con degli 0. Lo scorrimento a destra con segno invece, provoca il riempimento di 0 oppure di 1, a seconda che l’ultima cifra a sinistra prima dello scorrimento (bit del segno) sia 0 oppure 1, ovvero che la cifra prima dello scorrimento sia positiva o negativa. Consideriamo i seguenti esempi:

    byte a=35; byte b=-8;

    a = a>>2; b = b>>1;

rappresentazioni binarie

    >>2 >>1

    int a = -1;

    a = a>>>24;

    >>>24

Possiamo notare che l’operazione di scorrimento a sinistra equivale, a dividere l’operando di sinistra, per 2 elevato l’operando situato alla destra dell’operazione. Similmente, l’operazione di scorrimento a destra equivale a moltiplicare l’operando di sinistra per 2 elevato l’operando situato sulla destra dell’operazione. Il risultato viene arrotondato per difetto nelle operazioni con resto.

- Operatori relazionali o di confronto

Il risultato di queste operazioni è sempre un valore boolean, ovvero true o false

    uguale a             ==       tutti i tipi

    diverso da             !=         tutti i tipi

    maggiore           >          solo i tipi numerici

    minore               <          solo i tipi numerici

    maggiore o uguale         >=       solo i tipi numerici

    minore o uguale         <=           solo i tipi numerici

 Notiamo che se confrontiamo due reference con l’operatore ==, il risultato risulterà true se e solo se i due reference puntano allo stesso oggetto, false altrimenti. Un classico errore che l’aspirante programmatore commette spesso, è quello di scrivere = in luogo di ==.

- Operatori logico - booleani

I seguenti sono operatori che utilizzano solo operandi di tipo booleano, ed il risultato è di tipo boolean:

    NOT logico         !

    AND logico         &

    OR logico       |

    XOR logico         ^

    short circuit AND   &&

    short circuit OR       ||

    AND e assegnazione   &=

OR e assegnazione       |=

    XOR e assegnazione     ^=

E’ consuetudine utilizzare le versioni short circuit di AND ed OR. Ad esempio, la seguente riga di codice, mostra come avvantaggiarsi della valutazione logica di corto circuito per essere certi che il risultato di una operazione di divisione sia valido prima di valutarlo:

boolean flag = (a!=0 && b/a>10)

Affinché l’espressione tra parentesi sia vera, bisogna che entrambi gli operandi siano veri. Se il primo tra loro è in partenza falso, non ha senso andare a controllare la situazione dell’altro operando. In questo caso addirittura sarebbe dannoso essendoci il rischio di una divisione per zero. Quest'operatore short – circuit, a differenza della sua versione tradizionale, fa evitare il secondo controllo in caso di fallimento del primo. Equivalentemente l'operatore short - circuit ||, nel caso la prima espressione da testare risultasse verificata, convalida l'intera espressione, senza nessun'altra (superflua) verifica.

- Concatenazione di stringhe con +

In Java l’operatore +, oltre che ad essere un operatore aritmetico, è anche un operatore per concatenare stringhe. Per esempio il seguente frammento di codice:

    String nome = "James ";

String cognome = "Gosling";

    String nomeCompleto = "Mr. " + nome + cognome;

farà in modo che la stringa nomeCompleto, avrà come valore "Mr. James Gosling".

N.B.: se "sommiamo" un qualsiasi tipo di dato con una stringa, il tipo di dato sarà automaticamente convertito in stringa, e ciò può risultare molto utile.

- Priorità degli operatori

Nella seguente tabella sono riportati, in ordine di priorità, tutti gli operatori di Java. Alcuni di essi non sono ancora stati trattati.

Separatori

da sx a dx

++ -- + - ~ ! (tipi di dati)

Da sx a dx

Da sx a dx

Da sx a dx

<< >> >>>

Da sx a dx

< > <= >= instanceof

Da sx a dx

Da sx a dx

&

Da sx a dx

Da sx a dx

Da sx a dx

&&

Da sx a dx

Da dx a sx

da dx a sx

= *= /= %= += -= <<= >>= >>>= &= ^= |=

Il lettore non si spaventi! Non è necessario conoscere a memoria tutte le priorità per programmare. Nell’incertezza, si possono sempre utilizzare le parentesi tonde così come faremmo nell’aritmetica tradizionale.

Java: If e While

Il costrutto if

Questa condizione, ci permette di prendere semplici decisioni basate su valori immagazzinati. In fase di runtime, la Java Virtual Machine testa un’espressione booleana, e a seconda che essa risulti vera o falsa esegue un certo blocco di istruzioni, oppure no. Un’espressione booleana è un’espressione che come risultato può restituire solo valori di tipo boolean, cioè true o false. Essa di solito si avvale di operatori di confronto, e, magari, di operatori logici. La sintassi è la seguente:

    if (espressione-booleana) istruzione;

per esempio:

    if (numeroLati==3)

    System.out.println("Questo è un triangolo");

Nell’esempio, l’istruzione di stampa, sarebbe eseguita se e solo se la variabile numeroLati avesse valore 3. In quel caso l’espressione booleana in parentesi varrebbe true, e quindi sarebbe eseguita l’istruzione che segue l’espressione. Se invece l’espressione risultasse false, sarebbe eseguita direttamente la prima eventuale istruzione che segue l’istruzione di stampa. Possiamo anche estendere la potenzialità del costrutto if, mediante la parola chiave else:

    if (espressione-booleana) istruzione1;

    else istruzione2;

per esempio:

    if (numeroLati==3)

    System.out.println("Questo è un triangolo");

    else

    System.out.println("Questo non è un triangolo");

Se if potremmo tradurlo con "se", else potremmo tradurlo con "altrimenti". In pratica nell’esempio, se l’espressione booleana è vera, sarà stampata la stringa "Questo è un triangolo", se è falsa sarà stampata la stringa "Questo non è un triangolo".

Possiamo anche utilizzare dei blocchi di codice, con questo tipo di sintassi:

    if (espressione-booleana) else

ed anche comporre più costrutti nel seguente modo

    if (espressione-booleana) else if (espressione-booleana) else if (espressione-booleana) else

Possiamo anche annidare questi costrutti:

    if (x!=0)     if (x!=0)else

    z=7;         z=7;

Questi due frammenti di codice, sono equivalenti. Il secondo però, è effettivamente più chiaro, dal momento che si utilizza un blocco di codice in più.

- Il costrutto while

Questo ciclo, ci permette di iterare uno statement (o un insieme di statement compresi in un blocco di codice), tante volte fino a quando una certa condizione booleana è verificata. La sintassi è la seguente:

    [inizializzazione;]

    while (espr. booleana)

Come esempio proponiamo una piccola applicazione che stampa i primi dieci numeri:

class WhileDemo  

Seguiamo in sequenza le istruzioni che sarebbero eseguite in fase di runtime. Viene in primo luogo dichiarata ed inizializzate a 1 una variabile intera i. Poi inizia il ciclo, in cui è esaminato il valore booleano dell’espressione in parentesi. Siccome i è uguale ad 1, i è anche minore di 10 e la condizione è verificata. Quindi, verrà eseguito il blocco di codice relativo, nel quale, prima sarà stampato il valore della variabile i (ovvero 1), e poi verrà incrementata la variabile stessa di un’unità. Terminato il blocco di codice, verrà nuovamente testato il valore dell’espressione booleana. Durante questo secondo tentativo, la variabile i varrà 2. Quindi, anche in questo caso, sarà eseguito di nuovo il blocco di codice. Verrà allora stampato il valore della variabile i (ovvero 2), ed incrementata nuovamente di una unità, la variabile stessa. Questo ragionamento si ripete fino a quando la variabile i non assume il valore 11. Quando ciò accadrà, il blocco di codice non verrà eseguito, dal momento che, l’espressione booleana non sarà verificata. Il programma quindi eseguirà le istruzioni successive al blocco di codice e quindi terminerà.


Java: for, do, switch


Il costrutto for

Completiamo il discorso dei cicli presentando for e do. Essi sono due cicli, equivalenti al while, con delle caratteristiche particolari. Ecco la sintassi per il for, nel caso d’utilizzo di una o più istruzioni da iterare:

    for (inizializzazione; espr. booleana; aggiornamento)

istruzione;

    for (inizializzazione; espr. booleana; aggiornamento)

Presentiamo un esempio che stampa i primi 10 numeri partendo da 10 e terminando ad 1:

class ForDemo

N.B.: in questo caso notiamo che la sintassi è più compatta rispetto a quella relativa al while. Tra le parentesi tonde relative ad un ciclo for, addirittura dichiariamo una variabile locale n (che smetterà di esistere al termine del ciclo). Potevamo comunque anche dichiararla prima del ciclo, nel caso fosse stata nostra intenzione utilizzarla anche al di fuori di esso.

In pratica, se nel while utilizzavamo le parentesi tonde solo per l’espressione booleana, nel for le utilizziamo per inserirci rispettivamente, prima l’inizializzazione di una variabile, poi l’espressione booleana, ed infine l’aggiornamento che sarà eseguito ad ogni iterazione. Da notare che questi tre istruzioni possono anche essere completamente indipendenti tra loro.

Questo è il ciclo più utilizzato, vista la sua grande semplicità d’utilizzo. Il ciclo while, è molto utilizzato quando non si conosce il numero delle volte che le istruzioni devono essere eseguite, e soprattutto nei cicli infiniti, dove la sintassi è banale:

    while (true)

- Il costrutto do

Nel caso in cui si desideri la garanzia, che le istruzioni da iterare siano eseguite almeno una volta, dobbiamo utilizzare il ciclo do, eccone la sintassi:

    [inizializzazione;]

    do while (terminazione);

in questo caso viene eseguito prima il blocco di codice, e poi, viene valutata l’espressione booleana che si trova a destra della parola chiave while. Ovviamente se l’espressione booleana è verificata, viene rieseguito il blocco di codice, altrimenti termina. Notare il punto e virgola situato alla fine del costrutto.

- Il costrutto switch

Il costrutto switch, si presenta come alternativa al costrutto if. A differenza di if, non è possibile utilizzarlo in ogni situazione in cui c’è bisogno di scegliere tra l’esecuzione di parti di codice diverse. Di seguito vi presentiamo la sintassi:

    switch (variabile di test)

    break;

    case valore3

    case valore4

    break;

    [default

In pratica, a seconda del valore intero che assume la variabile di test, vengono eseguite determinate espressioni. La variabile di test, deve essere di un tipo di dato compatibile con un intero, ovvero un byte, uno short, un char, oppure, ovviamente un int. Inoltre valore1...valoren devono essere espressioni costanti e diverse tra loro. Notare che la parola chiave break, provoca l’immediata uscita dal costrutto. Se infatti, dopo aver eseguito tutte le istruzioni che seguono un’istruzione di tipo case, non è presente un’istruzione break, verranno eseguite tutti gli statement che seguono gli altri case, sino a quando non si arriverà ad un break. Di seguito viene presentato un esempio:

class SwitchStagione

    System.out.println("La stagione e’ "+stagione+".");

- Due importanti parole chiave: break e continue

La parola chiave break, è stata appena presentata come comando capace di fare terminare il costrutto switch. Ma break, è utilizzabile anche per far terminare un qualsiasi ciclo. Il seguente frammento di codice, provoca la stampa dei primi dieci numeri interi:

    int i=0;

    while (true) //ciclo infinito

Oltre al break, esiste la parola chiave continue, che fa terminare non l’intero ciclo, ma solo l’iterazione corrente. Il seguente frammento di codice provoca la stampa dei primi dieci numeri, escluso il cinque:

int i=0;

    do

    while(i<=10);

Sia break sia continue, possono utilizzare etichette (label) per specificare, solo nel caso di cicli annidati, su quale ciclo devono essere applicati. Il seguente frammento di codice stampa una sola volta, i soliti primi dieci numeri interi:

    int j=0;

    pippo: //possiamo dare un qualsiasi nome ad una //label

while (true)

Java: I paradigmi della programmazione ad oggetti

Ciò che caratterizza un linguaggio orientato agli oggetti, è il supporto che esso offre ai cosiddetti "paradigmi della programmazione ad oggetti", che di seguito elenchiamo:

  • Incapsulamento
  • Ereditarietà
  • Polimorfismo

A differenza di altri linguaggi di programmazione orientati agli oggetti, Java utilizza in modo estremamente chiaro, bensì complesso, i concetti appena accennati. Anche programmatori che si ritengono esperti di altri linguaggi orientati agli oggetti come il C++, studiando Java, potrebbero scoprire significati profondi in alcuni concetti che prima si ritenevano chiari.

Nel presente e nel prossimo modulo introdurremo i tre paradigmi in questione. Segnaliamo al lettore che non tutti i testi parlano di tre paradigmi. In effetti si dovrebbero considerare paradigmi della programmazione ad oggetti anche l’astrazione ed il riuso. Questi due sono spesso considerati secondari rispetto agli altri su elencati, non per minor potenza ed utilità, ma per il supporto alla definizione di linguaggio orientato agli oggetti. Infatti, l’astrazione ed il riuso, sono concetti che appartengono anche alla programmazione procedurale.

- Astrazione e riuso:

L’astrazione potrebbe definirsi come "l’arte di saperci concentrare solo sui dettagli veramente essenziali nella descrizione di un’entità". In pratica l’astrazione è un concetto chiarissimo a tutti noi, dal momento che lo utilizziamo in ogni istante della nostra vita. Per esempio, mentre state leggendo questo manuale, vi state concentrando sull’apprenderne correttamente i contenuti, senza badare troppo alla forma, ai colori, allo stile, e tutti particolari fisici e teorici che compongono la pagina che state visualizzando (o almeno lo speriamo!). Potremmo parlare di almeno tre livelli di astrazione per quanto riguarda la sua implementazione nella programmazione ad oggetti:

  • astrazione funzionale
  • astrazione dei dati
  • astrazione del sistema

Adoperiamo l’astrazione funzionale ogni volta che implementiamo un metodo. Infatti, tramite un metodo, riusciamo a portare all’interno di un’applicazione un concetto dinamico, sinonimo di azione, funzione. Per scrivere un metodo ci dovremmo limitare alla sua implementazione più robusta e chiara possibile. In questo modo avremo la possibilità di invocare quel metodo ottenendo il risultato voluto, senza dover tener presente l’implementazione del metodo stesso. Lo stesso concetto sussisteva anche nella programmazione procedurale grazie alle funzioni.

Adoperiamo l’astrazione dei dati ogni volta che definiamo una classe, raccogliendo in essa solo le caratteristiche e funzionalità essenziali degli oggetti che essa deve definire nel contesto in cui ci si trova. Potremmo dire che l’astrazione dei dati "contiene" l’astrazione funzionale.

Adoperiamo l’astrazione del sistema ogni volta che definiamo un’applicazione, in termini delle classi essenziali che devono soddisfare agli scopi dell’applicazione stessa. Questo assomiglia molto da vicina a quello che nella programmazione procedurale veniva chiamato metodo "Top down". Potremmo anche affermare che l’astrazione del sistema "contiene" l’astrazione dei dati, e, per la proprietà transitiva, l’astrazione funzionale.

Il riuso è invece da considerarsi una conseguenza degli altri paradigmi della programmazione ad oggetti. Notiamo anche come esso era una conseguenza dell’astrazione funzionale e del metodo "Top down" nella programmazione procedurale.

Java: Incapsulamento

L’incapsulamento è la chiave della programmazione orientata agli oggetti. Tramite esso, una classe riesce ad acquisire caratteristiche di robustezza, indipendenza e riusabilità. Inoltre la sua manutenzione risulterà più semplice al programmatore.

Una qualsiasi classe è essenzialmente costituita da dati e metodi. La filosofia dell’incapsulamento è semplice. Essa si basa sull’accesso controllato ai dati mediante metodi che possono prevenirne l’usura e la non correttezza dei dati stessi. A livello di implementazione, ciò si traduce nel dichiarare privati i membri di una classe e quindi inaccessibili al di fuori della classe stessa (a tale scopo esiste il modificatore private). Allora, l’accesso ai dati, potrà essere fornito da un’interfaccia pubblica costituita da metodi dichiarati public, e quindi accessibili da altre classi. In questo modo, tali metodi potrebbero ad esempio permettere di realizzare controlli prima di confermare l’accesso ai dati privati. Se l’incapsulamento è gestito in maniera intelligente, le nostre classi potranno essere utilizzate nel modo migliore e più a lungo, giacché le modifiche e le revisioni potranno riguardare solamente parti di codice non visibili all’esterno. Se volessimo fare un esempio basandoci sulla realtà che ci circonda, potremmo prendere in considerazione un telefono. La maggior parte degli utenti, infatti, sa utilizzare il telefono, ma ne ignora il funzionamento interno. Chiunque infatti, può alzare la cornetta, comporre un numero telefonico, e conversare con un’altra persona, ma pochi conoscono in dettaglio la sequenza dei processi scatenati da queste poche, semplici azioni. Evidentemente per utilizzare il telefono, non è necessario essere un tecnico: basta conoscere la sua interfaccia pubblica, non la sua implementazione interna.

Di seguito è presentata una classe che utilizza l’incapsulamento, gestendo l’accesso ad un saldo bancario personale, mediante l’inserimento di un codice segreto:

    class ContoBancario

    else

In generale, nella programmazione ad oggetti, si preferisce sempre, dichiarare i dati privati, e semmai fornire alla classe metodi pubblici di tipo "set" e "get" per accedervi. Possiamo comunque chiamare questi metodi utilizzando un qualsiasi identificatore, possibilmente significativo: "set" e "get" non sono parole chiave. Per correttezza la classe Data definita nel modulo 3, dovrebbe essere definita comunque in questo modo:

    class Data

    else . . .

    public int getGiorno()

    public void setMese(int m)

    else . . .

    public int getMese()

    public void setAnno(int a)

    public int getAnno()

Supponiamo inoltre che esista un’ altra classe che dichiara il seguente blocco di codice:

1) Data oggi=new Data();

   oggi.setGiorno(9);

Il lettore avrà notato che utilizzare l’incapsulamento, richiederà un maggior sforzo d’implementazione. Tuttavia, questo sforzo sarà presto ricompensato. Infatti, supponiamo di voler apportare modifiche migliorative alla classe precedente. Per esempio, notiamo che per testare efficacemente il settaggio della variabile giorno, dovremmo tener conto anche del valore del mese e dell’anno (se l’anno è bisestile, e il mese è febbraio, allora risulterà legale il giorno con valore 29 . ). Ecco allora che le modifiche riguarderanno solo l’implementazione del metodo setGiorno all’interno della classe Data e le classi che dichiarano il blocco di codice 1)non dovranno essere modificate.

- Prima osservazione sull’incapsulamento:

Sino ad ora abbiamo visto degli esempi di incapsulamento abbastanza classici, dove nascondevamo all’interno delle classi gli attributi mediante il modificatore private. Nulla ci vieta di utilizzare private, anche come modificatore di metodi, ottenendo così un incapsulamento funzionale. Un metodo privato infatti, potrà essere invocato solo da un metodo definito nella stessa classe, che potrebbe a sua volta essere dichiarato pubblico. Per esempio la classe ContoBancario definita precedentemente, in un progetto potrebbe evolversi nel seguente modo:

    class ContoBancario

    private String controllaCodice(int codiceDaTestare)

   else

Ciò favorirebbe il riuso di codice in quanto, introducendo nuovi metodi (come probabilmente accadrà in progetto che si incrementa), questi potrebbero risfruttare il metodo controllaCodice.

- Seconda osservazione sull’incapsulamento:

Abbiamo affermato che un membro di una classe dichiarato private, diventa "inaccessibile da altre classi". Questa frase è ragionevole per quanto riguarda l’ambito della compilazione, dove la dichiarazione delle classi è il problema da superare. Ma, se ci spostiamo nell’ambito della Java Virtual Machine, dove, come abbiamo detto, i protagonisti assoluti non sono le classi ma gli oggetti, dobbiamo rivalutare l’affermazione precedente. L’incapsulamento infatti, permetterà a due oggetti istanziati dalla stessa classe di accedere in "modo pubblico", ai rispettivi membri privati.

Facciamo un esempio, consideriamo la seguente classe Dipendente:

    class Dipendente

    public void setNome(String n)

   public String getAnni()

   public void setAnni(int n)

   public int getDifferenzaAnni(Dipendente altro)

Nel metodo getDifferenzaAnni notiamo che è possibile accedere direttamente alla variabile anni dell’oggetto altro, senza dover utilizzare il metodo getAnni. Il lettore è invitato a riflettere soprattutto sul fatto che il codice precedente è valido per la compilazione, ma, il seguente metodo

    public int getDifferenzaAnni(Dipendente altro)

favorirebbe sicuramente di più il riuso di codice, e quindi è da considerarsi preferibile. 

- Il reference "this":

L’esempio precedente potrebbe aver provocato nel lettore qualche dubbio. Sino ad ora, avevamo dato per scontato che accedere ad una variabile d’istanza all’interno di una classe che la definisce, fosse un processo naturale che non aveva bisogno di reference. Ad esempio della classe precedentemente descritta Data, all’interno del metodo getGiorno, accedevamo alla variabile giorno, senza referenziarla. Alla luce dell’ultimo esempio ci potremmo chiedere: se giorno è una variabile d’istanza, a quale istanza appartiene? La risposta a questa domanda che sino ad ora non avevamo neanche preso in considerazione, è: dipende "dall’oggetto corrente", ovvero dall’oggetto su cui è chiamato il metodo getGiorno. In fase di esecuzione di un certa applicazione, potrebbe essere istanziati due particolari oggetti, supponiamo che si chiamino mioCompleanno e tuoCompleanno. Entrambi questi oggetti hanno una propria variabile giorno. Ad un certo punto, all’interno del programma potrebbe presentarsi la seguente istruzione:

    System.out.println(mioCompleanno.getGiorno());

Sappiamo che sarà stampato a video il valore della variabile giorno dell’oggetto mioCompleanno, ma dal momento che sappiamo che una variabile anche all’interno di una classe potrebbe (e dovrebbe) essere referenziata, dovremmo sforzarci di capire come fa la Java Virtual Machine a scegliere la variabile giusta senza avere a disposizione reference! In realtà, se il programmatore non referenzia una certa variabile d’istanza, al momento della compilazione il codice sarà modificato dal compilatore stesso, che aggiungerà un reference all’oggetto corrente davanti alla variabile. Ma quale reference all’oggetto corrente? La classe non può conoscere a priori i reference degli oggetti che saranno istanziati da essa in fase di runtime!

Java introduce una parola chiave, che per definizione coincide ad un reference all’oggetto corrente: this (questo). Il reference this viene quindi implicitamente aggiunto nel bytecode compilato, per referenziare ogni variabile d’istanza non esplicitamente referenziata. Ancora una volta Java cerca di facilitare la vita del programmatore. In un linguaggio orientato agli oggetti puro, non è permesso non referenziare le variabili d’istanza.

In pratica il metodo getGiorno che avrà a disposizione la J.V.M. dopo la compilazione sarà:

    public int getGiorno()

          //il compilatore

In seguito vedremo altri utilizzi del reference "segreto" this.

N.B.: anche in questo caso, abbiamo notato un’altro di quei comportamenti del linguaggio, che fanno sì che Java sia definito come "semplice". Se non ci siamo posti il problema della referenzazione dei membri all’interno di una classe sino a questo punto, vuol dire che anche questa volta, "Java ci ha dato una mano".

- Due stili di programmazione a confronto:

Nel secondo modulo abbiamo distinto le variabili d‘istanza dalle variabili locali. La diversità tra i due concetti è tale che il compilatore ci permette di dichiarare una variabile locale (o un parametro di un metodo) ed una variabile di istanza, aventi lo stesso identificatore, nella stessa classe. La parola chiave this si inserisce in questo discorso nel seguente modo. Abbiamo più volte avuto a che fare con passaggi di parametri in metodi, al fine di inizializzare variabili d’istanza. Siamo stati costretti, sino ad ora, ad inventare per il parametro passato un identificatore differente da quello della variabile d’istanza da inizializzare. Consideriamo la seguente classe:

    class Cliente

Notiamo l’utilizzo dell’identificatore n per inizializzare nome, num per numeroDiTelefono, e ind per indirizzo. Non c’è nulla di sbagliato in questo. Conoscendo l’esistenza di this però, abbiamo la possibilità di scrivere equivalentemente:

    class Cliente

Infatti, tramite la parola chiave this, specifichiamo che la variabile referenziata, appartiene all’istanza. Di conseguenza la variabile non referenziata sarà il parametro del metodo. Non c’è ambiguità quindi, nel codice precedente. Questo stile di programmazione è da alcuni (compreso chi vi scrive) considerato preferibile. In questo modo, infatti, non c’è possibilità di confondere le variabili con nomi simili. Nel nostro esempio potrebbe capitare di assegnare il parametro n alla variabile d’istanza numeroDiTelefono, ed il parametro num alla variabile nome. Potremmo affermare che l’utilizzo di this aggiunge chiarezza al nostro codice.

N.B.: il lettore noti che se scrivessimo:

    class Cliente

il compilatore, non trovando riferimenti espliciti, considererebbe variabili locali le prime incontrate, e di istanza le seconde, in pratica leggerebbe:

    class Cliente

Ovviamente, in questo modo, comunque istanziamo un oggetto di questa classe, le sue variabili verranno inizializzate ai relativi valori nulli (poiché così sono inizializzate automaticamente le variabili d’istanza).

Java: Incapsulamento

Se volessimo essere brevi, la risposta alla domanda del titolo di quest’unità didattica, dovrebbe essere "sempre". Una qualsiasi classe di una qualsiasi applicazione dovrebbe essere sviluppata utilizzando l’incapsulamento. Anche se all’inizio di un progetto software può sembrarci che su determinate classi usufruire dell’incapsulamento sia superfluo, l’esperienza c’insegna che è preferibile l’applicazione in ogni situazione. Facciamo un esempio banale. Abbiamo già accennato al fatto, peraltro scontato, che per realizzare un’applicazione a qualsiasi livello (sempre che non sia veramente banale), bisogna apportare a questa, modifiche incrementali. Un lettore con un minimo d’esperienza di programmazione non potrà che confermare l’ultima affermazione.

Supponiamo di voler scrivere una semplice applicazione che assegnati due punti, disegni il segmento che li unisce. Supponiamo inoltre che questa utilizzi la seguente classe non incapsulata Punto, già incontrata nel modulo 2:

class Punto

l’applicazione, in un primo momento, istanzierà ed inizializzerà due punti con il seguente frammento di codice:

2) Punto p1=new Punto();

   Punto p2=new Punto();

   p1.x=5;

   p1.y=6;

   p2.x=10;

   p2.y=20;

Supponiamo che l’evolversi della nostra applicazione renda necessario che i due punti non debbano trovarsi al di fuori di un certo area piana ben delimitata. Ecco risultare evidente che la soluzione migliore sia quella di implementare l’incapsulamento all’interno della classe Punto in questo modo;

class Punto

    else

public void setY(int a)

    else

Purtroppo però, dopo aver apportato queste modifiche alla classe Punto, saremo costretti a modificare anche il frammento di codice 2) dell’applicazione nel modo seguente:

3) Punto p1=new Punto();

   Punto p2=new Punto();

   p1.setX(5);

   p1.setY(6);

   p2.setX(10);

   p2.setY(20);

Saremmo partiti meglio con la classe Punto forzatamente incapsulata in questo modo:

class Punto

    public void setY(int a)

dal momento che avremmo modificato solo il codice all’interno dei metodi di accesso, e saremmo stati costretti al codice 3) all’interno dell’applicazione che utilizza Punto

Arriviamo alla conclusione che l’incapsulamento è prassi ed obbligo in Java. Un linguaggio orientato agli oggetti "puro", infatti, non permetterebbe la dichiarazione di attributi pubblici. Java però, vuole essere un linguaggio semplice da apprendere, ed in questo modo, non costringe l’aspirante programmatore ad imparare prematuramente, un concetto ad ogni modo complesso quale l’incapsulamento. In particolare, nei primi tempi, non se ne apprezzerebbe completamente l’utilizzo, dovendo comunque approcciare con troppi concetti nuovi contemporaneamente.

Tuttavia, non bisognerebbe mai permettere di sacrificare l’incapsulamento per risparmiare qualche secondo di programmazione, le conseguenze sono ormai note al lettore.

Java: Introduzione a Java DataBase Connectivity (JDBC)

1.INTRODUZIONE

1.1 Java e Database

Negli ultimi anni è nato, si è sviluppato e si sta rapidamente affermando il linguaggio di programmazione Java di Sun. Per guadagnare ulteriori consensi è importante che il linguaggio fornisca al programmatore i mezzi per l'accesso e la gestione delle basi di dati.

Fra le caratteristiche più importanti di Java ricordiamo la semplicità e la portabilità, la possibilità cioè di portare e utilizzare i programmi scritti in Java su qualsiasi piattaforma così come sono, senza la necessità di modificarli o ricompilarli.

Visto il massiccio utilizzo che si fa delle basi di dati per la memorizzazione di informazioni sia nelle grandi aziende che nelle realtà più piccole è abbastanza semplice capire come questi due elementi, Java e i Database, uniti insieme possano fornire al programmatore uno strumento estremamente versatile e al contempo semplice per la realizzazione di applicazioni volte alla gestione di ingenti quantità di informazioni.

Per esempio nel caso di un'azienda che utilizzi più sistemi differenti, magari in località diverse, il risparmio di tempo nella realizzazione del software è sensibile, data anche la predisposizione naturale di Java per la rete.

I progettisti di Java devono aver tenuto in considerazione questi fattori e a tal proposito sono state infatti realizzate le API (Application Programming Interface) JDBC.

L'importanza di JDBC è sottolineata dal fatto di essere inclusa direttamente nella distribuzione standard di Java, e non come libreria separata.

1.2 Caratteristiche delle API JDBC

JDBC è una libreria di Java, quindi un insieme di classi, che fornisce i metodi per l'accesso e la manipolazione di basi di dati di tipo relazionale.

Mantenendo la filosofia di Java, le API JDBC sono state progettate per ottenere l'accesso ai dati indipendentemente dalla piattaforma e dal particolare tipo di database utilizzato; JDBC offre infatti delle interfacce standard e quindi uniche per qualunque DB; per ciascuno di questi deve poi esistere un driver che si occupi di tradurre le chiamate JDBC effettuate dall'applicazione in opportune istruzioni per accedere e manipolare i dati di uno specifico database.

Quando l'applicazione richiede l'accesso ad un DB un particolare componente detto Driver Manager controlla il tipo di DB per cui si è richiesta la connessione e carica il driver appropriato.

In questo modo è possibile cambiare sia piattaforma che DB semplicemente cambiando driver, sempre che ne esista uno.

Un driver per essere considerato pienamente compatibile con JDBC deve implementare tutte le interfacce previste dalle specifiche di JDBC. Se questa condizione è soddisfatta il driver si dice JDBC-Compliant.

1.3 Sull'esempio di ODBC

L'idea che sta alla base di JDBC è la stessa del già collaudato e diffuso ODBC (Open DataBase Connectivity) di Microsoft. Data la numerosa disponibilità di driver compatibili ODBC, i progettisti della Sun hanno ritenuto utile realizzare un cosiddetto driver bridge JDBC - ODBC, un particolare driver che aggiunge uno stadio alla traduzione e converte le chiamate JDBC in chiamate ODBC che quindi le traduce a sua volta in metodi specifici del particolare DB.

Questo driver bridge è stato realizzato principalmente allo scopo di garantire una discreta compatibilità di Java con numerosi DB già dalla sua nascita.

IMPORTANTE notare però che, nel caso ci si appoggi a questo driver, viene in qualche modo a cadere la caratteristica di portabilità, in quanto ODBC è compatibile solo con alcune piattaforme, in particolare è più probabile che funzioni su sistemi Microsoft.

1.4 JDBC e SQL

SQL è il linguaggio standard per la creazione e manipolazione di database relazionali. SQL non è però un linguaggio completo e, oltre al fatto di non essere in grado di eseguire alcune interrogazioni a causa della mancanza di alcuni costrutti tipici della programmazione imperativa, non è assolutamente adatto alla realizzazione di vere e proprie applicazioni.

Per questo motivo è necessario utilizzarlo insieme ad un altro linguaggio di programmazione che si occupi di tutti quegli aspetti che non hanno a che vedere con la manipolazione della base di dati.

Molti linguaggi incorporano SQL e si parla quindi di embedded-SQL. In questi casi il programmatore, nel punto in cui deve accedere al database, "smette" di scrivere nel linguaggio ospite e inserisce le istruzioni da eseguire in SQL direttamente nel codice.

Per ottenere il programma eseguibile il codice deve essere prima esaminato da un particolare traduttore che traduce l'SQL nel linguaggio ospite; solo a questo punto il codice ottenuto può passare al compilatore e quindi al linker.

L'approccio di Java è invece diverso. JDBC mette a disposizione dei metodi ai quali è possibile passare delle stringhe contenenti del codice SQL.

Il codice SQL non viene quindi tradotto prima della compilazione ma viene semplicemente passato al driver JDBC durante l'esecuzione.

Questa soluzione garantisce una maggiore flessibilità, visto che le istruzioni SQL possono essere memorizzate in stringhe variabili, e quindi più facilmente modificabili a run-time rispetto alle soluzioni tradizionali.

2. UTILIZZARE JDBC

2.1 Il package java.sql

Le classi e le interfacce JDBC si trovano nel package java.sql, contenuto nella distribuzione standard del jdk (Java Development Kit).

Le classi e le interfacce più importanti del package java.sql sono:

DriverManager Connection Statement PreparedStatement CallableStatement ResultSet DatabaseMetaData ResultSetMetaData Types

La classe DriverManager gestisce i driver JDBC e seleziona il driver apposito per il database in uso.

L'interfaccia Connection rappresenta una sessione di lavoro sul database e permette di ottenere informazioni sulla base di dati. Le operazioni sul database avvengono all'interno di una sessione.

Le interfacce Statement, PreparedStatement e CallableStatement permettono di eseguire interrogazioni e aggiornamenti sulla base di dati.

L'interfaccia ResultSet fornisce l'accesso alle tabelle.

DatabaseMetaData e ResultSetMetaData consentono di ottenere informazioni sullo schema del database.

La classe Types definisce tutta una serie di costanti che identificano i tipi di dati SQL.

Con queste classi e interfacce è possibile avere un controllo pressochè totale della base di dati.

2.2 Creazione di un Database

Le interfacce JDBC permetterebbero di creare un database da zero e modificarne lo schema. In realtà questa possibilità in diversi casi è preclusa dai driver che non supportano operazioni di modifica allo schema.

In molti casi è perciò consigliabile utilizzare gli strumenti forniti dal produttore del database per eseguire queste operazioni.

2.3 Connessione ad un Database

La prima cosa da fare per iniziare a lavorare sul database è caricare il driver JDBC adatto. Per farlo è necessario forzare il caricamento della classe che rappresenta il driver utilizzando il metodo forName della classe Class.

Supponendo di voler utilizzare il driver bridge ODBC, il caricamento si farà tramite l'istruzione:

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

A questo punto si stabilisce la connessione fra l'applicazione Java ed il database chiamando il metodo getConnection della classe DriverManager nel modo seguente:

Connection con = DriverManager.getConnection("jdbc:odbc:database");

Il metodo getConnection richiede come parametro una stringa che contiene il nome della sorgente di dati nella forma:

jdbc:<subprotocol>:<subname>

dove subprotocol indica il driver da utilizzare e subname è il nome della fonte di dati.

2.4 Estrazione e modifica dei dati del Database

Per eseguire delle interrogazioni o modifiche sulla base di dati si crea per prima cosa un oggetto di tipo Statement utilizzando il metodo createStatement dell'oggetto di tipo Connection ottenuto in precedenza.

Statement st = con.CreateStatement();

L'interfaccia Statement fornisce una serie di metodi come executeQuery ed executeUpdate per eseguire rispettivamente delle operazioni di lettura o modifica sulla base di dati tramite istruzioni SQL. Il metodo executeQuery verrà utilizzato per eseguire delle istruzioni SELECT mentre il metodo executeUpdate per l'esecuzione di istruzioni INSERT, UPDATE e DELETE.

Per esempio, per eseguire una query si farà così:

ResultSet rs = st.executeQuery("SELECT * FROM Tabella1");

Il parametro richiesto dal metodo executeQuery è una stringa contenente il codice SQL da eseguire. Questo metodo non fa altro che passare le istruzioni SQL al driver JDBC e successivamente ricevere da esso il risultato della query salvandolo in un oggetto di tipo ResultSet. L'utilizzo di ResultSet è spiegato in seguito (par 2.5.1).

Se invece si vogliono inserire dei dati in una tabella si utilizzerà un codice dimile al seguente:

st.executeUpdate("INSERT into Tabella1 (campo1, campo2) values(1,2)");

Il metodo executeUpdate non ritorna un oggetto ResultSet, le istruzioni INSERT, UPDATE e DELETE non danno infatti come risultato una tabella.

Il valore ritornato è invece un intero che indica il numero di record modificati.

Un altro esempio potrebbe essere quindi:

int nRows = st.executeUpdate("DELETE * FROM Tabella2 WHERE campo1 > 0");
System.out.println("Sono stati eliminati " + nRows + " record");

Queste due righe di codice eliminano i record della tabella Tabella2 in cui il valore del campo campo1 è maggiore di 0 e successivamente visualizzano il numero di record cancellati.

Oltre all'interfaccia Statement esistono anche le interfacce PreparedStatement e CallableStatement.

L'utilizzo di PreparedStatement è conveniente nel caso di query eseguite ripetutamente, in quanto consente di specificare parzialmente delle query e richiamarle successivamente modificando solamente i nomi dei parametri. In questo modo l'esecuzione è più rapida, in quanto il PreparedStatement conosce già la forma dell'interrogazione e deve solo cambiarne i parametri.

Inizialmente bisogna quindi creare l'interrogazione indicando con il carattere '?' i parametri il cui valore sarà inserito.

PreparedStatement ps = con.prepareStatement("INSERT into Tabella3 (parametro1,parametro2) values (?,?)");

Per inserire i valore mancanti si utilizzano i metodi setXXX (dove XXX è un tipo di dato) a cui vanno passati due parametri: il primo è l'indice del parametro della query che si vuole specificare, il secondo è il valore che gli si vuole dare.

ps.setString(1,"Ciao");
ps.setInt(2,15);
ps.execute();

La prima riga di quest'esempio specifica che il primo parametro dell'interrogazione è una stringa il cui valore è "Ciao", nella seconda riga invece si specifica che il secondo parametro dell'interrogazione è un intero di valore 15.

L'ultima riga esegue l'interrogazione sul database con i parametri specificati.

L'interfaccia CallableStatement consente invece di eseguire delle procedure memorizzate all'interno del database.

Per prima cosa è necessario creare un oggetto CallableStatement indicando, come per PreparedStatement, i parametri con un punto di domanda.

CallableStatement cs = con.prepareCall(" ");

Da notare che il primo '?' non indica un parametro di input ma uno di output. Prima di eseguire la procedura bisogna specificare esplicitamente di che tipo è il valore di ritorno della procedura tramite il metodo RegisterOutParameter.

cs.RegisterOutParameter(1,Types.INTEGER);

Questa istruzione specifica che il primo parametro del metodo prepareCall chiamato in precedenza è un parametro di output ed è un intero.

Per eseguire la procedura:

cs.setInt(2,10);
cs.execute();

La prima istruzione specifica che il parametro passato alla procedura (quindi il secondo '?' nella prepareCall) è un intero di valore 10.

La seconda istruzione esegue la procedura con i parametri specificati.

Per leggere il valore ritornato dalla procedura si utilizza il metodo getXXX (dove XXX indica il tipo di dato).

int val = cs.getInt(1);

Si era specificato in precedenza che il valore ritornato dalla procedura era di tipo intero, utilizziamo quindi il metodo getInt per leggerlo e lo salviamo in una variabile di tipo int.

2.5 Elaborare i dati

Oltre ad eseguire delle interrogazioni sulla base di dati, molto probabilmente si vorranno anche visualizzare i risultati ottenuti. A differenza di SQL però, un linguaggio procedurale come Java non è pensato per operare su tabelle.

A questo scopo sono quindi presenti le interfacce ResultSet, ResultSetMetaData e DatabaseMetaData.

2.5.1 ResultSet e cursori

Un oggetto di tipo ResultSet fornisce l'accesso ad una tabella.

Poichè una tabella può essere composta da due o più colonne (campi) e righe (record), per accedere ai dati si utilizza un cursore.

Un cursore è simile ad un puntatore che indica un determinato record di una tabella; è possibile quindi leggere i valori dei campi del record selezionato. In ogni momento è possibile spostare il cursore sul record successivo o precedente a quello corrente oppure è possibile posizionarlo su un qualunque altro record specificandone l'indice.

Per esempio per visualizzare i dati di una tabella è possibile procedere come segue:

while(rs.next())

Quando l'oggetto ResultSet viene creato il cursore punta al record "precedente al primo", quindi in realtà non punta a niente. In questo modo però è possibile scrivere un codice più elegante per scorrere tutta la tabella utilizzando un ciclo while.

Il metodo next sposta il cursore sul record successivo a quello corrente e ritorna vero se il nuovo record selezionato è valido, falso se non ci sono altri record nella tabella.

Il metodo getString richiede come parametro il nome di un campo della tabella e ritorna il valore di quel campo nel record corrente. Questo metodo legge il valore del campo come se fosse un campo di testo ma esiste un metodo per ogni tipo di dato (getInt, getFloat, getDate, getObject...).

2.5.2 I metadati

Negli esempi precedenti si è lavorato con il presupposto di conoscere a priori lo schema del DB. In alcuni casi comunque questo non avviene e, per esempio, si ha la necessità di visualizzare il contenuto di una tabella senza sapere quanti campia ha, quali sono i loro nomi e che tipo di dati contengono.

Per risolvere il problema si possono utilizzare le interfacce ResultSetMetaData e DatabaseMetaData che consentono di ottenere informazioni sullo schema del database.

Ad esempio, se volessimo visualizzare l'intestazione di una tabella che non conosciamo potremmo utilizzare il seguente codice:

ResultSetMetaData rsMeta = rs.getMetaData();
int nColumn = rsMeta.getColumnCount();

for(int i=1; i <= nColumn; i++)

Con il metodo getMetaData creiamo un oggetto di tipo ResultSetMetaData che ci fornisce i metadati della nostra tabella.

Il metodo getColumnCount ritorna il numero di campi della tabella; il metodo getColumnName ritorna il nome del campo nella posizione i-esima.

Questo frammento di codice, quindi, prima legge quanti campi contiene la tabella e poi li scorre uno a uno visualizzandone il nome.

L'interfaccia ResultSetMetaData consente di ottenere molti altri dati sullo schema di una tabella mentre l'interfaccia DatabaseMetaData fornisce dati generici sulla base di dati come tipo di database, version, grammatiche SQL supportate, istruzioni supportate (ad esempio se è supportata o meno l'istruzione UNION) eccetera.

Java: JDBC e l'accesso ai database

JDBC è un'eccellente interfaccia di programmazione che consente l'accesso ad un database da qualsiasi software Java. Le pagine JSP, in quanto modelli di classi Java, possono sfruttare appieno la potenza e la versatilità di JDBC. Per lavorare con un database sono sufficienti un DBMS ed un corrispondente driver JDBC o ODBC. Questa lezione fa luce su tali concetti.

Le applicazioni Web, nella stragrande maggioranza dei casi, fondano il proprio funzionamento sulla manipolazione delle informazioni provenienti da una base di dati. Trattando la tecnologia JSP, dunque, è impossibile non sfiorare l'argomento. Benché, in linea di massima, l'accesso ad un database da codice JSP possa essere effettuato osservando le comuni norme già valide per qualsiasi software Java, è conveniente dedicare alcune lezioni all'analisi delle applicazioni Web incentrate sulla manipolazione di una base di dati.

JDBC
JDBC (non è una sigla, secondo quanto sostenuto da Sun, anche se molti la interpretano come Java DataBase Connectivity) è un'interfaccia di programmazione che lavora da tramite tra codice Java e database. Più in particolare, JDBC racchiude una serie di classi che permettono l'accesso ad una base di dati mediante metodi e schemi di funzionamento che sono intuitivi e perfettamente in linea con lo stile di programmazione tipico del linguaggio di Sun. In sostanza, quindi, è possibile connettersi ad un particolare database sfruttando un apposito driver JDBC, costituito da una classe Java. Tutti i

principali DBMS dispongono oramai di un driver JDBC appositamente studiato. Esiste poi un particolare driver, chiamato ponte JDBC-ODBC, che permette l'utilizzo di qualsiasi fonte di dati per la quale è disponibile un driver ODBC. Ogni DBMS dotato di un'interfaccia ODBC, ad esempio Microsoft Access, può così essere immediatamente sfruttato da Java e JSP, senza la necessità di un driver appositamente studiato per la connettività da applicazioni Java. Tra JDBC ed il ponte ODBC, quindi, Java è virtualmente dotato della possibilità di interagire con tutti i DBMS in circolazione.

LAVORARE CON JDBC
L'impiego di JDBC è semplice, e solitamente si articola attraverso quattro passi:

  1. Per prima cosa, è necessario caricare il driver idoneo per l'utilizzo del particolare database che si intende sfruttare. Può essere caricato un apposito driver JDBC installato in precedenza nel sistema, oppure può essere sfruttato il ponte JDBC-ODBC. Non è importante il nome o il funzionamento interno del particolare driver selezionato: l'interfaccia di programmazione sarà sempre la medesima.
  2. Si apre una connessione verso il particolare database necessario all'applicazione, sfruttando il driver caricato al passo precedente.
  3. Si impiegano l'interfaccia di JDBC ed il linguaggio SQL per interagire con la base di dati. Generalmente, viene sottoposta al DBMS una query volta all'ottenimento di alcuni risultati.
  4. I risultati ottenuti possono essere manipolati sfruttando le classi JDBC e del codice Java studiato per il compito.

Questo corso non si occupa di alcun particolare DBMS, così come non tratta in maniera approfondita gli argomenti legati all'impiego delle basi di dati e del linguaggio SQL. Tutto quello che sarà fornito in questa sede, pertanto, sarà un approccio generico a JDBC e all'impiego dei database, attraverso la descrizione sommaria del pacchetto java.sql e l'utilizzo di alcuni celebri DBMS.

Saranno inoltre descritte le più basilari istruzioni di SQL. Se non disponete già delle conoscenze necessarie per l'impiego di un particolare tipo di database, dunque, dovrete rifarvi a delle letture di specifico interesse, per esempio dei numeri arretrati della rivista.

UN TEST CON MYSQL
Il primo test sarà effettuato servendosi di MySQL, un noto DBMS open source. Per prima cosa, dunque, dovrete installare tale software nella vostra postazione di lavoro. MySQL è disponibile per diverse piattaforme. Il download può essere eseguito gratuitamente partendo dall'indirizzo https://www.mysql.com. MySQL dispone sia di un driver JDBC sia di un driver ODBC. Per questo primo test sarà impiegato il driver JDBC chiamato Connector/J, che può essere prelevato dalla pagina https://www.mysql.com/downloads/api-jdbc-stable .html. Affinché tale driver possa essere visto dalle applicazioni Java, è necessario notificare alla macchina virtuale la presenza dell'archivio JAR contenuto nel pacchetto scaricato. Per far ciò, è possibile operare in diversi modi. Se utilizzate Tomcat o un Application Server della medesima fattura, vi conviene copiare il pacchetto JAR nella cartella WEB-INF/lib dell'applicazione Web che a breve andremo a realizzare. Se queste due cartelle nidificate non esistono già all'interno dello spazio dedicato all'applicazione, createle da voi. In altri casi, è possibile depositare una copia dell'archivio JAR all'interno della directory jre/lib/ext, nella cartella che contiene l'installazione di Java. Naturalmente, i percorsi e le operazioni necessarie possono variare da un

sistema all'altro. Un'altra efficace tecnica è l'aggiunta del percorso dell'archivio JAR nella variabile di sistema CLASSPATH. Una volta completate le operazioni preliminari, è necessario realizzare un database MySQL utile per il test. Attivate il DBMS, quindi connettetevi ad esso servendovi dell'apposito programma da riga di comando contenuto nella cartella bin di MySQL (mysql.exe, per la versione Windows). Il primo comando da impartire è il seguente:

CREATE DATABASE JSPTest;

Quindi, selezionate il database per l'uso appena creato:

USE JSPTest;

Realizzate la tabella che sarà impiegata per il test, sfruttando il seguente codice SQL:

CREATE TABLE `Persone` (
  Nome  VARCHAR (50) NOT NULL,
  Cognome  VARCHAR (50) NOT NULL,
  Indirizzo VARCHAR (50) NOT NULL);


Infine, è necessario popolare la tabella con alcuni record esemplificativi:

INSERT INTO Persone (
Nome, Cognome, Indirizzo) VALUES (
'Mario', 'Rossi', 'Via Roma 25');
INSERT INTO Persone (Nome, Cognome, Indirizzo)
VALUES ('Luigi', 'Bianchi', 'Via Milano 38');
INSERT INTO Persone (Nome, Cognome, Indirizzo)
VALUES ('Antonio', 'Verdi', 'Via Genova 12');


Giunti a questo punto, abbiamo ottenuto una tabella Persone come quella mostrata di seguito:

Nome

Cognome

Indirizzo

Mario

Rossi

Via Roma 25

Luigi

Bianchi

Via Milano 38

Antonio

Verdi

Via Genova 12



Prepariamoci per utilizzare la tabella da una pagina JSP esemplificativa. Il codice necessario è riportato di seguito:

<%@ page import="java.sql.*" %>
<%! String DRIVER = "com.mysql.jdbc.Driver";
String DB_URL = "jdbc:mysql://localhost:3306/JSPTest";
%>
<html>
<head> <title>Corso di JSP, Lezione 11, Esempio 1</title>
</head>
<body>
  <h1>Accesso ad un database MySQL</h1>
  <% // Carico il driver.
  Class.forName(DRIVER);
  // Preparo il riferimento alla connessione.
  Connection connection = null;
   try
} catch (SQLException e) finally
%>
</body> </html>

Passiamo in rassegna le operazioni effettuate:

<%@ page import="java.sql.*" %>



Con questa riga viene reso visibile il contenuto del pacchetto java.sql, che contiene le API JDBC.

<%! String DRIVER = "com.mysql.jdbc.Driver";
String DB_URL = "jdbc:mysql://localhost:3306/JSPTest"; %>



Qui vengono dichiarate due stringhe. La prima tiene traccia del nome del driver JDBC di cui ci si dovrà servire. Nel caso di Connector/J, che abbiamo installato in precedenza, il percorso da indicare è proprio com.mysql.jdbc.Driver. Con la stringa DB_URL, invece, si tiene traccia del database cui connettersi. La sintassi desiderata da Connector/J è la seguente:

jdbc:mysql://[hostname][:port]/[dbname][?param1=
value1][¶m2=value2]...



In questo caso, si è supposto che MySQL sia in esecuzione sulla stessa macchina che gestirà la pagina JSP (localhost) e che la porta ascoltata dal DBMS sia la numero 3306 (la predefinita di MySQL). Quindi, è stato specificato il nome del database con cui lavorare (JSPTest, creato poco sopra). Non sono stati impiegati parametri aggiuntivi. Se il database da voi realizzato è stato protetto con una coppia di dati username-password, bisognerà impiegare una lista di parametri così organizzata:

jdbc:mysql://localhost:3306/JSPTest?user=
nome&password=pass



In definitiva, il percorso specificato nel codice di esempio deve essere variato in base alle esigenze.

Class.forName(DRIVER);



Questa istruzione carica in memoria il driver, in modo che possa poi essere sfruttato dal codice Java che segue.

Connection connection = null;



Viene preparato il riferimento ad un oggetto Connection, necessario per stabilire una connessione con il DBMS. Tutto quello che segue è racchiuso in un

blocco try ... catch, poiché a rischio di eccezione: i disguidi, infatti, sono sempre in agguato (il DBMS, ad esempio, potrebbe essere momentaneamente non disponibile).

Connection connection =
DriverManager.getConnection(DB_URL);

Questa istruzione stabilisce la connessione ricercata.

Statement statement = connection.createStatement();


Questa riga prepara ed ottiene un oggetto Statement. Questo tipo di oggetto è necessario per interagire con il DBMS attraverso delle query espresse servendosi del linguaggio SQL.

ResultSet resultset = statement.executeQuery(
"SELECT Nome, Cognome, Indirizzo FROM Persone"


Una query SQL che recupera l'intero contenuto della tabella Persone viene sottoposta al DBMS. I risultati saranno conservati in un oggetto di tipo ResultSet.

while (resultset.next())


Questo ciclo scorre l'insieme dei risultati ottenuti. Il corpo del ciclo sarà ripetuto tante volte quanti sono i record restituiti da DBMS. Il metodo next() di un oggetto RecordSet, infatti, ha duplice funzionalità: scorre in avanti l'elenco dei record ottenuti e restituisce true fin quando non si giunge al termine dell'esplorazione.

String nome   = resultset.getString(1);
String cognome = resultset.getString(2);
String indirizzo    = resultset.getString(3);


All'interno del ciclo while, i singoli campi della tabella Persone vengono recuperati e memorizzati all'interno di apposite stringhe.

<b>Nome:</b> <%= nome %><br>
<b>Cognome:</b> <%= cognome %><br>
<b>Indirizzo:</b> <%= indirizzo %><br><br>


Questo codice mostra i risultati in output.

if (connection != null) connection.close();

La connessione, al termine di ogni operazione (clausola finally) viene chiusa, se attiva.

UN TEST CON ACCESS
Andiamo ora a sperimentare una connessione JDBC-ODBC, servendoci di un database Access. Ovviamente, per mettere in pratica l'esperimento, è necessario servirsi di un sistema Windows, giacché Access è un DBMS disponibile esclusivamente per questa piattaforma. Replichiamo quanto fatto per MySQL, realizzando con Access un file JSPTest.mdb che contenga una tabella Persone così strutturata:

  1. Nome, di tipo Testo.
  2. Cognome, di tipo Testo.
  3. Indirizzo, di tipo Testo.

Popolate la tabella con i medesimi dati mostrati nel paragrafo precedente. In questo caso, non è necessario servirsi della riga di comando, giacché Access dispone nativamente di un'interfaccia grafica che semplifica la creazione e l'utilizzo di un database. A questo punto, è necessario registrare il file JSPTest.mdb come sorgente ODBC. Per far ciò, dobbiamo sfruttare l'apposita utility disponibile in Windows, solitamente nominata "Origine Dati (ODBC)".

Nei sistemi di tipo 2000/XP è possibile reperire tale voce tra gli strumenti di amministrazione, mentre le piattaforme di tipo 98/ME conservano qualcosa di

analogo direttamente nel pannello di controllo. Avviate il tool, quindi aggiungete un nuovo riferimento nella scheda "DSN di sistema". Selezionate il driver "Microsoft Access Driver (*.mdb)", associate al DSN il nome JSPTest e quindi selezionate il file JSPTest.mdb. Dovreste ottenere una scheda come quella mostrata in Fig. 2. Confermate pure ogni operazione.
Il codice della pagina JSP da impiegare per il test è riportato di seguito:

&%@ page import="java.sql.*" %>
<%!
String DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";
String DB_URL = "jdbc:odbc:JSPTest"; %>
<html>
<head>
<title>Corso di JSP, Lezione 11, Esempio 2</title>
</head>
<body>
<h1>Accesso ad un database Access</h1>
<%
// Carico il driver.
Class.forName(DRIVER);
// Preparo il riferimento alla connessione.
Connection connection = null;
try
} catch (SQLException e) finally
%>
</body>
</html>



Come è possibile osservare, l'unica reale differenza con il codice sviluppato per la gestione del database di MySQL risiede nella parte:

<%!
// Nome del driver.
String DRIVER = "sun.jdbc.odbc.JdbcOdbcDriver";
// Indirizzo del database.
String DB_URL = "jdbc:odbc:JSPTest";
%>



JDBC, infatti, espone un'interfaccia generica che permette di lavorare alla medesima maniera con tutti i DBMS. In questo caso, infatti, è stato sufficiente specificare il percorso del driver JDBC-ODBC (sun.jdbc.odbc .JdbcOdbcDriver) e l'URL del database Access precedentemente associato ad un DSN di sistema (jdbc:odbc :JSPTest).

CONCLUSIONI
Impiegare JDBC è davvero molto semplice. Bisogna semplicemente disporre degli appositi driver (ricordandosi anche delle possibilità offerte dal ponte JDBC-ODBC), ed è ovviamente necessario documentarsi su come lo specifico driver debba essere caricato e su quale sia sintassi indispensabile per specificare il percorso del database da impiegare. Per il resto, JDBC è una soluzione comoda ed omogenea. In questa lezione ci siamo soffermati principalmente su quello che orbita intorno ai driver JDBC e al loro utilizzo. Successivamente, scenderemo nel dettaglio delle API contenute nel package java.sql, al fine di porre sotto i riflettori i passi necessari per le più comuni operazioni di interazione con un database.







Privacy




Articolo informazione


Hits: 2756
Apprezzato: scheda appunto

Commentare questo articolo:

Non sei registrato
Devi essere registrato per commentare

ISCRIVITI



Copiare il codice

nella pagina web del tuo sito.


Copyright InfTub.com 2024