SETTE GRANI DI CAFFÈ CONTRO LO STRESS GUIDA PER SOSPETTI PROGRAMMATORI JAVA di Giuseppe Vincenzi giuseppevincenzi.branded.me 2 Indice 1. INTRODUZIONE 5 2. IL KIT DELLO SVILUPPATORE JAVA 9 2.1. QUESTO NON È UN CORSO UNIVERSITARIO 9 2.2. UN LINGUAGGIO INTERPRETATO 9 2.3. COS’È IL COMPILATORE JAVA? 10 2.4. COME INSTALLARE LA JDK (“JAVA DEVELOPMENT KIT”) 12 2.5. L’AMBIENTE DI SVILUPPO, LA TELA DEL PITTORE 21 2.6. COME INSTALLARE L’IDE 21 2.7. ESEGUIRE UN PROGRAMMA JAVA COMPILATO 23 3. POLIMORFISMO: L’IPERURANIO INFORMATICO 37 3.1. DALL’IPERURANIO DI PLATONE ALLA PROGRAMMAZIONE ORIENTATA AGLI OGGETTI 37 3.2. UNA PICCOLA LIBRERIA NELL’IPERURANIO 39 3.3. IL POLIMORFISMO E L’EREDITARIETÀ 42 4. COMINCIAMO A SPORCARCI LE MANI: IL PRIMO ESPERIMENTO DELLA MOKA 47 4.1. FACCIAMO UN CAFFÉ: LA CLASSE MOKA 47 4.2. VARIABILI GLOBALI E METODI DI UNA CLASSE 47 4.3. IL METODO “MAIN” 48 4.4. ESPERIMENTO MOKA: SPECIFICHE 49 4.5. COME CREARE UN NUOVO PROGETTO IN ECLIPSE 50 4.6. ESPLORIAMO IL PACKAGE DI UN PROGETTO 57 4.7. CREAZIONE DI UNA CLASSE 64 4.8. DIALOGARE CON IL SISTEMA: LA CLASSE SYSTEM 69 4.9. PORTIAMO A TERMINE L'ESPERIMENTO 70 4.10. ESECUZIONE DI UNA CLASSE JAVA 73 5. ANALISI GRAMMATICALE: COME ESPRIMERSI CORRETTAMENTE IN JAVA 79 5.1. SINTASSI E SEMANTICA DI UN LINGUAGGIO 79 5.2. AVETE QUALCOSA DA DICHIARARE? LA VARIABILI IN JAVA 80 5.3. OPERATORI SULLE VARIABILI 83 5.4. OPERATORI ARITMETICI 83 5.5. OPERATORI ARITMETICI DI ASSEGNAMENTO 84 5.6. OPERATORI RELAZIONALI 85 5.7. OPERATORI LOGICI 86 5.8. OPERATORE TERNARIO 86 3 5.9. GLI ARRAY IN JAVA 87 5.10. ISTANZIARE UN OGGETTO: L’UTILIZZO DI “NEW” 88 5.11. UNA SEQUENZA DI CARATTERI: LA CLASSE STRING 89 5.12. MATRICI E ARRAY MULTIDIMENSIONALI 90 5.13. COSTRUIRE UNA FRASE NEL LINGUAGGIO JAVA: LE ISTRUZIONI 91 5.14. ISTRUZIONE CONDIZIONALE: IL COSTRUTTO IF... ELSE 93 5.15. ISTRUZIONE CONDIZIONALE: IL COSTRUTTO SWITCH 94 5.16. ISTRUZIONE RIPETITIVA: IL COSTRUTTO WHILE 95 5.17. ISTRUZIONE RIPETITIVA: IL COSTRUTTO FOR 96 5.18. ESPERIMENTOMOKASTRING 98 5.19. ESPERIMENTOCERCAMOKA 99 6. ELEMENTI DI SINTASSI PER DESCRIVERE IL NOSTRO IPERURANIO 103 6.1. LE IDEE NELL’IPERURANIO: SINTASSI PER LA DESCRIZIONE DI UNA CLASSE 103 6.2. SINTASSI PER LA DEFINIZIONE DI VARIABILI GLOBALI DI UNA CLASSE 105 6.3. SINTASSI PER LA DEFINIZIONE DEI METODI DI UNA CLASSE 107 6.4. METODO COSTRUTTORE 110 6.5. EREDITARIETÀ DELLE CLASSI 111 6.6. ESPERIMENTO LIBRERIA: SPECIFICHE 112 6.7. CLASSE ASTRATTA 131 6.8. INTERFACCE 132 6.9. LA CLASSE OBJECT 141 6.10. COME IMPLEMENTARE CORRETTAMENTE IL METODO EQUALS 142 7. IMPARIAMO A GESTIRE I NOSTRI ERRORI 147 7.1. EXCEPTION: L’IDEA DELL’ERRORE IN JAVA 147 7.2. ESPERIMENTO DELLA DIVISIONE FRA NUMERI DECIMALI: SPECIFICHE 147 7.3. ESPERIMENTO DELLA DIVISIONE FRA NUMERI DECIMALI: CONSIDERAZIONI SULLE ECCEZIONI 148 7.4. LA CLASSE EXCEPTION 151 7.5. ELEMENTI DI SINTASSI: LE PAROLE CHIAVE “THROWS” E “THROW” 152 7.6. ELEMENTI DI SINTASSI: BLOCCO TRY... CATCH... FINALLY 153 7.7. CREAZIONE DI ERRORE SPECIFICI 155 8. CONCLUSIONI 159 8.1. ORA È IL MOMENTO DI APPROFONDIRE 159 4 1. Introduzione Quando ero piccolo, mi piaceva aprire un Personal Computer, come li chiamavamo allora, e digitare quei due o tre comandi che mi avevano insegnato per veder scorrere tutte quelle lettere e numeri, per me ancora senza molto significato, davanti ai miei occhi: mi ricordava le scene dei film americani dove, gli esperti di computer, non avevano davanti a loro schermi con le finestre, o con i colori, ma sempre schermi neri con lettere verdi che scorrevano velocissime. Ho sempre sognato di capire cosa significassero; ho sempre sognato di guardare quegli schermi con gli stessi occhi con cui li guardava l’attore americano, ovvero, con gli occhi di chi ci capisce qualcosa, di chi ne ha il pieno controllo. O almeno sa farlo credere bene, nel caso specifico dell’attore americano. Spinto da questa curiosità, ho cominciato a leggere manuali sui Sistemi Operativi, ai miei tempi l’MS-DOS andava molto di moda nei Personal Computer casalinghi e non solo: capii però che il Sistema Operativo era già un livello superficiale, rispetto al linguaggio parlato dalle macchine. Mi resi conto che le macchine parlavano altre lingue e, imparandole, si poteva descrivere un problema al proprio PC, spiegargli la soluzione, e da quel momento in poi lui avrebbe svolto un lavoro meccanico e ripetitivo per noi. 5 Ed è così che ho cominciato a studiare i Linguaggi di Programmazione. Conoscere un Linguaggio di Programmazione è come saper scrivere uno spartito che il nostro computer potrà eseguire tutte le volte che vogliamo: l’esempio non è casuale, visto che io che scrivo queste righe sono uno sviluppatore e sono un pianista. E molte sono le cose in comune tra la programmazione e l’arte: grazie alla programmazione, e a tutti gli strumenti che i linguaggi vi mettono a disposizione, voi potrete ogni volta creare un piccolo mondo, attorno al vostro problema, analizzando e scrivendo tutte le varie soluzioni, e mettendo poi a disposizione degli utenti tutto quello che avete immaginato e realizzato. Praticamente è esattamente quello che fa un compositore: grazie al linguaggio musicale e ai suoi strumenti, lui riesce a tradurre tutto il mondo che ha in testa, rispetto ad un argomento che lo ha particolarmente colpito e ispirato, e, cercate e trovate tutte le elaborazioni del tema, mette a disposizione dei suoi ascoltatori il suo spartito, la sua musica. In questo piccolo manuale spesso citerò l’arte e la filosofia per spiegare la programmazione: non ne siate stupiti, perché l’utilizzo del pensiero e della creatività nell’ingegneria del software sono indispensabili. Più di quanto possiate immaginare. 6 La programmazione quindi è uno degli strumenti per mettere in pratica la propria creatività, e proprio per questo anche se non è e non sarà il vostro lavoro, o parte di esso, comunque potrà interessarvi e diventare anche un passatempo: programmare per me personalmente è un po’ come fare bricolage. Io lo chiamo “Bricolage digitale”: d’altronde come possiamo essere utili per appendere una mensola in casa, possiamo esserlo anche per scrivere un’applicazione che ci aiuta a fare i conti a fine mese, o che ci aiuta a mettere in ordine le bottiglie della nostra cantina, o ancora che può farci soltanto divertire. E come vi diranno che la mensola che avete attaccato voi è un po’ storta, e che sarebbe stato meglio chiamare un esperto, così vi diranno che la vostra applicazione per catalogare il vino non funziona benissimo, e che era meglio comprare un software già fatto: ma volete mettere la soddisfazione di vedere una mensola attaccata al muro da voi con sopra le bottiglie con un’etichetta stampata dal vostro piccolo software? Impagabile. Ed è però altrettanto vero che potrebbe diventare anche il vostro lavoro: avere conoscenze di programmazione potrebbe aprirvi nuove porte professionali e magari farvi lanciare in una nuova avventura lavorativa. Studiamo i concetti alla base dei Linguaggi di Programmazione e facciamolo per tenere in allenamento il nostro cervello, ma anche come un piccolo investimento: imparare un linguaggio significa conoscere il seme, l’origine dell’informatica che utilizziamo tutti i giorni, e questo ci 7 aiuterà anche a comprendere meglio gli strumenti con cui operiamo quotidianamente. Una leggenda narra che Baba Budan, un esponente indiano della cultura Sufista, si trovava in Arabia Saudita: conobbe il caffè e così decise di prendere sette chicchi e portarli con sé nella sua isola di origine. Avrebbe potuto direttamente portare con sé la bevanda o i grani già tostati e magari tritati: invece investì sul futuro suo e della sua isola, scegliendo i semi. Tornato a casa, iniziò la coltivazione di caffè nel suo paese, e ora ne è ancora uno dei maggiori produttori; filosofia e informatica hanno legami antichi e inaspettati, infatti, l’isola si chiama Java, e il caffè è lo stesso che bevevano i fondatori del Linguaggio di Programmazione, che descriveremo in questo manuale, e che, per questo, decisero di battezzarlo con lo stesso nome. 8 2. Il kit dello sviluppatore Java 2.1. Questo non è un corso universitario Questo manuale ha un obiettivo “professionalizzante”, e per questo è estremamente orientato all’informatica “on the road”: vorrà farvi divertire a sporcarvi le mani con il linguaggio Java, come vi sporchereste le mani con l’argilla ad un corso per la creazione di vasi in terracotta. Proprio per questo ci tengo, come autore e informatico al tempo stesso, a dire che questo manuale non si sostituisce assolutamente ad un corso universitario o ai manuali utilizzati per lo studio dell’ingegneria informatica: è un manuale che fornisce le basi della programmazione Java, cercando di farvi interessare all’argomento e magari spingervi, dopo aver letto questo libro, ed aver portato a termine gli esercizi che vi propongo, a comprare libri di Tecniche di programmazione sempre più specifici rispetto a quello che voi vorrete poi fare della vostra conoscenza informatica. 2.2. Un linguaggio interpretato Cominciamo a preparare gli strumenti del lavoro dell’informatico. Fossimo stati pittori, vi avrei cominciato a parlare di tavolozze, pennelli, colori e tele, ovvero di quello che potremmo chiamare “Kit del pittore”: ma 9 siamo sviluppatori Java, quindi comincio con il parlarvi del “Java Development Kit”, molto spesso abbreviato in JDK e declinato al femminile, per motivi grammaticalmente oscuri. In questo kit si trovano tutta una serie di strumenti fondamentali per uno sviluppatore Java, uno su tutti, è il javac, ovvero il compilatore del codice Java. 2.3. Cos’è il compilatore Java? Facciamo un piccolo passo indietro. Quando voi scriverete il vostro codice java, lo farete creando dei file che avranno l’estensione “.java” (Libro.java, Scaffale.java, GestioneLibreria.java, ...) : questo file, per essere eseguito da un Sistema Operativo ha bisogno di essere compilato, ovvero tradotto nel dialetto comprensibile dalla macchina su cui il software deve essere eseguito. Immaginate che l’Italia è il Sistema Operativo, dove quindi la lingua ufficiale è l’italiano. Immaginate ora che un Linguaggio di Programmazione è un dialetto parlato in una regione: per essere eseguito a livello nazionale, c’è bisogno di compilarlo, ovvero di tradurlo in italiano; in questo modo noi possiamo avere molteplici linguaggi diversi, come se fossero dialetti di 10 diverse regioni, che potranno essere eseguiti sulla stessa macchina Italia, proprio perché compilati in italiano. Java ha una caratteristica in più rispetto ad altri linguaggi di programmazione: è un linguaggio interpretato. Per tornare al nostro esempio, è come se gli inventori di Java avessero pensato ad un livello di astrazione maggiore rispetto all’italiano, decidendo di compilare il linguaggio Java in una lingua che è comune all’Italia e a tutti gli altri paesi del mondo: è come se venisse compilato in latino. A questo punto, una volta compilato, può essere eseguito in tutti i paesi del mondo senza doverlo ogni volta ricompilare: gli altri linguaggi in Italia sono compilati in italiano, ma se volessero essere eseguiti in Francia, dovrebbero essere ricompilati in francese, che è una lingua di origine latina, come l’italiano. Invece con il latino, basterà dare in mano un dizionario latino ad un francese, e il codice in latino verrà eseguito senza alcuna modifica necessaria. Tornando all’informatica, il compilatore javac compila i vostri file “.java” in file con estensione “.class”, che contengono la traduzione in “bytecode” (nome informatico del latino dell’esempio). Il dizionario latino da avere nei diversi Sistemi Operativi per interpretare il bytecode si chiama Java Virtual Machine. 11 Riassumiamo dunque questa caratteristica fondamentale di Java: i programmi scritti con questo linguaggio sono indipendenti dalla piattaforma, ovvero possono essere eseguiti su qualsiasi Sistema Operativo (DOS, Windows, Unix, Linux, ...), previa installazione di una Java Virtual Machine. 2.4. Come installare la JDK (“Java Development Kit”) Per installare la JDK bisogna seguire i seguenti passi: 12 Aprire un browser e andare sul sito del distributore di Java (Oracle) all’indirizzo http://www.oracle.com/technetwork/java/j avase/downloads/index.html (figura 1) Scegliere la “Java Platform (JDK) 7u17” Fare attenzione, dopo aver scelto la “Java Platform (JDK) 7u17”, a scaricare l’eseguibile dell’installazione che si riferisce al Sistema Operativo che state utilizzando sul vostro PC (Windows, Linux, …) (figura 2) Lanciate l’installazione e seguite le istruzioni che vi appariranno sullo schermo (figura 3, figura 4, figura 5, figura 6, figura 7, figura 8) Figura 1 13 Figura 2 14 Figura 3 15 Figura 4 16 Figura 5 17 Figura 6 18 Figura 7 19 Figura 8 20 2.5. L’ambiente di sviluppo, la tela del pittore Ora non ci resta che scaricare il nostro “ambiente di sviluppo”, che in inglese si chiama “Integrated Development Environment”, spesso abbreviato con l’acronimo IDE. Questo genere di software, come è facile immaginare, non è altro che uno strumento che aiuta lo sviluppatore a scrivere il codice più facilmente: è paragonabile ad un Editor di testi, come Microsoft Word, che offre una serie di strumenti che velocizzano la scrittura di un testo. Quando scrivete una lettera, ad esempio, avete un correttore automatico che vi evidenzia i potenziali errori: l’IDE fa esattamente lo stesso lavoro, mettendovi in evidenza errori nel codice, ed è solo un esempio dei moltissimi strumenti che mette a disposizione. 2.6. Come installare l’IDE Per la programmazione in Java viene spesso usato l’IDE Eclipse: è un software open source sempre in costante sviluppo e aggiornamento, distribuito da un consorzio che si chiama “Eclipse Foundation”. Per installare l’IDE bisogna seguire i seguenti passi: 21 22 Aprire un browser e andare sul sito all’indirizzo http://www.eclipse.org/downloads/ (figura 9) Scaricare “Eclipse IDE for Java EE Developers”, sempre facendo attenzione alla versione del vostro Sistema Operativo Il file ZIP che avrete scaricato (figura 10), basterà voi lo estraiate in una cartella a vostro piacere sul disco rigido (figura 11, figura 12) All’interno della cartella troverete un file che si chiama “eclipse” (figura 13) e che, con un doppio click, aprirà davanti a voi l’ambiente di sviluppo (figura 14). Vi si aprirà una popup che vi indicherà il percorso proposto (una directory che, al primo avvio verrà creata vuota) per il vostro workspace (figura 15). Alla prima apertura di Eclipse vi sarà proposta la finestra di benvenuto: potete chiuderla cliccando sulla ‘x’ accanto alla parola “Welcome” (figura 16). Vi consiglio ora di cambiare la combinazione di colori, per utilizzare quella più utilizzata dagli sviluppatori: Aprite il menu Window e scegliete l’opzione Preferences (figura 17) Nella finestra Preferences, nella parte sinistra, estendete il menù General e dentro cliccate l’opzione Appearance Scegliete il tema “Classic” e cliccate su OK (figura 18) Vi apparirà una finestra (figura 19) che vi chiederà di riavviare Eclipse: cliccate su Ok, chiudete e riavviate Eclipse. Adesso avete tutto il necessario (figura 20) per iniziare a programmare in Java: avete la tela bianca sullo schermo e i pennelli pronti accanto alla tavolozza. 2.7. Eseguire un programma Java compilato Se in futuro vorrete far utilizzare i vostri software a qualcuno, ovviamente questo utente non sarà obbligato a installare tutto quello che avete voi: a lui basterà installare il “Java Runtime Environment”, anche in questo caso spesso abbreviato in JRE e declinato al femminile, per motivi anche stavolta grammaticalmente oscuri. Dopo l’installazione, sulla macchina del vostro utente sarà disponibile la Java Virtual Machine e i comandi necessari a eseguire i file java compilati, o più spesso i cosiddetti file JAR. Cos’è un file JAR? Altro non è che un file compresso, esattamente come uno ZIP, che include più di un file java compilato, magari tutti appartenenti ad un progetto. 23 Figura 9 24 Figura 10 25 Figura 11 26 Figura 12 27 Figura 13 28 Figura 14 29 Figura 15 30 Figura 16 31 Figura 17 32 Figura 18 33 Figura 19 34 Figura 20 35 36 3. Polimorfismo: l’iperuranio informatico 3.1. Dall’iperuranio di Platone alla Programmazione Orientata agli Oggetti Java è un linguaggio di Programmazione Orientata agli Oggetti (OOP, Object Oriented Programming): spiegare cosa significa è molto semplice se facciamo ricorso alla filosofia platonica. Fedro, un giovane ateniese, appassionato di retorica, passeggiando per le strade di Atene si incontra con Socrate e con lui inizia a discorrere dell’arte di plasmare il linguaggio naturale, di meta-linguaggio: questo incontro verrà raccontato da Platone nel dialogo platonico denominato proprio “Fedro”, e ambientato tra il 420 e il 410 a.C. In questo dialogo Platone inserirà un concetto suo, che ora analizzeremo molto in superficie e sarà attraverso questo concetto filosofico che meglio capiremo il concetto di Programmazione Orientata agli Oggetti. Il concetto platonico che ci interessa analizzare è l’Iperuranio: una zona al di là del cielo nella quale risiedono le idee. 37 L’Iperuranio, in quanto posto al di là del limite estremo del luogo fisico, ha una dimensione aspaziale, atemporale, metafisica. Ma cosa contiene dunque questo posto? Contiene le idee di tutte le entità presenti sulla Terra, repliche imperfette dell’idea di esse stesse. Il rapporto tra le idee e le entità terrene può essere di differente natura, secondo la concezione platonica; uno dei rapporti in cui possono trovarsi l’idea e le sue entità terrene è il rapporto di mimesi: secondo questo rapporto gli oggetti terreni sono delle copie delle idee immutabili e perfette. Per esempio, se voi avete un albero (con la a minuscola) nel parco sotto casa, dovete immaginare che quest’albero, come tutti gli altri alberi del parco, non sono altro che una copia, dell’Albero (stavolta con la A maiuscola), intesa come idee perfetta e immutabile dell’albero: da un punto di vista della terminologia, nel mondo del linguaggio Java l’idea di Albero viene denominata classe, mentre la copia, ovvero ognuno degli alberi del parco, viene definita istanza. E allora cosa sono gli oggetti, verso i quali il linguaggio di programmazione è orientato? Gli oggetti sono semplicemente le istanze di una classe: per questo da adesso in poi, nel testo, quando useremo la parola oggetto, indicheremo sempre il concetto di istanza di una classe. 38 3.2. Una piccola libreria nell’iperuranio Un programmatore Java vede tutto il mondo esattamente come Platone: tutto quello che ha intorno è assimilabile a una serie di classi con un livello di astrazione il più alto possibile, e sempre replicabili. Provate a immagine una libreria. Di quali oggetti potreste immaginare composta la vostra libreria? Facciamo una prima divisione immaginando la libreria composta da: Lo scaffale del primo piano Lo scaffale del secondo piano Murder in Mesopotamia (Non c'è più scampo) di Agatha Christie Cards on the Table (Carte in tavola) di Agatha Christie Death on the Nile (Poirot sul Nilo) di Agatha Christie Dumb Witness (Due mesi dopo) di Agatha Christie The Picture of Dorian Gray (Il ritratto di Dorian Gray) di Oscar Wilde A Study in Scarlet (Uno studio in rosso ) di Sir Arthur Conan Doyle 39 Cominciamo a lavorare sull’astrazione dei concetti cercando di avvicinarci il più possibile all’iperuranio, ovvero, all’idea della nostra libreria. Lavoriamo sugli scaffali: ci sono due scaffali, per due piani diversi, ma le caratteristiche dello scaffale sono le stesse in tutte e due gli scaffali dei due piani diversi. Questo mi suggerisce l’esistenza quindi di una classe “Scaffale”, di cui abbiamo due repliche (istanze), che si caratterizzano per la loro altezza da terra, riferendomi al piano. Quindi possiamo dire che l’oggetto Libreria è composto da: 40 Oggetto “Libreria” o Un set di oggetti “Scaffale” o Murder in Mesopotamia (Non c'è più scampo) di Agatha Christie o Cards on the Table (Carte in tavola) di Agatha Christie o Death on the Nile (Poirot sul Nilo) di Agatha Christie o Dumb Witness (Due mesi dopo) di Agatha Christie o The Picture of Dorian Gray (Il ritratto di Dorian Gray) di Oscar Wilde o A Study in Scarlet (Uno studio in rosso) di Sir Arthur Conan Doyle Lavoriamo ora sui libri. Già avremmo facilmente un’astrazione per autore: Oggetto “Libreria” o Un set di oggetti “Scaffale” o Un set di oggetti “Libro di Agatha Christie” o Un set di oggetti “Libro di Oscar Wilde” o Un set di oggetti “Libro di Sir Arthur Conan Doyle” Ma ovviamente non basta, possiamo fare molto meglio, per esempio per genere: Oggetto “Libreria” o Un set di oggetti “Scaffale” o Un set di oggetti “Libro di genere Giallo” (di cui quelli di Agatha Christie e di Sir Arthur Conan Doyle saranno istanze) o Un set di oggetti “Libro di genere Romanzo” A questo punto vi verrà facile immaginare quale può essere un’astrazione ancora migliore e forse conclusiva per la nostra libreria: Oggetto “Libreria” o Un set di oggetti “Scaffale” o Un set di oggetti “Libro” 41 Come potete vedere, lavorando sulla ricerca dell’idea di libreria, nel senso platonico del termine, abbiamo trovato un’astrazione che può descrivere sia la nostra libreria di due piani con cinque libri sopra, che una libreria enorme di 10 piani con mille libri catalogati. Programmare a oggetti significa esattamente questo: individuare le idee (le classi) dei componenti del sistema che vogliamo realizzare, lavorando con le repliche, le istanze degli oggetti astratti. 3.3. Il polimorfismo e l’ereditarietà Finora abbiamo analizzato il ragionamento che può portarci alla definizione del nostro sistema astratto: Oggetto “Libreria” o Un set di oggetti “Scaffale” o Un set di oggetti “Libro” Ma se ora volessimo proprio descrivere la nostra piccola libreria, come dovremmo rapportarci rispetto agli oggetti astratti che abbiamo disegnato? Dobbiamo cominciare ad avvicinarci sempre più alla terminologia e alle specificità del linguaggio Java: cominciamo a parlare nello specifico di Polimorfismo. Cosa si intende per Polimorfismo del linguaggio? Si intende il fatto che ogni classe può essere istanziata e “estesa” in un altra classe che erediterà le 42 caratteristiche della classe estesa, ma ne avrà delle altre specifiche. Partiamo dai nostri scaffali descrivendo la classe, l’idea, di Scaffale, elencando le sue caratteristiche, che nel linguaggio Java chiameremo le variabili globali della classe: Classe “Scaffale” o Lunghezza o Larghezza o Profondità o Colore o Altezza da terra La nostra libreria quindi possiamo cominciare a istanziarla; non faremo dunque solo una descrizione astratta, ma scenderemo nello specifico delle implementazioni: Oggetto “Libreria” o Un set di oggetti “Scaffale” Scaffale#1 Lunghezza: 100cm Larghezza: 1cm Profondità: 30cm Colore: nero Altezzadaterra: 100cm Scaffale#2 Lunghezza: 100cm 43 o Larghezza: 1cm Profondità: 30cm Colore: giallo Altezzadaterra: 150cm Un set di oggetti “Libro” Proviamo ora a descrivere la classe Libro, elencando le sue caratteristiche, ovvero l’elenco delle sue variabili globali di classe: Classe “Libro” o Titolo o Autore o Genere o Protagonista Ora è interessante immaginare anche una specificità rispetto ai libri: un libro giallo, ad esempio, è una cosiddetta “estensione” dell’idea di libro. Cosa significa? Significa che un “Libro Giallo” eredita tutte le caratteristiche di un “Libro” ma, ad esempio, ha la specificità del “Colpevole”. Potremmo sintetizzare scrivendo la classe e l’elenco delle sue variabili globali: 44 Classe “LibroGiallo” o Titolo o o o o Autore Genere Protagonista Colpevole Ma, sfruttando il concetto proprio di Polimorfismo ed ereditarietà potremo scrivere: Classe “LibroGiallo” estende “Libro” o Colpevole E con questa descrizione affermiamo, molto più sinteticamente, che un oggetto “Libro Giallo”, in quanto estensione dell’oggetto “Libro”, avrà certamente tutte le caratteristiche del “Libro”, senza per forza doverle elencare, con in più la specificità del “Colpevole”. Ed ecco che l’istanza della nostra libreria diventa: Oggetto “Libreria” o Un set di oggetti “Scaffale” Scaffale#1 Lunghezza: 100cm Larghezza: 1cm Profondità: 30cm Colore: nero Altezza da terra: 100cm Scaffale#2 Lunghezza: 100cm 45 o 46 Larghezza: 1cm Profondità: 30cm Colore: giallo Altezza da terra: 150cm Un set di oggetti “Libro” “Libro” #1 The Picture of Dorian Gray (Il ritratto di Dorian Gray) Oscar Wilde Romanzo Dorian Gray “LibroGiallo” #2 Murder in Mesopotamia (Non c'è più scampo) Agatha Christie Giallo Hercule Poirot Il professor Leidner “LibroGiallo” #... 4. Cominciamo a sporcarci le mani: il primo esperimento della Moka 4.1. Facciamo un caffè: la classe Moka Affrontiamo ora un problema semplicissimo, giusto per rompere il ghiaccio: faremo un programma in Java che stamperà i passi per la preparazione di un caffè e poi, acceso il fuoco, aspetterà che il caffè è pronto e ci avviserà: ovviamente non ha alcuna utilità un software del genere, se non quella di farci divertire a scrivere le nostre prime istruzioni in Java. Un software del genere potrebbe avere una sola classe che potremmo caratterizzare come di seguito: Oggetto “Moka” o Variabili globali di classe Numero di tazze 4.2. Variabili globali e metodi di una classe Immaginate che la nostra Moka non è una Moka tradizionale, bensì è una di quelle elettroniche, con il tasto per avviarla e che si spengono automaticamente quando il caffè è pronto; il tasto della Moka, nel senso più generale 47 del termine, rappresenta un interruttore che fa partire una funzionalità dell’oggetto: un po’ come per un lettore MP3, ad esempio, avete un tasto che lancia la funzionalità Play, uno che lancia la funzionalità Stop, e un altro ancora che lancia la Riproduzione casuale. Nel linguaggio Java le funzionalità di una classe vengono denominate “metodi”; a questo punto siamo pronti a schematizzare la nostra idea di Moka elencando le sue caratteristiche e le sue funzionalità, ovvero le sue variabili globali e i suoi metodi: Oggetto “Moka” o Variabili globali di classe Numero di tazze (Un numero intero che indica il numero di tazze che possono essere riempite dal caffè prodotto dalla Moka) o Metodi Avvia (Funzionalità che lancia il riscaldamento della Moka, aspetta che il caffè sia pronto, e spegne la Moka avvertendo l’utente) 4.3. Il metodo “main” Ogni classe Java può contemplare la funzionalità detta main: come dice la parola stessa, è una funzionalità 48 “principale”, che conterrà una serie qualsiasi di operazioni, che viene eseguita da un’istanza della classe qualora questa dovesse rappresentare un oggetto che può vivere di vita propria, che può “funzionare” in maniera del tutto indipendente. Immaginiamo la classe MokaTradizionale: lei non potrebbe avere un metodo main, perché da sola non può svolgere nessuna attività, al contrario del nostro esempio di Moka elettronica che può da sola lanciare un processo di funzionamento. 4.4. Esperimento Moka: specifiche L’obiettivo di questo esperimento è creare una classe che chiameremo EsperimentoMoka, con la sola funzionalità main nella quale: Stamperemo sul video (che in un IDE chiameremo console) la sequenza di caratteri, che da ora in poi chiameremo stringa: “Comincia l’Esperimento Moka”; Stamperemo sulla console la stringa: “Passo 1: Apro la moka”; Stamperemo sulla console la stringa: “Passo 2: Metto il caffè”; Stamperemo sulla console la stringa: “Passo 3: Chiudo la moka”; 49 Stamperemo sulla console la stringa: “Passo 4: Premo sul tasto Avvia”; Stamperemo sulla console la stringa: “Passo 5: Il caffè è pronto!”; Stamperemo sulla console la stringa: “Fine dell’Esperimento Moka”; 4.5. Come creare un nuovo progetto in Eclipse Lanciate Eclipse e confermate il percorso del vostro workspace. Appena l’ambiente di lavoro è pronto, cliccate sul menù File e scegliete la voce New: si aprirà un menù a tendina contenente tutti i possibili tipi di progetto che si possono creare con Eclipse. Voi scegliete Project (figura 21): nella finestra che si aprirà davanti a voi (figura 22) scegliete la tipologia Java Project e cliccate su Next. Qui (figura 23) possiamo scegliere: 50 Il nome da dare al progetto, che poi sarà il nome di una cartella che Eclipse creerà nel percorso del workspace sul nostro disco rigido: digitate EsperimentoMoka; La JRE (Java Runtime Environment, vedi Capitolo “Il kit dello sviluppatore Java”) che vogliamo usare: potremmo avere JRE diverse installate sullo stesso PC; Il Project layout: qui possiamo decidere se dividere in cartelle separate i file con il codice sorgente, ovvero quello scritto dallo sviluppatore in Java (file con estensione .java), dai file contenenti il codice compilato (file con estensione .class). Generalmente si usa la preselezione suggerita da Eclipse, ovvero la separazione dei file in cartelle diverse. Cliccate su Finish. A questo punto vi sarà suggerito di aprire una nuova prospettiva da Eclipse (figura 24): in pratica questo software ha la capacità di cambiare faccia in base alla tipologia di progetto in lavorazione. Accettate il cambio di prospettiva verso la “Java Perspective”: il vostro ambiente di lavoro verrà trasformato e adattato al meglio rispetto al tipo di progetto su cui lavorerete. Il vostro progetto è stato creato: nella sezione a destra dello schermo “Package Explorer” vedrete apparire il vostro progetto “EsperimentoMoka” (figura 25). 51 Figura 21 52 Figura 22 53 Figura 23 54 Figura 24 55 Figura 25 56 4.6. Esploriamo il package di un progetto Ma che cos’è un package in Java? Provate a cliccare la freccia a destra del nome del progetto “EsperimentoMoka”: vi si aprirà il contenuto del progetto. Vedrete due linee: Un’icona simile alle icone che identificano le cartelle, con accanto il nome src Una riga con scritto “JRE System Library” Della seconda riga si parlerà più avanti; la prima riga invece rappresenta la cartella “source”, ovvero la cartella che conterrà tutti i file del codice sorgente: tutti i file del codice sorgente possono essere organizzati, a loro volta, in sottocartelle. Proprio queste sottocartelle in cui vengono organizzati i file del codice sorgente si chiamano package. Per esempio creiamo un nostro package “esperimento”: per farlo cliccate con il tasto destro del mouse sulla cartella src, scegliete la voce New, e dal menù a tendina scegliete la voce package (figura 26). Nella finestra che si aprirà per la creazione del package (figura 27), scrivete il nome “esperimento” e cliccate sul tasto Finish. 57 Figura 26 58 Figura 27 59 Vedrete comparire, nel Package Explorer, il vostro nuovo package “esperimento” (figura 28). Come avrete capito, si possono creare infiniti package per meglio organizzare il nostro codice, e ovviamente si possono creare package a più livelli: ad esempio ora noi possiamo creare un sottopackage del package “esperimento” che chiameremo “mokasemplice”. Per farlo cliccate con il tasto destro del mouse sul package “esperimento”, scegliete la voce New, e dal menù a tendina scegliete la voce package. Nella finestra che si aprirà per la creazione del package, nel campo del nome troverete già scritto “esperimento”: per creare il sottopackage, basterà scrivere “esperimento.mokasemplice” (figura 29). Cliccate sul tasto Finish e vederete comparire, nel Package Explorer, il vostro nuovo package “esperimento.mokasemplice” (figura 30). Da un punto di visto fisico non stiamo facendo altro, come avrete ormai capito, che creare delle cartelle e sottocartelle nel nostro workspace: nel caso del mio workspace (C:\Users\Giuseppe\workspace\), sul mio disco rigido, avremo creato cartelle fino alla profondità: C:\Users\Giuseppe\workspace\EsperimentoMoka\src\esperime nto\mokasemplice 60 Figura 28 61 Figura 29 62 Figura 30 63 4.7. Creazione di una classe A questo punto siamo pronti a creare la nostra classe in Java. Cliccate il tasto destro con il puntatore sul package “mokasemplice”: dal menù contestuale scegliere New e poi la voce Class (figura 31). La finestra di dialogo che vi si apre davanti (figura 32) è una delle finestre che userete di più nella vostra esperienza di sviluppatori Java; in questa finestra potrete selezionare tutte le caratteristiche principali di una classe, queste sono quelle che per ora voi potrete comprendere: 64 Il percorso della cartella che contiene il codice sorgente (“EsperimentoMoka/src”), sempre ovviamente a partire dall’indirizzo fisico del vostro workspace Il package nel quale creare la Classe (“esperimento.mokasemplice”) Il nome della classe (“EsperimentoMoka”) Potete chiedere a Eclipse di creare la classe direttamente con il metodo main già pronto per essere completato da voi, e per farlo basta spuntare la voce “public static void main(String[])” Figura 31 65 Figura 32 66 A questo punto potete cliccare su Finish, e si aprirà davanti a voi la classe che avete appena creato con il metodo main auto-generato da Eclipse (figura 33): tra l’altro nel corpo del metodo vedrete già una riga di commento che vi segnala che il metodo è auto-generato ed è ancora “da fare”, o come si dice in inglese, è ancora in “TODO”. 67 Figura 33 68 4.8. Dialogare con il sistema: la classe System Il nostro esperimento ci chiede di far stampare sulla console una serie di stringhe definite. Per stampare sulla console possiamo usare una classe fornita con la JDK che si chiama System. Questa classe, tra le altre cose, gestisce il flusso di dati che arrivano dalla console (in termini tecnici lo chiamiamo InputStream) e il flusso di dati che vanno verso la console (in termini tecnici, come potrete immaginare, vista la poca fantasia degli informatici, si chiama OutputStream). Questi due stream dovete immaginarli come variabili globali della classe System; potremo usarli come oggetti delle classi InputStream e OutputStream con i nomi delle variabili di classe "System.in" e "System.out"; in particolare, per il nostro esperimento, noi useremo un metodo dell'oggetto System.out che rappresenta la funzionalità che da la possibilità di scrivere una stringa sulla console: System.out o Lista di metodi associati print(String) Metodo per scrivere il contenuto di un 69 oggetto String (una stringa) println(String) Metodo per scrivere il contenuto di un oggetto String (una stringa), spostando poi il cursore sulla linea successiva 4.9. Portiamo a termine l'esperimento Ora abbiamo tutte le conoscenze per poter portare a termine il nostro primo esperimento: è arrivato il momento di scrivere il corpo del metodo main della nostra classe EsperimentoMoka (figura 34): package esperimento.mokasemplice; public class EsperimentoMoka { /** * @paramargs */ public static void main(String[] args) { System.out.println("Comincia l’Esperimento Moka"); System.out.println("Passo 1: Apro la moka"); System.out.println("Passo 2: Metto il caffé"); System.out.println("Passo 3: Chiudo la moka"); System.out.println("Passo 4: Premo sul tasto Avvia"); System.out.println("Passo 5: Il caffé è pronto!"); System.out.println("Fine dell’Esperimento Moka"); } } 70 Alla luce di quanto ci siamo detti finora possiamo comprendere tutto quello che c'è scritto in questo codice: abbiamo l'invocazione del metodo usato per scrivere sulla console ripetuta sette volte, come da specifiche dell’esperimento. Cosa cambia tra un'invocazione e un'altra? La differenza sta solo nella stringa da stampare, ovvero in quello che si chiama il parametro di un metodo, nello specifico parliamo del parametro del metodo “System.out.println(String s)”. 71 72 Figura 34 4.10. Esecuzione di una classe Java Per eseguire una classe Java, è necessario che la classe contenga la definizione di un metodo main: questa è comprensibile alla luce anche di quanto ci siamo detti nel paragrafo Il metodo main nel quale proprio ci siamo detti che una classe può vivere di vita propria se contiene questo metodo al suo interno, perciò sarà possibile eseguire le funzionalità elencate nel suo metodo di funzionamento principale. Quando in Eclipse abbiamo aperta, nello spazio di lavoro davanti a noi, una classe contenente un metodo main, possiamo eseguirla cliccando sul tasto “Run”, ovvero l’icona con il simbolo del “Play” su fondo verde (figura 35). Prima di premerlo, diciamoci cosa significa eseguire e cosa facciamo premendo questo tasto. La prima cosa che il tasto Run farà sarà quella di compilare il codice; come ci siamo detti nei primi capitoli di questo manuale, la compilazione è un passaggio obbligato e fondamentale nel linguaggio Java: la compilazione della classe “EsperimentoMoka.java” genererà un file di classe compilato contenente il bytecode, che si chiamerà “EsperimentoMoka.class”. Dopodiché sfrutteremo un componente della JDK che si chiama “java.exe” che ha il compito di eseguire sulla Java Virtual Machine il codice compilato: questo 73 componente aprirà la console e mostrerà all’utente il risultato del programma Java in esecuzione. Ma tutto questo che ci siamo detti, utilizzando un IDE come Eclipse, sarà un lavoro sporco che il software farà per noi: cliccando sul tasto Run a noi verrà direttamente aperta la console in una finestra specifica che ci mostrerà il risultato di quello che abbiamo scritto. Cliccate dunque sul tasto Run e godetevi il caffè che abbiamo preparato (figura 36): in base alla risoluzione del vostro monitor potete allargare la console posizionando il puntatore del mouse sui bordi della console stessa e tenendo premuto il tasto destro (figura 37). Con un doppio click sull’etichetta “Console” avrete il suo contenuto a tutto schermo (figura 38); con un secondo doppio click tornerete alla dimensione normale. Ripensando ora alle specifiche dell’EsperimentoMoka, otterrete proprio la stampa delle stringhe che abbiamo inserito come parametro nel metodo System.out.println(String s): godetevi il vostro primo esperimento riuscito con successo, magari preparando e bevendo un caffè vero. Ve lo siete meritati. 74 Figura 35 75 Figura 36 76 Figura 37 77 Figura 38 78 5. Analisi grammaticale: come esprimersi correttamente in Java 5.1. Sintassi e semantica di un linguaggio Mi è capitato spesso, soprattutto con gli italiani all’estero, di sentire persone che si esprimono in una lingua, riuscendo anche a farsi capire, ma utilizzando male lo strumento del linguaggio; non è raro sentire un italiano parlare in francese, ordinare la sua cena in un ristorante, magari commettendo molti errori grammaticali, o di sintassi, ma essere comunque capiti dal cameriere. Anzi, l’esempio che ho scelto forse non è dei migliori, perché i francesi sono conosciuti per essere intransigenti sulla sintassi: l’italiano dell’esempio rischia di non mangiare, o di ripiegare su un fast food nei dintorni: per uno straniero in Francia è decisamente più facile ordinare un hamburger che una cena più complessa. Con il linguaggio di programmazione si rischia lo stesso errore: finora abbiamo usato Java come un italiano all’estero da qualche mese usa il francese, ovvero, ci siamo fatti capire ma senza avere una vera conoscenza delle regola sintattiche e semantiche di Java. Ma chiariamo già un concetto: che differenza c’è fra semantica e sintattica di un linguaggio? Noi affronteremo ora i due aspetti, parlando di Java, allo stesso tempo; è necessario però sapere che per sintassi di un linguaggio si intendono le regole che permettono di stabilire se un’espressione (una frase) appartiene o meno ad un dato linguaggio; per semantica invece si intendono le regole 79 che stabiliscono se le espressioni sono valide per un dato linguaggio: è lo strumento chiave per l’interpretazione del linguaggio stesso. 5.2. Avete qualcosa da dichiarare? La variabili in Java L’italiano che prima ha avuto grosse difficoltà a ordinare la sua cena al ristorante in Costa Azzurra in Francia, immaginate che abbia deciso di arrendersi e andare a mangiare in un ristorantino della vicina Ventimiglia: in questa città italiana vanno anche molti francesi, e non per problemi con le ordinazioni, ma per godere del mercato ligure del sabato. Al rientro, sia i francesi, che il povero italiano finalmente sazio, si possono trovare al casello della frontiera al cospetto della Guardia di Finanza italiana che chiede se abbiamo qualcosa da dichiarare. Per esempio potremmo avere pacchi che possono contenere qualsiasi cosa, certamente legale, ma che è necessario dichiarare al momento del passaggio in Francia. Immaginate una variabile Java esattamente come uno scatolo nel quale potete mettere qualsiasi cosa, ma che deve necessariamente essere dichiarata prima di utilizzarla “legalmente”. 80 Analizziamo la sintassi di una dichiarazione di variabile in Java attraverso un esempio: int numero; Analizziamo la sintassi partendo da destra: Simbolo ‘;’ (punto e virgola) o Questo simbolo viene inserito alla fine della dichiarazione; numero o Nome della variabile int o Dichiarazione del tipo Ogni variabile ha un suo tipo che ne contraddistingue il contenuto In Java esistono i cosiddetti tipi primitivi che sono tipi di base del linguaggio Java Per ogni tipo di base esiste una classe corrispondente nel package java.lang che lo ingloba, detta Classe Wrapper 81 Nome del Tipo Contenuto Classe wrapper byte intero con segno a 8 bit Byte short intero con segno a 16 bit Short int intero con segno a 32 bit Integer long intero con segno a 64 bit Long float virgola mobile a 32 bit singola precisione Float double virgola mobile a 64 bit singola precisione Double char carattere singolo Unicode intero senza segno a 16 bit Character boolean Contiene un valore true o false Boolean Vediamo ora altri esempi di dichiarazione di variabile e analizziamone la sintassi: int base, esponente; int altezza = 10; int larghezza = 20, profondità = 50; Il primo è un esempio di dichiarazione di più variabili dello stesso tipo; il secondo è la dichiarazione di una variabile, definendone già il suo contenuto al momento della dichiarazione; il terzo è un esempio di dichiarazione di più variabili dello stesso tipo, con definizione del loro contenuto. 82 5.3. Operatori sulle variabili In Java per effettuare operazioni tra le variabili, si usano i cosiddetti operatori: è un concetto identico a quello di operazione nell’aritmetica. Gli operatori sono funzioni che si applicano tra due o più variabili dello stesso tipo (operandi), e danno origine ad una variabile risultato, sempre ovviamente dello stesso tipo degli operandi. Proprio come nelle quattro operazioni della matematica, esiste una priorità tra gli operatori di Java, qualora ce ne fossero diversi nella stessa istruzione. 5.4. Operatori aritmetici Questi tipi di operatori sono i seguenti: Simbolo ‘*’ o Moltiplicazione tra variabili di tipo numerico Simbolo ‘/’ o Divisione tra variabili di tipo numerico Simbolo ‘%’ o Operazione modulo, ovvero il resto della divisione tra due variabili numeriche Simbolo ‘-’ o Sottrazione tra variabili di tipo numerico Simbolo ‘+’ o Addizione tra variabili di tipo numerico 83 o Concatenazione di variabili di tipo carattere (char, Character, String, ...) 5.5. Operatori aritmetici di assegnamento 84 Simbolo ‘*=’ o Moltiplicazione tra variabili di tipo numerico e assegnamento del risultato alla variabile a sinistra dell’operatore Simbolo ‘/=’ o Divisione tra variabili di tipo numerico e assegnamento del risultato alla variabile a sinistra dell’operatore Simbolo ‘%=’ o Operazione modulo, ovvero il resto della divisione tra due variabili numeriche e assegnamento del risultato alla variabile a sinistra dell’operatore Simbolo ‘-=’ o Sottrazione tra variabili di tipo numerico e assegnamento del risultato alla variabile a sinistra dell’operatore Simbolo ‘+=’ o Addizione tra variabili di tipo numerico e assegnamento del risultato alla variabile a sinistra dell’operatore Concatenazione di variabili di tipo carattere (char, Character, String, ...) e assegnamento del risultato alla variabile a sinistra dell’operatore 5.6. Operatori relazionali Tutti i tipi primitivi, tranne i booleani, possono essere operandi degli operatori relazionali: in pratica viene verificata una certa relazione tra i due tipi. Gli operatori relazionali sono i seguenti: Simbolo ‘>’ o Verifica che la variabile a sinistra del simbolo sia maggiore di quella a destra Simbolo ‘<’ o Verifica che la variabile a sinistra del simbolo sia minore di quella a destra Simbolo ‘>=’ o Verifica che la variabile a sinistra del simbolo sia maggiore o uguale a quella a destra Simbolo ‘<=’ o Verifica che la variabile a sinistra del simbolo sia minore o uguale a quella a destra Simbolo ‘==’ o Verifica che la variabile a sinistra del simbolo sia uguale a quella a destra 85 Simbolo ‘!=’ o Verifica che la variabile a sinistra del simbolo sia diversa da quella a destra 5.7. Operatori logici Questi operatori vengono usati tra tipi booleani, e sono una rappresentazione delle classiche funzioni logiche: Simbolo ‘&&’ o Rappresenta l’operazione di AND logico Simbolo ‘||’ o Rappresenta l’operazione di OR logico Simbolo ‘&’ o Rappresenta l’operazione di AND logico booleano Simbolo ‘|’ o Rappresenta l’operazione di OR logico booleano Simbolo ‘^’ o Rappresenta l’operazione di XOR logico Simbolo ‘!’ o Rappresenta l’operazione di NOT logico 5.8. Operatore ternario Questo è un particolare operatore utilizzato per scegliere quale valore assegnare ad un variabile, basandosi su una 86 certa condizione. Vi propongo un esempio di utilizzo che ne chiarisce la sintassi: boolean condizione = true; int numero = condizione ? 5 : 10; Traducendo in italiano quello che c’è scritto nella seconda riga diremo: se la variabile condizione è verificata, allora assegno alla variabile numero il valore 5, altrimenti assegno il valore 10. 5.9. Gli array in Java Tornando all’esempio del controllo doganale, tutti i nostri pacchi potrebbero essere inseriti in strutture che li contengono: ad esempio se abbiamo comprato un bustone di mozzarelle in offerta, ogni mozzarella sarà contenuta nella sua bustina, ma tutte le mozzarelle saranno a loro volta contenute nel bustone, sul quale c’è scritto quante mozzarelle contiene (ed è ovvio che potrà contenere solo mozzarelle dello stesso tipo). In Java una struttura come il bustone delle mozzarelle è l’array; per definire una struttura che contiene numeri interi, ad esempio, scriveremo: int[] busta; 87 Se questa busta è il contenitore con i numeri della tombola, allora possiamo già definire il suo contenuto e scrivere una dichiarazione completa dell’array: int[] tombolone = new int[90]; Vediamo ora la sintassi per l’inserimento di elementi in un array, facendo l’esempio del gioco delle tre carte, dove solo una è quella vincente; in questo caso, tipico esempio di utilizzo del valore booleano, potremo dichiarare così l’array delle tre carte, con una delle tre vincente: boolean[] treCarte = new boolean[3]; treCarte[0]=false; treCarte[1]=true; treCarte[2]=false; 5.10. Istanziare un oggetto: l’utilizzo di “new” Nella dichiarazione del contenuto abbiamo inserito la parola chiave new: è una parola che definisce la creazione di una spazio di memoria nella quale non metteremo un tipo primitivo, ma un qualsiasi oggetto più complesso, e questo vale sia per l’array come per la dichiarazione di un’istanza di un qualsiasi oggetto non primitivo: Integer base, esponente; Integer altezza = new Integer(10); Long larghezza = new Long(20); Float profondità = new Float(50); Boolean[] treCarte = new Boolean[3]; treCarte[0]= new Boolean(false); treCarte[1]= new Boolean(true); treCarte[2]= new Boolean(false); 88 Più avanti vedremo che, l’utilizzo del new, viene definito “istruzione” per la creazione di oggetti. 5.11. Una sequenza di caratteri: la classe String Parlando di array, non si può non fare subito riferimento all’oggetto String. anche nell’esempio della Moka abbiamo cominciato a utilizzare il termine stringa, volendo indicare, con questo termine, una sequenza di caratteri. Prendiamo ad esempio una delle istruzioni che abbiamo usato nell’EsperimentoMoka, ma con un parametro stavolta diverso: System.out.println("Ciao"); Nella spiegazione dell’esperimento, abbiamo parlato del metodo “System.out.println(String s)”, dicendo che accetta come parametro proprio un oggetto String. Logicamente possiamo dedurre che l’elemento "Ciao" è riconducibile ad un oggetto di tipo String; infatti in realtà si tratta di una sequenza di caratteri che, utilizzando i tipi primitivi e gli array, potremmo dichiarare come di seguito: char[] arrayOfChars = new char[4]; arrayOfChars[0] = 'C'; arrayOfChars[1] = 'i'; arrayOfChars[2] = 'a'; arrayOfChars[3] = 'o'; 89 Sarebbe complesso trattare le stringhe come array di caratteri, e proprio in questo l’oggetto String ci viene in soccorso: è lui che fa il duro lavoro dietro le quinte di gestire l’array di caratteri, e a noi programmatori espone una serie di metodi per una gestione decisamente facilitata. Alla fine del capitolo trovate l’EsperimentoMokaString sull’utilizzo della classe String e di alcuni suoi metodi. 5.12. Matrici e array multidimensionali Un’importante struttura dati, legata al concetto di array, è l’array multidimensionale: detta così può sembrare un oggetto distante anni luce dai nostri problemi quotidiani, ed invece vi assicuro che, quando vi troverete a tradurre in Java i vostri problemi, troverete sempre un’applicazione di questa struttura. Spiegarla è semplice, perché basta che voi immaginiate la griglia della battaglia navale (una matrice, come la chiamerebbero i matematici e gli informatici), non per forza quadrata: immaginiamo ad esempio un campo di battaglia con un numero generico di righe e un numero generico di colonne. Vediamo la sintassi per dichiarare una matrice così fatta: int rows = 6, coloumns = 8; boolean[][] battleField = new boolean[rows][coloumns]; Alla fine del capitolo trovate l’EsperimentoCercaMoka sull’utilizzo della matrice. 90 5.13. Costruire una frase nel linguaggio Java: le istruzioni Come in qualsiasi altra lingua o linguaggio, anche Java ha le sue frasi: parliamo delle istruzioni. Analizziamo la classificazione di massima delle istruzioni Java, sottolineando le tipologie di istruzioni che finora abbiamo già avuto modo di utilizzare: Istruzione o Semplice Istruzione vuota Costituita solo dal punto e virgola, non esegue nulla: viene inserita per problemi di formattazione o chiarezza del codice Istruzione di return Questa istruzione termina un blocco di codice, e nel caso dei metodi di un oggetto, è utilizzata per terminare una funzione e restituire un risultato Istruzione di break Questa istruzione è utilizzata per terminare un’istruzione ripetitiva Istruzione di continue Questa istruzione è utilizzata per ricominciare un’istruzione ripetitiva, senza tener conto di eventuali istruzioni successive al continue Incremento o decremento 91 92 Questa istruzione è utilizzata per incrementare o decrementare una variabile di tipo numerico di una unità o L’istruzione di incremento è “++”, scritta accanto alla variabile alla quale sommare una unità o L’istruzione di decremento è “--”, scritta accanto alla variabile alla quale sommare una unità o Se in una istruzione l’incremento o il decremento è scritto dopo la variabile, allora prima viene eseguita l’istruzione e poi viene aumentata la variabile; se è scritto prima, si ottiene il comportamento contrario Assegnazione L’operatore di assegnazione ‘=’ rappresenta l’istruzione che assegna un dato valora ad una variabile Invocazione di un metodo L’invocazione del metodo println dell’oggetto System.out è considerata un’istruzione Creazione di un oggetto L’utilizzo di “new” rappresenta un’istruzione di creazione di oggetto o Strutturata Blocco di codice La coppia di parentesi graffe, che abbiamo già visto all’apertura e chiusura della classe, e all’apertura e chiusura di un metodo, rappresenta essa stessa una singola istruzione che contiene al suo interno altre istruzioni Istruzione condizionale Istruzione ripetitiva 5.14. Istruzione condizionale: il costrutto if... else Il costrutto condizionale if... else da la possibilità allo sviluppatore di utilizzare blocchi di codice diversi, in base a condizioni differenti. Il costrutto è molto semplice da descrivere, e già nella sua sintassi sta tutta la sua logica di funzionamento: boolean condizione = ...; if(condizione){ // Codice eseguito in caso di condizione verificata }else{ // Codice eseguito in caso di condizione non // verificata } Per tradurla nella nostra lingua madre con questa istruzione diciamo: “se è vera un data condizione, allora eseguiamo il blocco di codice successivo, altrimenti eseguiamo il blocco di codice successivo alla parola chiave ‘else’”. 93 5.15. Istruzione condizionale: il costrutto switch Il costrutto condizionale switch da la possibilità allo sviluppatore di utilizzare blocchi di codice diversi, in base al valore di una variabile numerica. Per capirne il significato si può immaginare il funzionamento di un distributore automatico: in base al bottone selezionato, che potrebbe essere rappresentato da un numero, la macchinetta avrà un comportamento diverso. Questa la sintassi del costrutto: int scelta = 2; switch(scelta){ case 0: macchiato(); break; case 1: lungo(); break; case 2: normale(); break; default: restituisciMoneta(); } Ad ogni valore possibile della variabile scelta sarà associato un blocco case: alla fine delle istruzioni associate ad ognuno dei case, bisognerà inserire l’istruzione di break, che impedisce all’esecuzione di andare avanti negli altri casi possibili. Infine si può inserire un caso default che sarà eseguito nel caso in cui la variabile scelta non assume nessuno dei valori elencati nei case. 94 5.16. Istruzione ripetitiva: il costrutto while Questo costrutto fornisce la possibilità di ripetere lo stesso blocco di codice fino a che una certa condizione (variabile di tipo boolean) risulta ancora verificata. Nel blocco di codice associato al ciclo while si può usare: L’istruzione di break, per interrompere il ciclo e riprendere l’esecuzione a partire dall’istruzione subito successiva al while, uscendo dal ciclo L’istruzione continue, per interrompere il ciclo e riprenderlo dall’inizio. Qui di seguito la sintassi dell’istruzione while: boolean condizione = ...; while(condizione){ // Codice eseguito in caso di condizione verificata } 95 Ora vi propongo un blocco di codice, che potrete inserire in un metodo main ed eseguire, per vedere quale sarà il risultato sulla console, e se è quella che vi aspettate: public static void main(String[] args) { int contatore = 0; while(contatore >= 0){ contatore++; if(contatore%2==0){ System.out.print(contatore); }else{ continue; } System.out.println(" è un numero pari"); if(contatore==20){ break; } } System.out.println("Il test è finito"); } 5.17. Istruzione ripetitiva: il costrutto for Il costrutto for è un’istruzione ripetitiva che da la possibilità di ripetere un blocco di codice per un numero prestabilito di volte. Nella dichiarazione di un blocco for, abbiamo la definizione di un indice, con un suo valora iniziale; poi dichiariamo una condizione sull’indice e il ciclo verrà effettuato fino a che questa condizione è vera; infine definiamo un aggiornamento dell’indice che verrà fatto ad ogni ciclo completato. 96 Vi propongo un esempio di ciclo for che chiarisce la sintassi: char[] arrayOfChars = new char[4]; arrayOfChars[0] = 'C'; arrayOfChars[1] = 'i'; arrayOfChars[2] = 'a'; arrayOfChars[3] = 'o'; int valoreLimite = arrayOfChars.length - 1; for (int index = 0; index <= valoreLimite; index++) { System.out.print(arrayOfChars[index]); } In questo blocco di codice: Si definisce un array di caratteri Vengono associati dei caratteri ad ognuna delle posizioni dell’array Poi viene definito un valoreLimite pari alla lunghezza dell’array creata Infine abbiamo un istruzione for o L’indice parte da zero o Il ciclo sarà ripetuto fino a che l’indice è inferiore o uguale al valore limite o Un’istruzione di incremento è l’aggiornamento che viene fatto alla fine di ogni ciclo Nel blocco di codice ripetuto abbiamo la scrittura sulla console del carattere presente nell’array nella posizione pari all’indice corrente 97 5.18. EsperimentoMokaString Ho realizzato, e vi propongo il codice, una riscrittura della classe EsperimentoMoka alla luce di quello che ci siamo detti in questo capitolo. La differenza, come vedrete sta nell’uso dell’oggetto String, nell’utillizzo degli array, e nell’utilizzo del costrutto for e degli operatori sulle stringhe. package esperimento.mokasemplice; public class EsperimentoMokaString { /** * @param args */ public static void main(String[] args) { //Dichiarazione delle stringhe di inizio e fine esperimento String[] messages = new String[2]; messages[0]=new String("Comincia l’Esperimento Moka"); messages[1]=new String("Fine dell’Esperimento Moka"); //Dichiarazione delle stringhe di descrizione degli steps String[] steps = new String[5]; steps[0] = new String("Apro la moka"); steps[1] = new String("Metto il caffé"); steps[2] = new String("Chiudo la moka"); steps[3] = new String("Premo sul tasto Avvia"); steps[4] = new String("Il caffé è pronto!"); //Stampa sulla console del messaggio di inizio esperimento System.out.println(messages[0]); //Stampa sulla console della descrizione di ogni step for(int i = 0; i<steps.length; i++){ String index = String.valueOf(i+1); System.out.println("Passo "+index+": "+steps[i]); } //Stampa sulla console del messaggio di fine esperimento System.out.println(messages[1]); } } 98 5.19. EsperimentoCercaMoka Qui invece vi propongo un semplice esercizio. Immaginiamo di avere una cucina con una serie di dispense appese, ed un’altra serie di dispense in basso: in tutto abbiamo 4 dispense in alto e 4 in basso. La nostra compagna, ogni volta che lava la Moka dopo aver fatto il caffè, la posiziona sempre in posti diversi. Scriviamo un software che posiziona a casa la Moka in uno qualsiasi di questi scaffali; poi chiede a noi le coordinate della dispensa dove crediamo che sia la Moka (‘0,0’ per la prima dispensa in alto a sinistra, fino a ‘2,3’ per l’ultima dispensa in basso a destra), e se la troviamo si complimenta con noi. Io ora vi scrivo come ho risolto io il problema, voi provate a farlo a modo vostro. package esperimento.mokasemplice; import java.util.Random; import java.util.Scanner; public class EsperimentoCercaMoka { /** * EsperimentoCercaMoka * * @param args */ public static void main(String[] args) { System.out.println("EsperimentoCercaMoka"); // Instanziazione di un oggetto di tipo Random // che ha la capacità di creare oggetti con valori // casuali Random casuale = new Random(); // Instanziazione della matrice che rappresenta la // nostra credenza Boolean[][] credenza = new Boolean[2][4]; 99 // Questa variabile diventa verificata (true) quando la // Moka // viene posizionata in un posta casuale della credenza Boolean mokaPosizionata = false; System.out.println("La tua compagna inizia a scegliere il posto per la Moka dopo averla lavata"); // Doppio ciclo for: // 1. Il primo ciclo serve per passare in rassegna tutte // le righe della matrice // 2. Il secondo ciclo, per ogni riga, passa in rassegan // tutte le colonne della matrice for (int row = 0; row < credenza.length; row++) { for (int coloumn = 0; coloumn < credenza[0].length; coloumn++) { // Prima ci accertiamo che la Moka non sia già stata // posizionata, perché in questo caso possiamo // inserire il valore false nelle posizioni restanti if (!mokaPosizionata) { // Se la Moka non è posizionata, generiamo un // prossimo valora casuale booleano per capire se // è il momento di posizionare la Moka Boolean posizionareMoka = casuale.nextBoolean(); // Assegniamo il valore ottenuto, sia false che // true, nella posizione corrente della matrice e // nel Boolean mokaPosizionata credenza[row][coloumn] = posizionareMoka; mokaPosizionata = posizionareMoka; } else { credenza[row][coloumn] = false; } } } // Instanziazione di un oggetto di tipo Scanner che, // preso come parametro il flusso InputStream dalla // console, ci fornisce poi una serie di metodi per // leggere cosa scrive l'utente nella console Scanner console = new Scanner(System.in); System.out.println("La tua compagna ha scelto il posto per la Moka"); System.out.println("Vediamo se ora riesci a trovarla"); // Viene usato il metodo nextInt() dell'oggetto Scanner // che legge il numero intero che l'utente ha inserito // nella console e lo assegniamo alla variabile row 100 System.out.print("Inserisci in quale piano della credenza tu pensi l'abbia messa (0 oppure 1) e premi invio:"); int row = console.nextInt(); // Viene usato il metodo nextInt() dell'oggetto Scanner // che legge il numero intero che l'utente ha inserito // nella console e lo assegniamo alla variabile coloumn System.out.print("Inserisci in quale sportello della credenza tu pensi l'abbia messa (0, 1, 2, 3) e premi invio:"); int coloumn = console.nextInt(); // Chiudiamo il flusso in ingresso dalla console perché // non ci serve più console.close(); System.out.println("Tu pensi che la tua compagna stavolta abbiamo messo la Moka nel piano " + row + " e nello sportello " + coloumn); if (credenza[row][coloumn] == true) { System.out.println("Bravo! Stavolta l'hai trovata!"); } else { System.out.println("Mi dispiace... ma anche stavolta ha vinto la tua compagna!"); } } } 101 102 6. Elementi di sintassi per descrivere il nostro iperuranio 6.1. Le idee nell’iperuranio: sintassi per la descrizione di una classe Chi si trova a leggere questo capitolo ha ormai acquisito tutti gli elementi della grammatica Java per proseguire nello studio della sintassi: possiamo ora affrontare il problema della descrizione delle idee del nostro iperuranio in Java. Innanzitutto, come avrete già notato negli esempi svolti nei capitoli precedenti, alla creazione di una classe, davanti a voi vi si apre un file che inizia con le parole chiave: public class NomeClasse{ // Corpo della classe } Analizziamo la sintassi partendo dall’esempio: public o Modificatore di accesso: descrive la visibilità della classe all’interno del progetto public: la classe sarà visibile, ovvero istanzabile, all’interno di qualsiasi classe appartenente a qualsiasi package del progetto 103 class Parola chiave che dice al compilatore Java che il prossimo blocco di codice deve essere interpretato come una classe NomeClasse o Il nome della classe deve essere composto da una sola parola e può contenere caratteri alfanumerici a meno di qualche precisazione: Non può contenere solo caratteri numerici Non può contenere caratteri speciali Il galateo vorrebbe il nome cominciasse sempre con la lettera maiuscola o Il nome della classe sarà anche il nome che verrà attribuito al file “.java” che contiene il codice o 104 protected: la classe sarà visibile dalle sue sottoclassi e dalle altre classi dello stesso package private: non sarà visibile da nessun altra classe di nessun package del progetto default: se nessuna visibilità è indicata dallo sviluppatore, allora il compilatore Java assegna la visibilità protected Parentesi graffe o Segnano il blocco di codice che sarà interpretato dal compilatore Java come contenuto di una classe 6.2. Sintassi per la definizione di variabili globali di una classe Analizziamo ora la corretta sintassi per dichiarare una variabile globale all’interno di una classe Java: public int numero; protected int base, esponente; private int altezza = 10; int larghezza = 20, profondità = 50; Le regola rispetto alla dichiarazione formale sono le stesse regole utilizzate per la dichiarazione di variabili all’interno del corpo di un metodo; la differenza da sottolineare è la dichiarazione del Modificatore di accesso per ogni variabile: Modificatore di accesso: descrive la visibilità della variabile all’interno del progetto o public: la variabile sarà visibile dai metodi della classe di appartenenza e dai metodi di altre classi che utilizzano un’istanza della classe corrente o protected: la variabile sarà visibile dai metodi della classe di appartenenza e dai 105 o o o metodi di altre classi che estendono la classe di appartenenza private: la variabile sarà visibile solo dai metodi della classe di appartenenza default: se nessuna visibilità è indicata dallo sviluppatore, allora il compilatore Java assegna la visibilità protected static può essere presente o assente: nel caso fosse presente significa che la variabile è strettamente legata alla classe e non all’istanza della classe. La variabile può essere raggiunta, quindi, senza istanziare un oggetto della classe di appartenenza. Un esempio è la classe Math che contiene una serie di variabili statiche che si raggiungono senza la necessità di istanziare un oggetto Math double piGreco = Math.PI; o 106 final può essere presente o assente: nel caso fosse presente significa che la variabile non è modificabile in corso di esecuzione del programma Java 6.3. Sintassi per la definizione dei metodi di una classe Analizziamo la sintassi per dichiarare un metodo all’interno di una classe: public static void nomeMetodo(Object parametro1, String paramentro2){ // Corpo del metodo } Analizziamo la sintassi partendo dall’esempio: public [static] o Modificatore di accesso: descrive la visibilità del metodo all’interno del progetto public: il metodo sarà visibile dai metodi della classe di appartenenza e dai metodi di altre classi che utilizzano un’istanza della classe corrente protected: il metodo sarà visibile dai metodi della classe di appartenenza e dai metodi di altre classi che estendono la classe di appartenenza private: il metodo sarà visibile solo dai metodi della classe di appartenenza default: se nessuna visibilità è indicata dallo sviluppatore, 107 allora il compilatore Java assegna la visibilità protected static può essere presente o assente: nel caso fosse presente significa che il metodo è strettamente legato alla classe e non all’istanza della classe. Il metodo può essere invocato, quindi, senza istanziare un oggetto della classe di appartenenza. Un esempio è la classe Math che contiene una serie di metodi statici che si invocano senza la necessità di istanziare un oggetto Math double casuale = Math.random(); Integer Rappresenta il tipo di ritorno del metodo: in questo caso specifico stiamo dicendo che il risultato dell’invocazione di questo metodo è un oggetto Integer. Se il metodo non ha alcun tipo di oggetto da restituire si deve utilizzare void come tipo di ritorno nomeMetodo o Il nome del metodo deve essere composto da una sola parola e può contenere caratteri alfanumerici a meno di qualche precisazione: o 108 Non può contenere solo caratteri numerici Non può contenere caratteri speciali Il galateo vorrebbe il nome di un metodo cominciasse sempre con la lettera minuscola (String parametro1, String parametro2) o Tra parentesi tonde viene inserita la lista dei parametri necessari all’esecuzione del metodo; ogni parametro viene indicato con Tipo e nome e diviso da una virgola dagli altri parametri Ora vi propongo il codice di una semplice classe di esempio nella quale potrete verificare tutti i modi di invocazione di un metodo: package esperimento.numeronaturale; public class NumeroNaturale { private int numero; public static double media(int a, int b){ return (double) ((a+b)/2); } public double doppio(){ return numero*2; } public void dimezza(){ numero/=2; } 109 /** * @param args */ public static void main(String[] args) { int n1=5,n2=15; double media = NumeroNaturale.media(n1, n2); System.out.println("La media tra "+n1+" e "+n2+" é "+media); NumeroNaturale nn = new NumeroNaturale(); nn.numero = 10; System.out.println("Il NumeroNaturale è "+nn.numero); double nDoppio = nn.doppio(); System.out.println("Il doppio del NumeroNaturale è "+nDoppio); System.out.println("Dimezzo il NumeroNaturale"); nn.dimezza(); System.out.println("Il NumeroNaturale è "+nn.numero); } } 6.4. Metodo costruttore Il metodo costruttore è utilizzato, come suggerisce il suo stesso nome, alla creazione dell’istanza di un oggetto. Questo metodo si presenta con una sintassi simile a quella dei metodi normali; la sua particolarità è legata al fatto che il nome del metodo è identico al nome della classe e non avrà nessun tipo di ritorno indicato. Ecco un esempio di costruttore: public NumeroNaturale(int numeroNaturale){ this.numero = numeroNaturale; } In questo metodo ci occuperemo di tutta la preparazione necessaria alla creazione di un’istanza di un oggetto, 110 partendo da una serie di parametri; è molto importante sapere che ogni classe può avere diversi costruttori che differiscono per i parametri in ingresso. 6.5. Ereditarietà delle classi Abbiamo già parlato del concetto di Polimorfismo e Ereditarietà tra le classi, ora ci occuperemo del come descrivere utilizzando il linguaggio Java, il fatto che una classe estenda un’altra classe. La sintassi è molto semplice; per dire che la classe Giallo estende la classe Libro scriveremo: public class Giallo extends Libro{ // Corpo della classe } All’interno della classe Giallo potremo quindi utilizzare tutti i metodi e le variabili visibili della classe Libro, come se fossero istanziate nella stessa classe. In più potremo sovrascrivere l’implementazione di un metodo, cambiandone il comportamento, qualora fosse necessario, sfruttando una possibilità dell’overriding: basta aggiungere l’annotation “@Override” prima del metodo, per comunicare al compilatore che siamo davanti ad un metodo già presente nella classe madre, ovvero la cosiddetta superclasse. 111 6.6. Esperimento Libreria: specifiche L’obiettivo di questo esperimento riguardo l’ereditarietà sarà quello di creare un oggetto “Libro”, con una serie di variabili ed un metodo specifico, ed un oggetto “Giallo” che lo estende e che fa overriding del metodo implementato nella superclasse. Mettetevi al lavoro con il vostro IDE: Create un nuovo progetto, come visto nei capitoli precedenti, e chiamatelo “EsperimentoLibreria” (figura 39) Create il package “esperimento.libreria” (figura 40) Create la classe “Libro” senza un metodo main (figura 41) A questo punto possiamo descrivere la nostra idea di “Libro” definendo le variabili della classe come di seguito: package esperimento.libreria; public class Libro { public String titolo, autore; public double prezzo; } Dopodiché, per ogni variabile, aggiungeremo una coppia di metodi che hanno il compito di restituire il valore della 112 variabile e di cambiarlo, i cosiddetti metodi “getter and setter”. Per farlo il nostro IDE ci viene in soccorso: Cliccate con il tasto destro su una qualsiasi delle variabili Selezionate la voce “Source → Generate Getters and Setters...” (figura 42) Nella finestra di dialogo che si aprirà potrete scegliere per quali variabili generare i metodi (e noi le sceglieremo tutte) e in più l’Insertion Point, ovvero il punto in cui verrà inserito il codice generato (e noi sceglieremo Last member, ovvero sempre alla fine della classe) Si possono anche scegliere la visibilità (per noi sarà public) Si può chiedere di generare i commenti ai metodi (ed è sempre corretto ed elegante farlo) spuntando la checkbox con l’etichetta “Generate method comments” Cliccate su OK (figura 43) e avrete tutti i metodi automaticamente generati (figura 44) Aggiungiamo ora noi un metodo costruttore ed un metodo che ci restituisce una descrizione del libro: package esperimento.libreria; public class Libro { public String titolo, autore; public double prezzo; /** * Costruttore * @param titolo Titolo del libro 113 * @param autore Autore del libro * @param prezzo Prezzo in Euro del libro */ public Libro(String titolo, String autore, double prezzo){ setTitolo(titolo); setAutore(autore); setPrezzo(prezzo); } /** * Metodo che restituisce una descrizione del libro * @return String contenente la descrizione del libro */ public String descrizione(){ return "Libro: "+titolo+" di "+autore+" - Costo di copertina: €"+prezzo; } /** * @return the titolo */ public String getTitolo() { return titolo; } /** * @param titolo the titolo to set */ public void setTitolo(String titolo) { this.titolo = titolo; } /** * @return the autore */ public String getAutore() { return autore; } /** * @param autore the autore to set */ public void setAutore(String autore) { this.autore = autore; } /** 114 * @return the prezzo */ public double getPrezzo() { return prezzo; } /** * @param prezzo the prezzo to set */ public void setPrezzo(double prezzo) { this.prezzo = prezzo; } } Il nostro oggetto “Libro” è pronto per l’uso. Ora è il momento di creare la sua estensione “Giallo”: Nello stesso package dell’oggetto “Libro”, create una nuova classe Nella finestra di dialogo di creazione della classe, inserite il nome “Giallo” e stavolta sfruttate anche il campo “Superclass”; cliccate sul tasto “Browse”, posto accanto al campo che contiene il nome della superclasse della quale il nostro nuovo oggetto sarà estensione (figura 45) La finestra di dialogo che vi si apre davanti è una finestra nella quale potrete cercare e scegliere la superclasse che volete estendere (figura 46). Nel campo “Choose a type” scrivete il nome della superclasse “Libro” e l’IDE vi mostrerà i risultati che corrispondono al nome che state cercando (figura 47) Selezionate il risultato che vi interessa (Libro esperimento.libreria) e cliccate su OK 115 Spuntate la voce “Constructors from superclass”: l’IDE scriverà per noi il costruttore della classe che eredita, sfruttando il costruttore della superclasse e utilizzando la corretta sintassi Finiti tutti i settaggi (figura 48) cliccate su OK Aggiungiamo ora le variabili specifiche all’oggetto “Giallo” e i metodi “getters and setters”: package esperimento.libreria; public class Giallo extends Libro { public String colpevole; /** * Costruttore senza colpevole * @param titolo Titolo del libro * @param autore Autore del libro * @param prezzo Prezzo in Euro del libro */ public Giallo(String titolo, String autore, double prezzo) { super(titolo, autore, prezzo); } /** * Costruttore con il colpevole * @param titolo Titolo del libro * @param autore Autore del libro * @param prezzo Prezzo in Euro del libro * @param colpevole Nome del personaggio colpevole del delitto nel giallo */ public Giallo(String titolo, String autore, double prezzo, String colpevole) { super(titolo, autore, prezzo); setColpevole(colpevole); } /** * @return the colpevole */ 116 public String getColpevole() { return colpevole; } /** * @param colpevole the colpevole to set */ public void setColpevole(String colpevole) { this.colpevole = colpevole; } } A questo punto possiamo decidere di implementare una versione particolare del metodo che restituisce la descrizione del libro, sfruttando il polimorfismo e l’annotazione “@Override”: package esperimento.libreria; public class Giallo extends Libro { public String colpevole; /** * Metodo che restituisce una descrizione del libro giallo * @return String contenente la descrizione del libro giallo */ @Override public String descrizione(){ return "Libro Giallo: "+titolo+" di "+autore+" - Costo di copertina: €"+prezzo+" - Nome del personaggio colpevole: "+colpevole; } /** * Costruttore senza colpevole * @param titolo Titolo del libro * @param autore Autore del libro * @param prezzo Prezzo in Euro del libro */ public Giallo(String titolo, String autore, double prezzo) { super(titolo, autore, prezzo); } /** 117 * Costruttore con il colpevole * @param titolo Titolo del libro * @param autore Autore del libro * @param prezzo Prezzo in Euro del libro * @param colpevole Nome del personaggio colpevole del delitto nel giallo */ public Giallo(String titolo, String autore, double prezzo, String colpevole) { super(titolo, autore, prezzo); setColpevole(colpevole); } /** * @return the colpevole */ public String getColpevole() { return colpevole; } /** * @param colpevole the colpevole to set */ public void setColpevole(String colpevole) { this.colpevole = colpevole; } } Ed ora possiamo creare una classe “EsperimentoLibreria”, con un metodo main nel quale: Istanziamo un oggetto Libro Istanziamo un oggetto Giallo Stampiamo sulla console le loro due descrizioni 118 Ecco il codice della classe: package esperimento.libreria; public class EsperimentoLibreria { /** * @param args */ public static void main(String[] args) { Libro wilde = new Libro("The Picture of Dorian Gray", "Oscar Wilde", new Double(18.50)); Libro christie = new Giallo("Murder in Mesopotamia", "Agatha Christie", new Double(15.50),"Il professor Leidner"); System.out.println(wilde.descrizione()); System.out.println(christie.descrizione()); } } Eseguite la classe e vedrete nella console il risultato nel quale la descrizione dei due oggetti “Libro” sarà diversa grazie all’override del metodo che abbiamo implementato nella classe “Giallo” (figura 49). 119 Figura 39 120 Figura 40 121 Figura 41 122 Figura 42 123 Figura 43 124 Figura 44 125 Figura 45 126 Figura 46 127 128 Figura 47 Figura 48 129 Figura 49 130 6.7. Classe astratta In Java si ha la possibilità di definire delle classi che, per loro natura, devono necessariamente essere estese. Un tipico esempio che tutti gli informatici hanno visto nella loro vita è quello della Figura Geometrica: in pratica noi possiamo definire l’idea di Figura Geometrica, sapendo che certamente avrà un perimetro ed un area, ma saranno le varie estensioni particolari di Figura Geometrica (Quadrato, Rettangolo, Triangolo, ...) che potranno implementare la logica di calcolo del perimetro e dell’area. Queste classi vengono dette astratte: avranno una lista di metodi, detti a loro volta astratti, che dovranno necessariamente trovare implementazione nelle classi che estenderanno la classe astratta in cui sono definiti. Vi propongo un esempio che chiarisce la sintassi e l’utilizzo della parola chiave abstract: package esperimento.figurageometrica; public abstract class FiguraGeometrica { public abstract double perimetro(); public abstract double area(); public String descrizione(){ return "Questa FiguraGeometrica ha un perimetro di "+perimetro()+" e un'area di "+area(); } } 131 package esperimento.figurageometrica; public class Quadrato extends FiguraGeometrica { private double lato; @Override public double perimetro() { return getLato()*4; } @Override public double area() { return getLato()*getLato(); } /** * @return the lato */ public double getLato() { return lato; } /** * @param lato the lato to set */ public void setLato(double lato) { this.lato = lato; } } 6.8. Interfacce Nel linguaggio Java si definisce interfaccia la definizione di una lista di metodi, senza implementazione, che definisce una sorta di codice di comportamento di un oggetto: se una classe implementa un’interfaccia, allora si impegna a fornire un’implementazione di tutti i metodi contenuti nell’interfaccia. 132 Per creare un’interfaccia basta cliccare il tasto destro su un package e scegliere la voce New → Intrerface (figura 50). Nella finestra di dialogo si può inserire il nome dell’interfaccia e premere su OK. A questo punto avrete davanti a voi un blocco di codice in cui inserire: una lista di metodi, senza specificarne la loro implementazione; costanti (ovvero variabili globali static final) Vi fornisco un esempio che chiarisce la sintassi e l’utilizzo: package esperimento.figurageometrica; public interface Triangolo { static final double coeffSemiPerimetro = 0.5; public double calcolaSemiperimetro(); public double calcolaAreaByErone(); } package esperimento.figurageometrica; public abstract class FiguraGeometrica { /** * Perimetro della Figura Geometrica * @return perimetro della figura */ public abstract double perimetro(); /** * Area della Figura Geometrica * @return area della figura */ public abstract double area(); /** * Descrizione della Figura * @return descrizione dettagliata della figura geometrica 133 */ public String descrizione(){ return "Questa FiguraGeometrica ha un perimetro di "+perimetro()+" e un'area di "+area(); } /** * Metodo toString proprio dell'oggetto {@link FiguraGeometrica} */ @Override public String toString(){ return descrizione(); } } package esperimento.figurageometrica; public class Quadrato extends FiguraGeometrica { /** * Lato del quadrato */ private double lato; /** * Costruttore del quadrato * @param lato LMato del quadrato */ public Quadrato(double lato){ setLato(lato); } @Override public double perimetro() { return getLato()*4; } @Override public double area() { return getLato()*getLato(); } /** * @return the lato */ public double getLato() { return lato; 134 } /** * @param lato the lato to set */ public void setLato(double lato) { this.lato = lato; } } package esperimento.figurageometrica; public class TriangoloScaleno extends FiguraGeometrica implements Triangolo { /** * Lati del triangolo */ private double a,b,c; /** * Costruttore di default */ public TriangoloScaleno() { setA(10); setB(10); setC(10); } /** * Costruttore * @param a lato del triangolo * @param b lato del triangolo * @param c lato del triangolo */ public TriangoloScaleno(double a, double b, double c) { setA(a); setB(b); setC(c); } @Override public double perimetro() { return getA()+getB()+getC(); } 135 @Override public double area() { return calcolaAreaByErone(); } /** * @return the a */ public double getA() { return a; } /** * @param a the a to set */ public void setA(double a) { this.a = a; } /** * @return the b */ public double getB() { return b; } /** * @param b the b to set */ public void setB(double b) { this.b = b; } /** * @return the c */ public double getC() { return c; } /** * @param c the c to set */ public void setC(double c) { this.c = c; } 136 /** * Metodo dell'interfaccia implementata */ @Override public double calcolaSemiperimetro() { return Triangolo.coeffSemiPerimetro*(perimetro()); } /** * Metodo dell'interfaccia implementata */ @Override public double calcolaAreaByErone() { return Math.sqrt(calcolaSemiperimetro()*(calcolaSemiperimetro()getA())*(calcolaSemiperimetro()-getB())*(calcolaSemiperimetro()getC())); } } package esperimento.figurageometrica; public class TriangoloRettangolo extends FiguraGeometrica implements Triangolo { /** * Lati del triangolo rettangolo */ private double cateto1,cateto2,ipotenusa; public TriangoloRettangolo() { setCateto1(3); setCateto2(4); setIpotenusa(Math.sqrt(Math.pow(cateto1,2)+Math.pow(cate to2,2))); } @Override public double perimetro() { return getCateto1()+getCateto2()+getIpotenusa(); } @Override public double area() { return getCateto1()*getCateto2()/2; } 137 /** * @return the cateto1 */ public double getCateto1() { return cateto1; } /** * @param cateto1 the cateto1 to set */ public void setCateto1(double cateto1) { this.cateto1 = cateto1; } /** * @return the cateto2 */ public double getCateto2() { return cateto2; } /** * @param cateto2 the cateto2 to set */ public void setCateto2(double cateto2) { this.cateto2 = cateto2; } /** * @return the ipotenusa */ public double getIpotenusa() { return ipotenusa; } /** * @param ipotenusa the ipotenusa to set */ public void setIpotenusa(double ipotenusa) { this.ipotenusa = ipotenusa; } 138 /** * Metodo dell'interfaccia implementata */ @Override public double calcolaSemiperimetro() { return Triangolo.coeffSemiPerimetro*(perimetro()); } /** * Metodo dell'interfaccia implementata */ @Override public double calcolaAreaByErone() { return Math.sqrt(calcolaSemiperimetro()*(calcolaSemiperimetro()getCateto1())*(calcolaSemiperimetro()getCateto2())*(calcolaSemiperimetro()-getIpotenusa())); } } package esperimento.figurageometrica; public class EsperimentoFiguraGeometrica { public static void main(String[]args) { FiguraGeometrica[] figure = new FiguraGeometrica[3]; figure[0] = new Quadrato(10); figure[1] = new TriangoloScaleno(); figure[2] = new TriangoloRettangolo(); for(int i=0; i<figure.length; i++){ System.out.println(figure[i]); } } } 139 140 Figura 50 6.9. La classe Object Non si può concludere un capitolo legato alle classi e agli oggetti Java senza aver parlato della classe Object. Questa classe, volendo rimanere nell’ambito del parallelismo tra programmazione orientata agli oggetti e Iperuranio Platonico, potremmo dire che rappresenta “l’idea delle idee”: in pratica tutte le idee sono istanze di questa idea. Tutti gli oggetti Java sono implicite estensioni della classe Object; e cosa ereditano da questa classe? Innanzitutto ereditano un codice di comportamento che ogni oggetto Java dovrà seguire, del quale ci tengo subito a elencarne due punti fondamentali che già chi si affaccia alla programmazione Java può comprendere e seguire: Ogni oggetto dovrà essere capace di confrontarsi con un altro oggetto dello stesso tipo e dire se è uguale o meno o A questo scopo eredita il metodo equals, del quale ovviamente potrà fare overriding e implementarne uno specifico Ogni oggetto dovrà essere capace di convertirsi in una stringa, ovvero di fornire una rappresentazione in String dell’oggetto; potrà poi ad esempio essere passato come parametro al metodo System.out.println(obj) o A questo scopo eredita il metodo toString, del quale ovviamente potrà fare overriding e implementarne uno specifico 141 6.10. Come implementare metodo equals correttamente il In questo piccolo volume dedicato al linguaggio Java, mi è capitato di fare riferimento al galateo, perché ci tengo a precisare che ci sono delle buone abitudini che gli sviluppatori dovrebbero sempre tener presenti per scrivere del codice di buon livello: una di queste buone abitudini è strettamente legata all’implementazione del metodo equals. Consiglio a tutti gli sviluppatori che si trovano davanti alla necessità di implementare il metodo equals per una loro classe, di implementare anche il metodo hashcode: è un metodo che restituisce una traduzione in hash (una traduzione che potremmo definire numerica, semplificandone il senso) del proprio oggetto. Ci sono molti costrutti in Java, ad esempio, che, per determinare l’uguaglianza tra oggetti, sfruttano proprio il loro hashcode e non il metodo equals: per evitare che il vostro oggetto possa avere comportamenti non voluti in situazioni inaspettate, pensate alle buone abitudini quando siete in tempo per farlo. Cominciamo con il dire che l’implementazione dei metodi equals e hashcode deve rispettare alcune restrizioni importanti; la relazione deve essere: Simmetrica: se objA.equals(objB) allora b.equals(objA); Riflessiva: per qualsiasi oggetto diverso da null, objA.equals(objA) deve essere verificata; Transitiva : se objA.equals(objB) e objB.equals(objC) allora objA.equals(objC) 142 Inoltre i metodi hashCode() e equals devono essere consistenti, ovvero, due oggetti uguali per il metodo equals devono necessariamente avere lo stesso hashcode, mentre la relazione inversa non è necessaria. Nella classe Object, il metodo equals ha la seguente implementazione: public boolean equals(Object obj) { return (this == obj); } Il metodo verifica, attraverso l’operatore “==” che l’oggetto corrente, ovvero il this, sia uguale all’oggetto passato come parametro: nel caso di tipi primitivi non ci sarebbero problemi, ma nel caso di oggetti complessi in questo caso non verifichiamo il contenuto dell’oggetto, ma la sola referenza in memoria. Praticamente il metodo equals, per default, verifica che l’oggetto passato come parametro sia proprio lo stesso oggetto corrente (this): noi invece dobbiamo ridefinire il metodo equals per riconoscere che due oggetti distinti, due istanze dello stesso oggetto, abbiano caratteristiche tali da poter essere definiti identici. Il metodo hashcode, invece, nell’implementazione di default nella classe Object, restituisce l’indirizzo in memoria convertito in numero intero. 143 Ecco un esempio di implementazione dei metodi equals e hashcode: package esperimento.figurageometrica; public class TriangoloScaleno extends FiguraGeometrica implements Triangolo { /** * Lati del triangolo */ private double a,b,c; /** * Costruttore di default */ public TriangoloScaleno() { setA(10); setB(10); setC(10); } /** * Costruttore * @param a lato del triangolo * @param b lato del triangolo * @param c lato del triangolo */ public TriangoloScaleno(double a, double b, double c) { setA(a); setB(b); setC(c); } @Override public double perimetro() { return getA()+getB()+getC(); } @Override public double area() { return calcolaAreaByErone(); } 144 /** * @return the a */ public double getA() { return a; } /** * @param a the a to set */ public void setA(double a) { this.a = a; } /** * @return the b */ public double getB() { return b; } /** * @param b the b to set */ public void setB(double b) { this.b = b; } /** * @return the c */ public double getC() { return c; } /** * @param c the c to set */ public void setC(double c) { this.c = c; } /** * Metodo dell'interfaccia implementata */ @Override public double calcolaSemiperimetro() { 145 return Triangolo.coeffSemiPerimetro*(perimetro()); } /** * Metodo dell'interfaccia implementata */ @Override public double calcolaAreaByErone() { return Math.sqrt(calcolaSemiperimetro()*(calcolaSemiperimetro()getA())*(calcolaSemiperimetro()-getB())*(calcolaSemiperimetro()getC())); } @Override public boolean equals(Object obj) { if(obj instanceof TriangoloScaleno){ TriangoloScaleno ts = (TriangoloScaleno) obj; return getA()==ts.getA() && getB()==ts.getB() && getC()==ts.getC(); }else{ return false; } } @Override public int hashCode() { int hash = 1; hash = hash * 17 + (int) Math.round(getA()); hash = hash * 31 + (int) Math.round(getB()); hash = hash * 13 + (int) Math.round(getC()); return hash; } } 146 7. Impariamo a gestire i nostri errori 7.1. Exception: l’idea dell’errore in Java Una delle cose più complesse e necessarie nella concezione di un componente software, per quanto semplice possiate immaginarlo, è la gestione degli errori; quando vi trovate davanti ad un problema da affrontare e risolvere attraverso la scrittura di una procedura software di qualsiasi natura, dovrete sempre immaginare e progettare la soluzione al problema, ma specificando e gestendo tutte le possibili anomalie di comportamento della vostra soluzione. Per affrontare al meglio vi consiglio di ragionare come un ingegnere dell’Automazione Industriale, ovvero, immaginando sempre un problema come una serie di variabili di ingresso, una dinamica di funzionamento del processo, e delle variabili in uscita: ognuna di queste componenti ha un dominio di validità, oltre i limiti del quale il processo non può essere lanciato o, qualora lanciato, non può essere correttamente portato a termine. 7.2. Esperimento della divisione fra numeri decimali: specifiche Un esempio semplice, ma efficace da un punto di vista della comprensione del problema, è la realizzazione di un 147 metodo che realizza la divisione tra numeri decimali; verifichiamo le condizioni iniziali: Le variabili in ingresso devono contenere un valore (questo significa che devono essere diverse dal valore null) Il denominatore deve necessariamente essere diverso dal valore zero, per ottenere un risultato diverso dall’infinito Nell’esperimento realizziamo: Un progetto dal nome EsperimentoDivisione Un package esperimento.divisione Una classe EsperimentoDivisione.java o Un metodo pubblico e statico dividi che prende in ingresso due variabili di tipo Double e ne restituisce una terza che è il risultato della divisione tra i due numeri in ingresso o Un metodo main che inizializza due variabili random di tipo Double e stampa sulla console una String del tipo “Il risultato della divisione tra 6 e 3 è 2” 7.3. Esperimento della divisione fra numeri decimali: considerazioni sulle eccezioni Leggendo le specifiche di un progetto, spesso non troverete riferimenti ai possibili casi di errore: è abilità dello sviluppatore e di chi si occupa del testing del 148 software quella di prevedere le possibili anomalie di funzionamento. Cominciamo anche con il precisare che il galateo nel linguaggio Java consiglia di verificare all’inizio di ogni metodo queste condizioni di funzionamento al fine di evitare il lancio di un metodo che genererebbe una Exception durante la propria esecuzione; nel nostro IDE Eclipse è facile verificarne il risultato, scrivendo una classe con un metodo main ed un metodo che realizza la divisione fra numeri decimali, ma senza la giusta gestione dell’errore: package esperimento.divisione; public class EsperimentoDivisione { /** * @param args */ public static void main(String[] args) { eseguiDivisione(); } /** * Metodo eseguiDivisione */ public static void eseguiDivisione() { Double a = Math.random(); Double b = null; Double risultato = dividi(a, b); System.out.println("Il risultato della divisione tra "+a+" e "+b+" è "+risultato); } /** * Metodo che realizza la divisione tra numeri decimali * @param numeratore variabile di tipo Double * @param denominatore variabile di tipo Double * @return risultato della divisione 149 */ public static Double dividi(Double numeratore, Double denominatore){ return numeratore/denominatore; } } Come vedete dal codice il denominatore l’abbiamo inizializzato a null; se eseguite il main vedrete che l’esecuzione viane bruscamente bloccata dal lancio di una Exception: Exception in thread "main" java.lang.NullPointerException at esperimento.divisione.EsperimentoDivisione.dividi (EsperimentoDivisione.java:31) at esperimento.divisione.EsperimentoDivisione.eseguiDivisione (EsperimentoDivisione.java:19) at esperimento.divisione.EsperimentoDivisione.main (EsperimentoDivisione.java:9) Evidentemente in fase di compilazione nessuno ha potuto segnalare un’anomalia, perché da un punto di vista strettamente teorico non c’era niente di sbagliato nel metodo che abbiamo scritto per realizzare la divisione; ma al momento dell’esecuzione, la macchina virtuale si è trovata davanti ad una situazione che non sa gestire, quindi ha lanciato un errore che ha fermato il funzionamento. Uno dei compiti principali di uno sviluppatore è quella di prevedere il più possibile questi errori, gestendoli o semplicemente bloccando l’esecuzione di un metodo spiegandone il motivo. Nel caso specifico possiamo decidere di gestire l’eccezione restituendo a nostra volta un risultato null per la divisione, ma evitando l’arresto brusco dell’esecuzione: 150 /** * Metodo che realizza la divisione tra numeri decimali * @param numeratore variabile di tipo Double * @param denominatore variabile di tipo Double * @return risultato della divisione, null se denominatore e/o numeratore sono null */ public static Double dividi(Double numeratore, Double denominatore){ if(numeratore == null || denominatore == null){ return null; } return numeratore/denominatore; } Stavolta non avremo un arresto inaspettato e il risultato sarà una stringa del tipo “Il risultato della divisione tra 0.3370904229000804 e null è null”. 7.4. La classe Exception Come si è potuto vedere nel risultato dell’esperimento precedente, l’errore è stato segnalato attraverso la dicitura in Console “Exception in thread "main" java.lang.NullPointerException”, con poi le indicazioni del numero di riga nel quale è avvenuta l’anomalia. La classe “java.lang.NullPointerException” è un’estensione della classe Exception; quest’ultima rappresenta l’oggetto interpretato dalla Java Virtual Machine come una generica anomalia di funzionamento: tutti gli errori più specifici, per essere interpretati come eccezioni, dovranno essere oggetti che estendono la classe Exception e inoltre, 151 sempre facendo riferimento al galateo, dovranno avere un nome che finisce sempre con la parola Exception. Nel caso specifico la classe NullPointerException è un’estensione della classe Exception che segnala la presenza inaspettata di una variabile con valore null. 7.5. Elementi di sintassi: le parole chiave “throws” e “throw” Ogni metodo può al suo interno lanciare una nuova Exception attraverso l’uso della parola chiave throw; può altresì specificare tutte le Exceptions che sono potenzialmente lanciate all’interno del suo blocco di codice attraverso l’utilizzo della parola chiave throws; mostriamo un esempio che chiarisce la sintassi: /** * Metodo che realizza la divisione tra numeri decimali * @param numeratore variabile di tipo Double * @param denominatore variabile di tipo Double * @return risultato della divisione */ public static Double dividi(Double numeratore, Double denominatore) throws NullPointerException, IllegalArgumentException{ if(denominatore == 0){ throw new IllegalArgumentException(); } return numeratore/denominatore; } 152 7.6. Elementi di sintassi: blocco try... catch... finally All’interno del blocco di codice di un metodo nel quale utilizziamo un metodo che segnala le sue potenziali eccezioni con un throws, abbiamo due possibilità: Evitare di gestire le eccezioni e rilanciare a nostra volta le Exceptions aggiungendole nell’elenco del throws del nostro metodo Gestire l’eccezione posizionando l’invocazione del metodo che potenzialmente lancia un’eccezione all’interno di un blocco try o Alla fine di un blocco try, dobbiamo inserire una serie di blocchi catch, uno per ogni Exception potenzialmente lanciata, con dentro il codice eseguito nel caso si dovesse verificare l’errore specifico o Dopo i blocchi catch si può inserire un blocco finally nel quale si specifica un blocco di codice di codice eseguito in ogni caso dopo il blocco try, sia quindi nel caso in cui si siano verificate eccezioni, sia nel caso contrario. 153 Vediamo una classe di esempio che ne mostra la sintassi e l’utilizzo: /** * Metodo eseguiDivisione con gestione delle eccezioni */ public static void eseguiDivisione() { Double a = Math.random(); Double b = null; Double risultato = null; try{ risultato = dividi(a, b); }catch(IllegalArgumentException e){ System.out.println(e); }finally{ System.out.println("Il risultato della divisione tra "+a+" e "+b+" è "+risultato); } } /** * Metodo che realizza la divisione tra numeri decimali * @param numeratore variabile di tipo Double * @param denominatore variabile di tipo Double * @return risultato della divisione * @throws IllegalArgumentException */ public static Double dividi(Double numeratore, Double denominatore) throws IllegalArgumentException{ if(numeratore == null || denominatore == null){ throw new IllegalArgumentException("Numeratore e/o denominatore nullo"); }else if(denominatore == 0){ throw new IllegalArgumentException("Denominatore uguale a zero"); } return numeratore/denominatore; } 154 7.7. Creazione di errore specifici Uno sviluppatore puo creare dfelle proprie specifriche Excpetion soltanto creando delle normali classi Java che estendono la classe Exception e che implementano tutti i costruttori ereditati dalla classe Exception. Ecco un esempio di Exception personalizzata: package esperimento.divisione; public class DivisioneException extends Exception { /** * Costruttore della classe {@link DivisioneException} */ public DivisioneException(Double numeratore, Double denominatore) { super("numeratore: "+numeratore+" denominatore: "+denominatore); } /** * Costruttore della classe {@link DivisioneException} * @param arg0 */ public DivisioneException(Double numeratore, Double denominatore, String arg0) { super("numeratore: "+numeratore+" denominatore: "+denominatore+" -> "+arg0); } /** * Costruttore della classe {@link DivisioneException} * @param arg0 */ public DivisioneException(Double numeratore, Double denominatore, Throwable arg0) { super("numeratore: "+numeratore+" denominatore: "+denominatore+" -> "+arg0); } /** * Costruttore della classe {@link DivisioneException} 155 * @param arg0 * @param arg1 */ public DivisioneException(Double numeratore, Double denominatore, String arg0, Throwable arg1) { super("numeratore: "+numeratore+" denominatore: "+denominatore+" -> "+arg0, arg1); } /** * Costruttore della classe {@link DivisioneException} * @param arg0 * @param arg1 * @param arg2 * @param arg3 */ public DivisioneException(Double numeratore, Double denominatore, String arg0, Throwable arg1, boolean arg2, boolean arg3) { super("numeratore: "+numeratore+" denominatore: "+denominatore+" -> "+arg0, arg1, arg2, arg3); } } package esperimento.divisione; public class EsperimentoDivisione { /** * @param args */ public static void main(String[] args) { eseguiDivisione(); } /** * Metodo eseguiDivisione */ public static void eseguiDivisione() { Double a = Math.random(); Double b = null; Double risultato = null; try{ risultato = dividi(a, b); }catch(DivisioneException e){ System.out.println(e); 156 }finally{ System.out.println("Il risultato della divisione tra "+a+" e "+b+" è "+risultato); } } /** * Metodo che realizza la divisione tra numeri decimali * @param numeratore variabile di tipo Double * @param denominatore variabile di tipo Double * @return risultato della divisione * @throws DivisioneException */ public static Double dividi(Double numeratore, Double denominatore) throws DivisioneException{ if(numeratore == null || denominatore == null){ throw new DivisioneException(numeratore, denominatore, "Numeratore e/o denominatore nullo"); }else if(denominatore == 0){ throw new DivisioneException(numeratore, denominatore, "Denominatore uguale a zero"); } return numeratore/denominatore; } } 157 158 8. Conclusioni 8.1. Ora è il momento di approfondire Ho scritto questo libro pensando di realizzare non un manuale, ma una sorta di “racconto tecnico”: ho immaginato di poter raccontare un linguaggio di programmazione attraverso parole diverse, rispetto a quello che io e i miei colleghi utilizziamo quotidianamente. Lo scopo di questo volume è quello di farvi incuriosire nella programmazione: ho cercato di fornirvi tutti gli strumenti per cominciare a giocare con Java, attraverso l’uso dell’IDE e soprattutto attraverso la vostra fantasia e voglia di mettervi alla prova. In effetti avrei potuto riempire il libro di esempi di ogni tipo, ma la rete è colma di esercitazioni, piccoli manuali, tutorial: quello che invece vi chiedo ora è di studiare bene quello che ci siamo detti in questo libro, per poi tuffarvi in Internet e trovare problemi piccoli e grandi da affrontare e risolvere. Abbiate curiosità e voglia di studiare: le possibilità di un linguaggio di programmazione ad oggetti sono pressoché infinite, e tra l’altro Java è un mondo open source nel quale troverete una quantità notevole di casi studio e librerie jar da poter importare nei vostri progetti e usare nei vostri esempi. 159 Io, come autore del libro, resto a vostra disposizione; e se davvero vi state appassionando, vi consiglio di approfondire il linguaggio, seguendo corsi o leggendo manuali specifici sul linguaggio e sui suoi costrutti: insomma non fermatevi a questo libro perché è solo un antipasto per aprirvi l’appetito. E fra un po’ di tempo, magari, ci rivedremo come colleghi: sarò felicissimo di aver convinto qualcuno a scegliere il mio stesso mestiere, di averlo convinto a diventare un Analista Programmatore. Un lavoro può essere bello o brutto, e dipende dalla propria soggettività e dal contesto, ma c’è una caratteristica dal lavoro dell’Analista Programmatore che non deve sfuggire: nonostante sembri un mestiere freddo, perché legato alle macchine, ai bit, ai numeri, in realtà è un lavoro molto vicino alla filosofia e talvolta poetico. Lo so che leggere l’aggettivo “poetico” vi avrà fatto sorridere, immaginandolo un po’ eccessivo rispetto allo sviluppo software; ma, per me, leggere le specifiche di un cliente e svilupparle, realizzando un software proprio come lui l’aveva immaginato, è un po’ come sentirmi capace di poter realizzare i sogni di qualcuno. E questo non vi sembra abbastanza poetico? 160