Sei sulla pagina 1di 562

Versione 0.1.

8 Rilascio del 02/05/2001

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 2

Titolo Originale Java Enterprise Computing Copyrigth per la prima edizione Massimiliano Tarquini Copyrigth per ledizione corrente Massimiliano Tarquini Coordinamento Editoriale Massimiliano Tarquini Progetto Editoriale Massimiliano Tarquini Progetto Grafico Riccardo Agea

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 3

Se in un primo momento speranza che si realizzi.

l'idea non assurda, allora non c' nessuna Albert Einstein

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 4

INDICE ANALITICO
1 Introduzione alla programmazione Object Oriented ................................ 6 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.10 1.11 1.12 1.13 1.14 INTRODUZIONE .............................................................................6 UNA EVOLUZIONE NECESSARIA ..........................................................7 IL PARADIGMA PROCEDURALE ...........................................................7 CORREGGERE GLI ERRORI PROCEDURALI .............................................9 IL PARADIGMA OBJECT ORIENTED ...................................................10 CLASSI DI OGGETTI ......................................................................11 EREDITARIET ............................................................................11 IL CONCETTO DI EREDITARIET NELLA PROGRAMMAZIONE .....................12 VANTAGGI NELLUSO DELLEREDITARIET ..........................................13 PROGRAMMAZIONE OBJECT ORIENTED ED INCAPSULAMENTO ...............13 I VANTAGGI DELLINCAPSULAMENTO ...............................................14 ALCUNE BUONE REGOLE PER CREARE OGGETTI ..................................15 STORIA DEL PARADIGMA OBJECT ORIENTED ....................................16 CONCLUSIONI ...........................................................................17

2 Unified Modeling Language ..................................................................18 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.10 2.11 2.12 INTRODUZIONE ...........................................................................18 DAL MODELLO FUNZIONALE ALLE TECNICHE DI BOOCH E OMT ...............18 UNIFIED MODELLING LANGUAGE .....................................................22 LE CARATTERISTICHE DI UML ........................................................23 GENEALOGIA DEL LINGUAGGIO UML ................................................24 USE CASE DIAGRAMS....................................................................24 CLASS DIAGRAMS ........................................................................27 SEQUENCE DIAGRAMS ...................................................................29 COLLABORATION DIAGRAM ............................................................31 STATECHART DIAGRAM ...............................................................32 ACTIVITY DIAGRAMS ..................................................................34 COMPONENT DIAGRAM E DEPLOYMENT DIAGRAM .............................36

3 IL linguaggio Java ...............................................................................38 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 INTRODUZIONE ...........................................................................38 LA STORIA DI JAVA ......................................................................38 INDIPENDENZA DALLA PIATTAFORMA ................................................39 USO DELLA MEMORIA ....................................................................41 MULTI-THREADING ......................................................................42 DYNAMIC LOADING AND LINKING ...................................................42 MECCANISMO DI CARICAMENTO DELLE CLASSI DA PARTE DELLA JVM.......43 LA VARIABILE DAMBIENTE CLASSPATH .........................................43

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 5

3.9 3.10 3.11 3.12 3.13 3.14 3.15 3.16 3.17 3.18 3.19 3.20 3.21 3.22 3.23

IL JAVA SOFTWARE DEVELOPMENT KIT (SDK) ..................................43 SCARICARE ED INSTALLARE IL JDK................................................44 IL COMPILATORE JAVA (JAVAC).....................................................45 OPZIONI STANDARD DEL COMPILATORE...........................................46 COMPILARE ELENCHI DI CLASSI .....................................................47 COMPILARE UNA CLASSE JAVA .......................................................47 LINTERPRETE JAVA....................................................................49 SINTASSI DELLINTERPRETE JAVA ..................................................50 INSERIRE COMMENTI ..................................................................51 JAVADOC .................................................................................51 SINTASSI DEL COMANDO JAVADOC .................................................54 JAVA HOTSPOT ..........................................................................55 IL NUOVO MODELLO DI MEMORIA ...................................................56 SUPPORTO NATIVO PER I THREAD ..................................................56 IL GARBAGE COLLECTOR DI HOTSPOT .............................................58

4 La sintassi di Java................................................................................62 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 4.10 4.11 4.12 4.13 4.14 4.15 4.16 4.17 4.18 INTRODUZIONE ...........................................................................62 VARIABILI..................................................................................62 INIZIALIZZAZIONE DI UNA VARIABILE...............................................63 VARIABILI CHAR E CODIFICA DEL TESTO ............................................64 VARIABILI FINAL .........................................................................64 OPERATORI ................................................................................65 OPERATORI DI ASSEGNAMENTO .......................................................67 OPERATORE DI CAST .....................................................................68 OPERATORI ARITMETICI ................................................................69 OPERATORI RELAZIONALI ............................................................71 OPERATORI LOGICI ....................................................................71 OPERATORI LOGICI E DI SHIFT BIT A BIT .........................................73 ARRAY ....................................................................................77 LAVORARE CON GLI ARRAY ...........................................................78 ARRAY MULTIDIMENSIONALI ........................................................80 ESPRESSIONI ED ISTRUZIONI .......................................................82 REGOLE SINTATTICHE DI JAVA .....................................................82 BLOCCHI DI ISTRUZIONI ..............................................................83

5 Definizione di oggetti ...........................................................................85 5.1 5.2 5.3 5.4 5.5 5.6 INTRODUZIONE ...........................................................................85 METODI .....................................................................................85 DEFINIRE UNA CLASSE ..................................................................86 VARIABILI REFERENCE...............................................................87 SCOPE DI UNA VARIABILE JAVA .......................................................90 L OGGETTO NULL .........................................................................93

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 6

5.7 5.8 5.9 5.10 5.11 5.12 5.13 5.14 5.15 5.16 5.17 5.18 5.19 5.20

CREARE ISTANZE..........................................................................96 OGGETTI ED ARRAY ANONIMI.........................................................98 LOPERATORE PUNTO . ...............................................................98 AUTO REFERENZA ESPLICITA .......................................................101 AUTO REFERENZA IMPLICITA ......................................................102 STRINGHE ..............................................................................104 STATO DI UN OGGETTO JAVA.......................................................105 COMPARAZIONE DI OGGETTI.......................................................105 METODI STATICI ......................................................................106 IL METODO MAIN .....................................................................107 CLASSI INTERNE ......................................................................108 CLASSI LOCALI ........................................................................110 CLASSI INTERNE ED AUTOREFERENZA ...........................................111 LOGGETTO SYSTEM ..................................................................112

6 Controllo di flusso e distribuzione di oggetti ........................................115 6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9 6.10 6.11 6.12 6.13 6.14 6.15 6.16 6.17 6.18 6.19 6.20 6.21 INTRODUZIONE .........................................................................115 ISTRUZIONI PER IL CONTROLLO DI FLUSSO ......................................115 LISTRUZIONE IF .......................................................................116 LISTRUZIONE IF-ELSE ................................................................118 ISTRUZIONI IF, IF-ELSE ANNIDATE ................................................119 CATENE IF-ELSE-IF ....................................................................120 LISTRUZIONE SWITCH ................................................................123 LISTRUZIONE WHILE ..................................................................127 LISTRUZIONE DO-WHILE.............................................................128 LISTRUZIONE FOR ...................................................................129 ISTRUZIONE FOR NEI DETTAGLI ...................................................131 ISTRUZIONI DI RAMIFICAZIONE ..................................................131 LISTRUZIONE BREAK ................................................................132 LISTRUZIONE CONTINUE ...........................................................133 LISTRUZIONE RETURN ..............................................................134 PACKAGE JAVA ........................................................................134 ASSEGNAMENTO DI NOMI A PACKAGE ............................................135 CREAZIONE DEI PACKAGE ...........................................................136 DISTRIBUZIONE DI CLASSI .........................................................137 IL MODIFICATORE PUBLIC ..........................................................138 LISTRUZIONE IMPORT ..............................................................141

7 Incapsulamento.................................................................................143 7.1 7.2 7.3 7.4 INTRODUZIONE .........................................................................143 MODIFICATORI PUBLIC E PRIVATE .................................................144 IL MODIFICATORE PRIVATE ..........................................................144 IL MODIFICATORE PUBLIC ............................................................145

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 7

7.5 7.6 7.7 7.8 7.9 7.10 7.11 7.12 7.13

IL MODIFICATORE PROTECTED.......................................................146 UN ESEMPIO DI INCAPSULAMENTO .................................................147 LOPERATORE NEW .....................................................................149 METODI COSTRUTTORI ................................................................150 UN ESEMPIO DI COSTRUTTORI ......................................................151 OVERLOADING DEI COSTRUTTORI ................................................153 RESTRIZIONE SULLA CHIAMATA AI COSTRUTTORI ............................155 CHIAMATE INCROCIATE TRA COSTRUTTORI ....................................155 UN NUOVO ESEMPIO .................................................................157

8 Ereditariet .......................................................................................162 8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8 8.9 8.10 8.11 8.12 8.13 8.14 8.15 8.16 8.17 8.18 8.19 8.20 INTRODUZIONE .........................................................................162 DISEGNARE UNA CLASSE BASE.......................................................162 OVERLOAD DI METODI.................................................................167 ESTENDERE UNA CLASSE BASE .......................................................169 EREDITARIET ED INCAPSULAMENTO ..............................................170 CONSEGUENZE DELLINCAPSULAMENTO NELLA EREDITARIET ...............174 EREDITARIET E COSTRUTTORI .....................................................176 AGGIUNGERE NUOVI METODI ........................................................179 OVERRIDING DI METODI..............................................................180 CHIAMARE METODI DELLA CLASSE BASE ........................................181 COMPATIBILIT TRA VARIABILI REFERENCE ...................................183 RUN-TIME E COMPILE-TIME........................................................184 ACCESSO A METODI ATTRAVERSO VARIABILI REFERENCE ...................185 CAST DEI TIPI .........................................................................186 LOPERATORE INSTANCEOF .........................................................187 LOGGETTO OBJECT ..................................................................188 IL METODO EQUALS() ...............................................................189 RILASCIARE RISORSE ESTERNE ...................................................192 OGGETTI IN FORMA DI STRINGA ..................................................192 GIOCHI DI SIMULAZIONE ...........................................................194

9 Eccezioni...........................................................................................204 9.1 9.2 9.3 9.4 9.5 9.6 9.7 9.8 9.9 9.10 INTRODUZIONE .........................................................................204 PROPAGAZIONE DI OGGETTI .........................................................206 OGGETTI THROWABLE .................................................................208 ECCEZIONI CONTROLLATE ED ECCEZIONI INCONTROLLATE ...................211 ERRORI JAVA ............................................................................214 DEFINIRE ECCEZIONI PERSONALIZZATE ...........................................215 LISTRUZIONE THROW.................................................................217 LA CLAUSOLA THROWS ................................................................218 ISTRUZIONI TRY / CATCH ............................................................219 SINGOLI CATCH PER ECCEZIONI MULTIPLE .....................................222

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 8

9.11 LE ALTRE ISTRUZIONI GUARDIANE. FINALLY ..................................223 10 Polimorfismo ed ereditariet avanzata...............................................225 INTRODUZIONE .......................................................................225 POLIMORFISMO : UNINTERFACCIA, MOLTI METODI ......................226 INTERFACCE ...........................................................................226 DEFINIZIONE DI UNINTERFACCIA ................................................227 IMPLEMENTARE UNA INTERFACCIA ...............................................229 EREDITARIET MULTIPLA IN JAVA ...............................................232 CLASSI ASTRATTE.....................................................................232

10.1 10.2 10.3 10.4 10.5 10.6 10.7 11

Java Threads...................................................................................235 INTRODUZIONE .......................................................................235 THREAD E PROCESSI .................................................................236 VANTAGGI E SVANTAGGI NELLUSO DI THREAD ................................238 LA CLASSE JAVA.LANG.THREAD....................................................239 LINTERFACCIA RUNNABLE......................................................241 CICLO DI VITA DI UN THREAD .....................................................244 LAPPLICAZIONE OROLOGIO .......................................................246 PRIORIT DI UN THREAD ...........................................................252 SINCRONIZZARE THREAD ..........................................................254 LOCK SU OGGETTO..................................................................256 LOCK DI CLASSE.....................................................................257 PRODUTTORE E CONSUMATORE .................................................257 UTILIZZARE I METODI WAIT E NOTIFY .........................................261 BLOCCHI SINCRONIZZATI.........................................................263

11.1 11.2 11.3 11.4 11.5 11.6 11.7 11.8 11.9 11.10 11.11 11.12 11.13 11.14 12

Java Networking..............................................................................267

12.1 INTRODUZIONE .......................................................................267 12.2 I PROTOCOLLI DI RETE (INTERNET) .............................................267 12.3 INDIRIZZI IP .........................................................................269 12.4 COMUNICAZIONE CONNECTION ORIENTED O CONNECTIONLESS ...272 12.5 DOMAIN NAME SYSTEM : RISOLUZIONE DEI NOMI DI UN HOST ...........272 12.6 URL .....................................................................................274 12.7 TRASMISSION CONTROL PROTOCOL : TRASMISSIONE CONNECTION ORIENTED ........................................................................................275 12.8 USER DATAGRAM PROTOCOL : TRASMISSIONE CONNECTIONLESS .......277 12.9 IDENTIFICAZIONE DI UN PROCESSO : PORTE E SOCKET.....................278 12.10 IL PACKAGE JAVA.NET .............................................................279 12.11 UN ESEMPIO COMPLETO DI APPLICAZIONE CLIENT/SERVER ..............281 12.12 LA CLASSE SERVERSOCKET .......................................................283

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 9

12.13 12.14 12.15 12.16 13

LA CLASSE SOCKET .................................................................284 UN SEMPLICE THREAD DI SERVIZIO ............................................285 TCP SERVER ........................................................................286 IL CLIENT .............................................................................287

Java Enterprise Computing...............................................................290 INTRODUZIONE .......................................................................290 ARCHITETTURA DI J2EE ...........................................................292 J2EE APPLICATION MODEL .......................................................294 CLIENT TIER ...........................................................................295 WEB TIER ..............................................................................296 BUSINESS TIER .......................................................................298 EIS-TIER ..............................................................................300 LE API DI J2EE......................................................................301 JDBC : JAVA DATABASE CONNECTIVITY.......................................301 RMI : REMOTE METHOD INVOCATION .......................................303 JAVA IDL ............................................................................304 JNDI .................................................................................304 JMS ...................................................................................305

13.1 13.2 13.3 13.4 13.5 13.6 13.7 13.8 13.9 13.10 13.11 13.12 13.13 14

Architettura del Web Tier .................................................................307 INTRODUZIONE .......................................................................307 LARCHITETTURA DEL WEB TIER ..............................................307 INVIARE DATI .........................................................................308 SVILUPPARE APPLICAZIONI WEB .................................................309 COMMON GATEWAY INTERFACE ..................................................309 ISAPI ED NSAPI ...................................................................311 ASP ACTIVE SERVER PAGES ...................................................311 JAVA SERVLET E JAVASERVER PAGES ...........................................311

14.1 14.2 14.3 14.4 14.5 14.6 14.7 14.8 15

Java Servlet API ..............................................................................313 INTRODUZIONE .......................................................................313 IL PACKAGE JAVAX.SERVLET........................................................313 IL PACKAGE JAVAX.SERVLET.HTTP ................................................314 CICLO DI VITA DI UNA SERVLET ...................................................315 SERVLET E MULTITHREADING ......................................................317 LINTERFACCIA SINGLETHREADMODEL .........................................318 UN PRIMO ESEMPIO DI CLASSE SERVLET .......................................318 IL METODO SERVICE()...............................................................319

15.1 15.2 15.3 15.4 15.5 15.6 15.7 15.8 16

Servlet http .....................................................................................321

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 10

16.1 16.2 16.3 16.4 16.5 16.6 16.7 16.8 16.9 16.10 16.11 16.12 16.13 16.14 16.15 16.16 16.17 16.18 16.19 16.20 16.21 16.22 17

INTRODUZIONE .......................................................................321 IL PROTOCOLLO HTTP 1.1 ........................................................321 RICHIESTA HTTP ....................................................................323 RISPOSTA HTTP .....................................................................324 ENTIT ..................................................................................327 I METODI DI REQUEST ...............................................................327 INIZIALIZZAZIONE DI UNA SERVLET .............................................328 LOGGETTO HTTPSERVLETRESPONSE ............................................329 I METODI SPECIALIZZATI DI HTTPSERVLETRESPONSE ......................332 NOTIFICARE ERRORI UTILIZZANDO JAVA SERVLET .........................332 LOGGETTO HTTPSERVLETREQUEST ............................................333 INVIARE DATI MEDIANTE LA QUERY STRING .................................335 QUERY STRING E FORM HTML ..................................................336 I LIMITI DEL PROTOCOLLO HTTP: COOKIES .................................338 MANIPOLARE COOKIES CON LE SERVLET ......................................339 UN ESEMPIO COMPLETO ...........................................................339 SESSIONI UTENTE ..................................................................340 SESSIONI DAL PUNTO DI VISTA DI UNA SERVLET ............................341 LA CLASSE HTTPSESSION.........................................................342 UN ESEMPIO DI GESTIONE DI UNA SESSIONE UTENTE ......................343 DURATA DI UNA SESSIONE UTENTE .............................................344 URL REWRITING ...................................................................344

JavaServer Pages ............................................................................346 INTRODUZIONE .......................................................................346 JAVASERVER PAGES .................................................................346 COMPILAZIONE DI UNA PAGINA JSP ............................................347 SCRIVERE PAGINE JSP .............................................................348 INVOCARE UNA PAGINA JSP DA UNA SERVLET ................................350

17.1 17.2 17.3 17.4 17.5 18

JavaServer Pages: Nozioni Avanzate ................................................352 INTRODUZIONE .......................................................................352 DIRETTIVE .............................................................................352 DICHIARAZIONI ......................................................................354 SCRIPTLETS ............................................................................354 OGGETTI IMPLICITI: REQUEST ....................................................354 OGGETTI IMPLICITI: RESPONSE...................................................355 OGGETTI IMPLICITI : SESSION ....................................................355

18.1 18.2 18.3 18.4 18.5 18.6 18.7 19

JDBC ..............................................................................................356

19.1 INTRODUZIONE .......................................................................356

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 11

19.2 19.3 19.4 19.5 19.6 19.7 19.8 19.9 19.10 20

ARCHITETTURA DI JDBC...........................................................356 DRIVER DI TIPO 1....................................................................357 DRIVER DI TIPO 2....................................................................357 DRIVER DI TIPO 3....................................................................359 DRIVER DI TIPO 4....................................................................360 UNA PRIMA APPLICAZIONE DI ESEMPIO .........................................361 RICHIEDERE UNA CONNESSIONE AD UN DATABASE ...........................363 ESEGUIRE QUERY SUL DATABASE .................................................364 LOGGETTO RESULTSET ...........................................................364

Remote Method Invocation ..............................................................368 INTRODUZIONE .......................................................................368 ARCHITETTURE DISTRIBUITE ......................................................368 RMI IN DETTAGLIO ..................................................................370 CAPIRE ED USARE RMI .............................................................372 LEVOLUZIONE IN RMI-IIOP ....................................................376 SERIALIZZAZIONE ....................................................................380 MARSHALLING DI OGGETTI ........................................................381 ATTIVAZIONE DINAMICA DI OGGETTI ............................................382

20.1 20.2 20.3 20.4 20.5 20.6 20.7 20.8 21

Enterprise Java Beans......................................................................383 INTRODUZIONE .......................................................................383 GLI EJB: CONCETTI BASE ..........................................................383 RAGIONARE PER COMPONENTI ....................................................385 LARCHITETTURA A COMPONENTI .................................................387 IL RUOLO DELLAPPLICATION SERVER E STRUTTURA DEGLI EJB ..........388 I TRE TIPI DI EJB....................................................................393 SESSION BEANS ......................................................................393 TIPI DI SESSION BEANS ............................................................394 FILE SAMPLESESSIONHOME.JAVA ................................................395 FILE SAMPLESESSIONBEAN.JAVA...............................................395 CICLO DI VITA DI UN SESSIONBEAN ...........................................397 ENTITY BEAN: DEFINIZIONI E TIPI ............................................399 STRUTTURA DI UN ENTITYBEAN ................................................401 UN ESEMPIO DI ENTITYBEAN CMP............................................404 UN ESEMPIO DI ENTITYBEAN BMP............................................407 MESSAGEDRIVEN BEANS .........................................................413 CENNI SUL DEPLOY .................................................................416 IL LINGUAGGIO EJB-QL .........................................................418 LIMPORTANZA DEGLI STRUMENTI DI SVILUPPO ............................419

21.1 21.2 21.3 21.4 21.5 21.6 21.7 21.8 21.9 21.10 21.11 21.12 21.13 21.14 21.15 21.16 21.17 21.18 21.19 22

Introduzione ai WebServices ............................................................420

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 12

22.1 22.2 22.3 22.4 22.5 22.6 22.7 22.8 23

INTRODUZIONE .......................................................................420 DEFINIZIONE DI WEB SERVICE ...................................................421 IL PROTOCOLLO SOAP .............................................................423 GLI STRUMENTI JAVA ...............................................................425 JAXP ...................................................................................425 JAXR ...................................................................................426 JAXM...................................................................................427 JAX-RPC..............................................................................427

Design Pattern ................................................................................429

23.1 INTRODUZIONE .......................................................................429 23.2 COSA UN DESIGN PATTERN .......................................................430 23.3 LA BANDA DEI QUATTRO ............................................................431 24 Creational Pattern............................................................................433 INTRODUZIONE .......................................................................433 FACTORY METHOD ...................................................................433 ABSTRACT FACTORY .................................................................438 SINGLETON ............................................................................442 BUILDER ................................................................................445 PROTOTYPE ............................................................................449

24.1 24.2 24.3 24.4 24.5 24.6 25

Structural Patterns...........................................................................454 INTRODUZIONE .......................................................................454 ADAPTER ...............................................................................454 BRIDGE .................................................................................461 DECORATOR ...........................................................................466 COMPOSITE ............................................................................471 FAADE .................................................................................477 FLYWEIGHT ............................................................................482 PROXY ..................................................................................486

25.1 25.2 25.3 25.4 25.5 25.6 25.7 25.8 26

Behavioral Pattern ...........................................................................491 INTRODUZIONE .......................................................................491 CHAIN OF RESPONSIBILITY ........................................................491 COMMAND ..............................................................................495 INTERPRETER..........................................................................499 ITERATOR ..............................................................................503 MEDIATOR .............................................................................507 STRATEGY ..............................................................................510

26.1 26.2 26.3 26.4 26.5 26.6 26.7

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 13

26.8 26.9 26.10 26.11 26.12 27 28

STATE ...................................................................................515 TEMPLATE ..............................................................................517 VISITOR ..............................................................................524 OBSERVER............................................................................528 MEMENTO ............................................................................532

Glossario dei termini ........................................................................536 Bibliografia......................................................................................546

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 14

INTRODUZIONE
Premesse
La guerra dei desktop ormai persa, ma con Java 2 Enterprise Edition la Sun Microsystem ha trasformato un linguaggio in una piattaforma di sviluppo integrata diventata ormai standard nel mondo del Server Side Computing. Per anni, il mondo della IT ha continuato a spendere soldi ed energie in soluzioni proprietarie tra loro disomogenee, dovendo spesso reinvestire in infrastrutture tecnologiche per adattarsi alle necessit emergenti di mercato. Nella ultima decade dello scorso secolo con la introduzione di tecnologie legate ad Internet e pi in generale alle reti, lindustria del software ha immesso sul mercato circa trenta application server, ognuno con un modello di programmazione specifico. Con la nascita di nuovi modelli di business legati al fenomeno della neweconomy, la divergenza di tali tecnologie diventata in breve tempo un fattore destabilizzante ed il maggior ostacolo alla innovazione tecnologica. Capacit di risposta in tempi brevi e produttivit con conseguente aumento della vita del software, sono diventate oggi le chiavi del successo di applicazioni enterprise. Con la introduzione della piattaforma J2EE, la Sun ha proposto non pi una soluzione proprietaria, ma una architettura basata su tecnologie aperte e portabili proponendo un modello in grado di accelerare il processo di implementazione di soluzioni server-side attraverso lo sviluppo di funzionalit nella forma di componenti in grado di girare su qualsiasi application server compatibile con lo standard. Oltre a garantire tutte le caratteristiche di portabilit (Write Once Run Everywhere) del linguaggio Java, J2EE fornisce: Un modello di sviluppo semplificato per l enterprise computing : La piattaforma offre ai vendors di sistemi la capacit di fornire una soluzione che lega insieme molti tipi di middleware in un unico ambiente, riducendo tempi di sviluppo e costi necessari alla integrazioni di componenti software di varia natura. J2EE permette di creare ambienti server side contenenti tutti i middletiers di tipo server come connettivit verso database, ambienti transazionali, servizi di naming ecc.; Una architettura altamente scalabile: La piattaforma fornisce la scalabilit necessaria allo sviluppo di soluzione in ambienti dove le applicazioni scalano da prototipo di lavoro ad architetture 24x7 enterprise wide; Legacy Connectivity: La piattaforma consente lintegrabilit di soluzioni pre esistenti in ambienti legacy consentendo di non reinvestire in nuove soluzioni;

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 2

Piattaforme Aperte: J2EE uno standard aperto. La Sun in collaborazione con partner tecnologici garantisce ambienti aperti per specifiche e portabilit; Sicurezza La piattaforma fornisce un modello di sicurezza in grado di proteggere dati in applicazioni Internet. Alla luce di queste considerazioni si pu affermare che la soluzione offerta da Sun abbia traghettato l enterprise computing in una nuova era in cui le applicazioni usufruiranno sempre pi di tutti i vantaggi offerti da uno standard aperto.

Obiettivi del libro


Questo libro, ricco di schemi e di esempi che facilitano la lettura e la comprensione, offre una panoramica delle metodologie Object Oriented e a partire dal linguaggio Java, mostra al lettore le chiavi per aiutarlo a comprendere i vantaggi e gli inconvenienti delle diverse soluzioni offerte dalla piattaforma J2EE. Nella prima parte, il libro introduce al linguaggio Java come strumento per programmare ad oggetti partendo dal paradigma Object Oriented e identificando di volta in volta le caratteristiche del linguaggio che meglio si adattano nellapplicazione dei principi di base della programmazione Object Oriented. La seconda parte del libro, offre una panoramica delle tecnologie legate alla piattaforma J2EE, cercando di fornire al programmatore gli elementi per identificare le soluzioni pi idonee alle proprie esigenze. Infine, la trza parte del libro dedicata alle tecniche di programmazione mediate luso dei design pattern, introducendo i ventitr pattern noti come GoF (Gang of Four). Un cdrom ricco di documentazione, strumenti per il disegno e lo sviluppo di applicazioni Java e contenente tutti gli esempi riportatie nel libro, completa il panorama didattico offerto da Java Mattone dopo Mattone. Lasciamo quindi a voi lettori il giudizio finale e augurandovi buona lettura, ci auguriamo che il nostro lavoro vi risulti utile.

Massimiliano Tarquini Alessandro Ligi

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 3

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 4

Parte Prima Programmazione Object Oriented

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 5

1 INTRODUZIONE ALLA PROGRAMMAZIONE OBJECT ORIENTED


1.1 Introduzione

Questo capitolo dedicato al paradigma Object Oriented ed ha lo scopo di fornire ai neofiti della programmazione in Java i concetti di base necessari allo sviluppo di applicazioni orientate ad oggetti. In realt, le problematiche che si andranno ad affrontare nei prossimi paragrafi sono molto complesse e trattate un gran numero di testi che non fanno alcuna menzione a linguaggi di programmazione, quindi limiter la discussione soltanto ai concetti pi importanti. Procedendo nella comprensione del nuovo modello di programmazione, sar chiara levoluzione che, dallapproccio orientato a procedure e funzioni e quindi alla programmazione dal punto di vista del calcolatore, porta oggi ad un modello di analisi che, partendo dal punto di vista dellutente suddivide lapplicazione in concetti rendendo il codice pi comprensibile e semplice da mantenere. Il modello classico conosciuto come paradigma procedurale, pu essere riassunto in due parole: Divide et Impera ossia dividi e conquista. Difatti secondo il paradigma procedurale, un problema complesso suddiviso in problemi pi semplici in modo che siano facilmente risolvibili mediante programmi procedurali. E chiaro che in questo caso, lattenzione del programmatore accentrata al problema. A differenza del primo, il paradigma Object Oriented accentra lattenzione verso dati. Lapplicazione suddivisa in un insieme di oggetti in grado di interagire tra loro, e codificati in modo tale che la macchina sia in gradi di comprenderli.

Figura 1:

Evoluzione del modello di programmazione

Il primo cambiamento evidente quindi a livello di disegno dellapplicazione. Di fatto, lapproccio Object Oriented non limitato a linguaggi come Java o C++. Molte applicazioni basate su questo modello, sono state scritte con

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 6

linguaggi tipo C o Assembler. Un linguaggio Object Oriented semplifica il meccanismo di creazione degli Oggetti allo stesso modo con cui un linguaggio procedurale semplifica la decomposizione in funzioni.

1.2

Una evoluzione necessaria

Quando i programmi erano scritti in assembler, ogni dato era globale e le funzioni andavano disegnate a basso livello. Con lavvento dei linguaggi procedurali come il linguaggio C, i programmi sono diventati pi robusti e semplici da mantenere perch il linguaggio forniva regole sintattiche e semantiche che, supportate da un compilatore, consentivano un maggior livello di astrazione rispetto a quello fornito dallassembler fornendo un ottimo supporto alla scomposizione procedurale dellapplicazione. Con laumento delle prestazioni dei calcolatori e di conseguenza con laumento della complessit delle applicazioni, lapproccio procedurale ha iniziato a mostrare i propri limiti evidenziando la necessit di definire un nuovo modello e nuovi linguaggi di programmazione. Questa evoluzione stata schematizzata nella figura 1. I linguaggi come Java e C++ forniscono il supporto ideale allo sviluppo di applicazioni per componenti, fornendo un insieme di regole, sintattiche e semantiche, che aiutano nello sviluppo di oggetti.

1.3

Il paradigma procedurale

Secondo il paradigma procedurale, il programmatore analizza il problema ponendosi dal punto di vista del computer che esegue solamente istruzioni semplici e di conseguenza adotta un approccio di tipo "divide et impera1". Il programmatore sa perfettamente che unapplicazione per quanto complessa pu essere suddivisa in procedure e funzioni di piccola entit. Questapproccio stato formalizzato in molti modi ed ben supportato da un gran numero di linguaggi che forniscono al programmatore un ambiente in cui siano facilmente definibili procedure e funzioni. Le procedure e le funzioni sono blocchi di codice riutilizzabile che possiedono un proprio insieme di dati e realizzano specifiche funzioni. Le funzioni definite possono essere richiamate ripetutamente in un programma, possono ricevere parametri che modificano il loro stato e possono tornare valori al codice chiamante. Procedure e funzioni possono essere legate assieme a formare unapplicazione, ed quindi necessario che, allinterno di una applicazione, i dati siano condivisi tra loro. Questo meccanismo si risolve mediante luso di variabili globali, passaggio di parametri e ritorno di valori.

Divide et Impera ossia dividi e conquista era la tecnica utilizzata dagli antichi romani che sul campo di battaglia dividevano le truppe avversarie per poi batterle con pochi sforzi.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 7

Unapplicazione procedurale tipica, ed il suo diagramma di flusso, riassunta nella Figura 2. Luso di variabili globali genera per problemi di protezione dei dati quando le procedure si richiamano tra loro. Per esempio nellapplicazione mostrata nella Figura 2, la procedura output esegue una chiamata a basso livello verso il terminale e pu essere chiamata soltanto dalla procedura print la quale a sua volta modifica dati globali. A causa del fatto che le procedure non sono auto-documentanti (selfdocumenting), ossia non rappresentano entit ben definite, un programmatore dovendo modificare lapplicazione e non conoscendone a fondo il codice, potrebbe utilizzare la routine Output senza chiamare la procedura Print dimenticando quindi laggiornamento dei dati globali a carico di Print e producendo di conseguenza effetti indesiderati (sideeffects) difficilmente gestibili.

Figura 2:

Diagramma di una applicazione procedurale

Per questo motivo, le applicazioni basate sul modello procedurale sono difficili da aggiornare e controllare con meccanismi di debug. Gli errori derivanti da effetti indesiderati possono presentarsi in qualunque punto del codice causando la propagazione incontrollata dellerrore. Ad esempio riprendendo ancora la nostra applicazione, una gestione errata dei dati globali dovuta ad una mancata chiamata a Print potrebbe avere effetto su f4() che a sua volta propagherebbe lerrore ad f2() ed f3() fino al main del programma causando la terminazione anomala del processo.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 8

1.4

Correggere gli errori procedurali

Per risolvere i problemi presentati nel paragrafo precedente, i programmatori hanno fatto sempre pi uso di tecniche mirate a proteggere dati globali o funzioni nascondendone il codice. Un modo sicuramente spartano, ma spesso utilizzato, consisteva nel nascondere il codice di routine sensibili (Output nel nostro esempio) allinterno di librerie contando sul fatto che la mancanza di documentazione scoraggiasse un nuovo programmatore ad utilizzare impropriamente queste funzioni. Il linguaggio C fornisce strumenti mirati alla circoscrizione del problema come il modificatore static con il fine di delimitare sezioni di codice all'interno dellapplicazione in grado di accedere a dati globali, eseguire funzioni di basso livello o, evitare direttamente luso di variabili globali: se applicato, il modificatore static ad una variabile locale, il calcolatore alloca memoria permanente in modo molto simile a quanto avviene per le variabili globali. Questo meccanismo consente alla variabile dichiarata static di mantenere il proprio valore tra due chiamate successive ad una funzione. A differenza di una variabile locale non statica, il cui ciclo di vita (di conseguenza il valore) limitato al tempo necessario per lesecuzione della funzione, il valore di una variabile dichiarata static non andr perduto tra chiamate successive. La differenza sostanziale tra una variabile globale ed una variabile locale static che la seconda nota solamente al blocco in cui dichiarata ossia una variabile globale con scopo limitato, viene inizializzata solo una volta allavvio del programma, e non ogni volta che si effettui una chiamata alla funzione in cui sono definite. Supponiamo ad esempio di voler scrivere un programma che calcoli la somma di numeri interi passati ad uno ad uno per parametro. Grazie alluso di variabili static sar possibile risolvere il problema nel modo seguente:
int _sum (int i) { static int sum=0; sum=sum + I; return sum; }

Usando una variabile static, la funzione in grado di mantenere il valore della variabile tra chiamate successive evitando luso di variabili globali. Il modificatore static pu essere utilizzato anche con variabili globali. Difatti, se applicato ad un dato globale indica al compilatore che la variabile creata dovr essere nota solamente alle funzioni dichiarate nello stesso file contenente la dichiarazione della variabile. Stesso risultato lo otterremmo applicando il modificatore ad una funzione o procedura. Questo meccanismo consente di suddividere applicazioni procedurali in moduli. Un modulo un insieme di dati e procedure logicamente correlate tra loro, le cui parti sensibili possono essere isolate in modo da poter essere

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 9

invocate solo da determinati blocchi di codice. Il processo di limitazione dellaccesso a dati o funzioni conosciuto come incapsulamento. Un esempio tipico di applicazione suddivisa in moduli schematizzato nella figura 3 nella quale rappresentata una nuova versione del modello precedentemente proposto. Il modulo di I/O mette a disposizione degli altri moduli la funzione Print incapsulando la routine Output ed i dati sensibili.

Figura 3:

Diagramma di una applicazione procedurale suddiviso per moduli

Questevoluzione del modello fornisce numerosi vantaggi; i dati ora non sono completamente globali e sono quindi pi protetti che nel modello precedente, limitando di conseguenza i danni causati da propagazioni anomale degli errori. Inoltre il numero limitato di procedure pubbliche viene in aiuto ad un programmatore che inizi a studiare il codice dellapplicazione. Questo nuovo modello si avvicina molto a quello proposto dallapproccio Object Oriented.

1.5

Il paradigma Object Oriented

Il paradigma Object Oriented formalizza la tecnica vista in precedenza di incapsulare e raggruppare parti di un programma. In generale, il programmatore divide le applicazioni in gruppi logici che rappresentano concetti sia a livello di utente sia applicativo; i pezzi sono poi riuniti a formare unapplicazione. Scendendo nei dettagli, il programmatore ora inizia con lanalizzare tutti i singoli aspetti concettuali che compongono un programma. Questi concetti sono chiamati oggetti ed hanno nomi legati a ci che rappresentano: una volta che gli oggetti sono identificati, il programmatore decide di quali attributi (dati) e funzionalit (metodi) dotare le entit. Lanalisi infine dovr

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 10

includere le regole di interazione tra gli oggetti. Proprio grazie a queste interazioni sar possibile riunire gli oggetti a formare unapplicazione. A differenza di procedure e funzioni, gli oggetti sono auto-documentanti (self-documenting). Unapplicazione pu essere scritta avendo solo poche informazioni ed in particolare, il funzionamento interno delle funzionalit di ogni oggetto completamente nascosto al programmatore (Incapsulamento Object Oriented).

1.6

Classi di Oggetti

Concentriamoci per qualche istante su alcuni concetti tralasciando laspetto tecnico del paradigma Object Oriented e proviamo per un istante a pensare alloggetto che state utilizzando: un libro. Pensando ad un libro naturale richiamare alla mente un insieme di oggetti aventi caratteristiche in comune: tutti i libri contengono delle pagine, ogni pagina contiene del testo e le note sono scritte sul fondo. Altra cosa che ci viene subito in mente riguarda le azioni che tipicamente compiamo quando utilizziamo un libro: voltare pagina, leggere il testo, guardare le figure etc. E interessante notare che utilizziamo il termine libro per generalizzare un concetto relativo a qualcosa che contiene pagine da sfogliare, da leggere o da strappare ossia ci riferiamo ad un insieme di oggetti con attributi comuni, ma comunque composto di entit aventi ognuna caratteristiche proprie che rendono ognuna differente rispetto allaltra. Pensiamo ora ad un libro scritto in francese. Ovviamente sar comprensibile soltanto a persone in grado di comprendere questa lingua; daltro canto possiamo comunque sfogliarne i contenuti (anche se privi di senso), guardarne le illustrazioni o scriverci dentro. Questo insieme generico di propriet rende un libro utilizzabile da chiunque a prescindere dalle caratteristiche specifiche (nel nostro caso la lingua). Possiamo quindi affermare che un libro un oggetto che contiene pagine e contenuti da guardare. Abbiamo quindi definito una categoria di oggetti che chiameremo classe e che, nel nostro caso, fornisce la descrizione generale del concetto di libro. Ogni nuovo libro con caratteristiche proprie apparterr comunque a questa classe di partenza.

1.7

Ereditariet

Con la definizione di una classe di oggetti, nel paragrafo precedente abbiamo stabilito che, in generale, un libro contiene pagine che possono essere girate, scarabocchiate, strappate etc. Stabilita la classe base, possiamo creare tanti libri purch aderiscano alle regole definite (Figura 1-4). Il vantaggio maggiore nellaver stabilito questa

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 11

classificazione che ogni persona deve conoscere solo le regole base per essere in grado di poter utilizzare qualsiasi libro. Una volta assimilato il concetto di "pagina che pu essere sfogliata", si in grado di utilizzare qualsiasi entit classificabile come libro.

Figura 4:

La classe generica Libro e le sue specializzazioni

1.8

Il concetto di ereditariet nella programmazione

Estendendo i concetti illustrati alla programmazione iniziamo ad intravederne i reali vantaggi. Una volta stabilite le categorie di base, possiamo utilizzarle per creare tipi specifici di oggetti, ereditando e specializzando le regole di base.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 12

Figura 5:

Diagramma di ereditariet

Per definire questo tipo di relazioni, utilizzata una forma a diagramma in cui la classe generica riportata come nodo sorgente di un grafo orientato, i sottonodi rappresentano categorie pi specifiche, gli archi che uniscono i nodi sono orientati da specifico a generale (Figura 5). Un linguaggio orientato ad oggetti fornisce al programmatore strumenti per rappresentare queste relazioni. Una volta definite classi e relazioni, sar possibile mediante il linguaggio implementare applicazioni in termini di classi generiche; questo significa che unapplicazione sar in grado di utilizzare ogni oggetto specifico senza essere necessariamente riscritta, ma limitando le modifiche alle funzionalit fornite dalloggetto per manipolare le sue propriet.

1.9

Vantaggi nelluso dellereditariet

Come facile intravedere, lorganizzazione degli oggetti fornita dal meccanismo di ereditariet rende semplici le operazioni di manutenzione di unapplicazione: ogni volta che si renda necessaria una modifica, in genere sufficiente crearne un nuovo allinterno di una classe di oggetti ed utilizzarlo per rimpiazzarne uno vecchio ed obsoleto. Un altro vantaggio dellereditariet la capacit di fornire codice riutilizzabile. Creare una classe di oggetti per definire entit molto di pi che crearne una semplice rappresentazione: per la maggior parte delle classi, limplementazione spesso inclusa allinterno della descrizione. In Java ad esempio ogni volta che definiamo un concetto, esso definito come una classe allinterno della quale scritto il codice necessario ad implementare le funzionalit delloggetto per quanto generico esso sia. Se un nuovo oggetto creato per mezzo del meccanismo della ereditariet da un oggetto esistente, si dice che la nuova entit deriva dalla originale. Quando questo accade, tutte le caratteristiche delloggetto principale diventano parte della nuova classe. Poich l'oggetto derivato eredita le funzionalit del suo predecessore, lammontare del codice da implementare notevolmente ridotto. Il codice dell'oggetto dorigine stato riutilizzato. A questo punto necessario iniziare a definire formalmente alcuni termini. La relazione di ereditariet tra oggetti espressa in termini di superclasse e sottoclasse. Una superclasse la classe pi generica utilizzata come punto di partenza per derivare nuove classi. Una sottoclasse rappresenta invece una specializzazione di una superclasse. E uso comune chiamare una superclasse classe base e una sottoclasse classe derivata. Questi termini sono comunque relativi perch una classe derivata pu a sua volta essere una classe base per una pi specifica.

1.10 Programmazione Object Oriented ed incapsulamento


Come gi ampiamente discusso, nella programmazione "Object Oriented" definiamo oggetti creando rappresentazioni di entit o nozioni da utilizzare

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 13

come parte di unapplicazione. Per assicurarci che il programma lavori correttamente, ogni oggetto deve rappresentare in modo corretto il concetto di cui modello senza che lutente possa disgregarne lintegrit. Per fare questo importante che loggetto esponga solo la porzione di codice e dati che il programma deve utilizzare. Ogni altro dato e codice devono essere nascosti affinch sia possibile mantenere loggetto in uno stato consistente. Ad esempio, se un oggetto rappresenta uno stack2 di dati ( figura 6 ), lapplicazione dovr poter accedere solo al primo dato dello stack od inserirne uno nuovo sulla cima, ossia alle funzioni di Push e Pop.

Figura 6:

Stack di dati

Il contenitore, ed ogni altra funzionalit necessaria alla sua gestione, dovranno essere protetti rispetto allapplicazione garantendo cos che lunico errore in cui si pu incorrere quello di inserire un oggetto sbagliato in testa allo stack o estrapolare pi dati del necessario. In qualunque caso lapplicazione non sar mai in grado di creare inconsistenze nello stato del contenitore. Lincapsulamento inoltre localizza tutti i possibili problemi in porzioni ristrette di codice. Una applicazione potrebbe inserire dati sbagliati nello stack, ma saremo comunque sicuri che lerrore localizzato allesterno delloggetto.

1.11 I vantaggi dellincapsulamento


Una volta che un oggetto stato incapsulato e testato, tutto il codice ed i dati associati sono protetti. Modifiche successive al programma non potranno
2

Uno Stack di dati una struttura a pila allinterno della quale possibile inserire dati solo sulla cima ed estrarre solo lultimo dato inserito.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 14

causare rotture nelle dipendenze tra gli oggetti poich, non saranno in grado di vedere i legami tra dati ed entit. Leffetto principale sullapplicazione sar quindi quello di localizzare gli errori evitandone la propagazione e dotando lapplicazione di grande stabilit: per sua natura, un oggetto incapsulato non produce effetti secondari. In un programma scomposto per blocchi, le procedure e le funzioni tendono ad essere interdipendenti. Ogni variazione al programma, richiede spesso la modifica di funzioni condivise cosa che pu propagare un errore ad altri membri del programma che le utilizza. In un programma Object Oriented, le dipendenze sono sempre strettamente sotto controllo e sono mascherate allinterno delle entit concettuali. Modifiche a programmi di questo tipo riguardano tipicamente laggiunta di nuovi oggetti, ed il meccanismo di ereditariet ha leffetto di preservare lintegrit dei membri dellapplicazione. Se invece fosse necessario modificare internamente un oggetto le modifiche sarebbero, in ogni caso, limitate al corpo dellentit e quindi confinate allinterno delloggetto impedendo la propagazione di errori allesterno del codice. Anche le operazioni di ottimizzazione sono semplificate. Quando un oggetto risulta avere performance molto basse, si pu cambiare facilmente la sua struttura interna senza dovere riscrivere il resto del programma, purch le modifiche non tocchino le propriet gi definite delloggetto.

1.12 Alcune buone regole per creare oggetti


Un oggetto deve rappresentare un singolo concetto ben definito.
Rappresentare piccoli concetti con oggetti ben definiti aiuta ad evitare confusione inutile allinterno della applicazione. Il meccanismo dellereditariet rappresenta uno strumento potente per creare concetti pi complessi a partire da concetti semplici.

Un oggetto deve rimanere in uno stato consistente per tutto il tempo che viene utilizzato, dalla sua creazione alla sua distruzione.
Qualunque linguaggio di programmazione venga utilizzato, bisogna sempre mascherare limplementazione di un oggetto al resto della applicazione. Lincapsulamento una ottima tecnica per evitare effetti indesiderati e spesso incontrollabili.

Fare attenzione nellutilizzo della ereditariet.


Esistono delle circostanze in cui la convenienza sintattica della ereditariet porta ad un uso inappropriato della tecnica. Per esempio una lampadina pu essere accesa o spenta. Usando il meccanismo della ereditariet sarebbe

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 15

possibile estendere queste sue propriet ad un gran numero di concetti come un televisore, un fono etc.. Il modello che ne deriverebbe sarebbe inconsistente e confuso.

1.13 Storia del paradigma Object Oriented


Lidea di modellare applicazioni con oggetti piuttosto che funzioni, comparve per la prima volta nel 1967 con il rilascio del linguaggio Simula, prodotto in Norvegia da Ole-Johan Dahl e Kristen Nygaard. Simula, linguaggio largamente utilizzato per sistemi di simulazione, adottava i concetti di classe ed ereditariet. Il termine Object Oriented fu utilizzato per la prima volta solo qualche anno dopo, quando la Xerox lanci sul mercato il linguaggio Smalltalk sviluppato nei laboratori di Palo Alto. Se lidea iniziale di introdurre un nuovo paradigma di programmazione non aveva riscosso grossi successi allinterno della comunit di programmatori, lintroduzione del termine Object Oriented stimol la fantasia degli analisti e negli anni immediatamente a seguire videro la luce un gran numero di linguaggi di programmazione ibridi come C++ e Objective-C, oppure ad oggetti puri come Eiffel. Il decennio compreso tra il 1970 ed il 1980 fu decisivo per la metodologia Object Oriented. Lintroduzione di calcolatori sempre pi potenti e la successiva adozione di interfacce grafiche (GUI) prima da parte della Xerox e successivamente della Apple, spinse i programmatori ad utilizzare sempre di pi il nuovo approccio in grado adattarsi con pi semplicit alla complessit dei nuovi sistemi informativi. Fu durante gli anni 70 che la metodologia Object Oriented guadagn anche lattenzione della comunit dei ricercatori poich il concetto di ereditariet sembrava potesse fornire un ottimo supporto alla ricerca sullintelligenza artificiale. Fu proprio questo incredibile aumento di interesse nei confronti della programmazione ad oggetti che, durante gli anni successivi, diede vita ad una moltitudine di linguaggi di programmazione e di metodologie di analisi spesso contrastanti tra loro. Di fatto, gli anni 80 possono essere riassunti in una frase di Tim Rentsch del 1982: ...Object Oriented programming will be in the 1980's what structured programming was in the 1970's. Everyone will be in favor of it. Every manufacturer will promote his products as supporting it. Every manager will pay lip service to it. Every programmer will practice it (differently). And no one will know just what it is.'' [Rentsch82]

la programmazione Object Oriented fu nel 1980 quello che fu nel 1970 la programmazione strutturata. Tutti ne erano a favore. Tutti i produttori di

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 16

software promuovevano le loro soluzioni ed i loro prodotti a supporto. Ogni manager, a parole, ne era favorevole. Ogni programmatore la praticava (ognuno in modo differente). Nessuno sapeva di cosa si trattasse esattamente. [Rentsch82]
A causa dei tanti linguaggi orientati ad oggetti, dei molteplici strumenti a supporto, delle tante metodologie di approccio e di analisi gli anni ottanta rappresentano quindi la torre di Babele della programmazione ad oggetti.

1.14 Conclusioni
Che cosa sia esattamente unapplicazione Object Oriented appare quindi difficile da definire. Nel 1992 Monarchi e Puhr recensirono gran parte della letteratura esistente e riguardante metodologia Object Oriented. Dalla comparazione dei testi recensiti furono identificati tutti gli elementi in comune al fine di determinare un unico modello. I risultati di questa ricerca possono essere riassunti nel modo seguente:

Gli oggetti: o Sono entit anonime; o Incapsulano le proprie logiche; o Comunicano tramite messaggi; Organizzazione degli oggetti: o L ereditariet il meccanismo che consente di organizzare oggetti secondo gerarchie; o La gerarchia deve prevedere la possibilit di definire oggetti astratti per un maggior realismo nella definizione di un modello; o Gli oggetti devono essere auto-documentanti; Programmi o Un programma realizza il modello di un sistema; o Un cambiamento di stato del sistema si riflette in un cambiamento di stato degli oggetti; o Gli oggetti possono operare in concorrenza; o Supportano tecniche di programmazione non orientate agli oggetti l dove risulti necessario.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 17

2 UNIFIED MODELING LANGUAGE


2.1 Introduzione

Possedere un martello non fa un Architetto, progettare un edificio cosa complessa, ma ancor pi complesso coordinare tutto il personale e le attivit legate alla successiva fase di messa in opera. Analizzare e disegnare una applicazione, non molto differente dal costruire un edificio, bisogna prima risolvere tutte la complessit delineando la struttura finale del prodotto, e coordinare poi tutti gli attori coinvolti nella fase di scrittura del codice. Scopo dellanalisi quindi quello di modellare il sistema rappresentando in maniera precisa la formulazione di un problema che spesso pu risultare vaga od imprecisa; scopo del progetto quello di definirne i dettagli delineando le caratteristiche portanti per la successiva fase di sviluppo. I passi fondamentali nellanalizzare e progettare un sistema possono essere schematizzati come segue: Analisi di un sistema Fase Analisi Dettaglio 1. Esamina i requisiti e ne determina tutte le implicazioni; 2. Formula il problema in maniera rigorosa; 3 . Definisce le caratteristiche importanti rimanadando i dettagli alla fase successiva. 1. Definisce i dettagli della applicazione; 2. Disegna le entit e ne definisce le relazioni; 3 . Analizza gli stati di un sistema e ne disegna le varie transizioni; 4. Assegna i compiti per la successiva fase di sviluppo.

Progetto

Le tecniche di analisi e progettazione hanno subito una evoluzione che, camminando pari passo alle tecnologie, a partire dalla analisi funzionale ha portato oggi alla definizione del linguaggio UML (Unified Modelling Language) diventato in breve uno standard di riferimento per il mondo dellindustria.

2.2

Dal modello funzionale alle tecniche di Booch e OMT

Gli ultimi anni hanno visto la nascita di metodologie e strumenti di analisi e disegno che, utilizzando linguaggi visuali, facilitano la scomposizione in entit semplici di entit arbitrariamente complesse. Prima dellaffermazione del paradigma Object Oriented, la metodologia di disegno pi popolare era quella chiamata analisi strutturale che, aiutava lanalista nella scomposizione di unapplicazione in funzioni e procedure semplici, definendone per ognuna le interazioni con le altre. Le funzioni e le

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 18

procedure venivano a loro volta scomposte secondo un meccanismo gerarchico formato da funzioni e sotto funzioni, descrivendo le trasformazioni dei valori dei dati dentro al sistema mediante diagrammi di flusso. Un diagramma di flusso un grafo i cui nodi sono processi, e i cui archi sono flussi di dati. A seguito della affermazione del paradigma Object Oriented, gli anni 80 e linizio degli anni 90 videro la nascita di numerose metodologie di analisi e disegno ognuna con le proprie regole, le proprie notazioni ed i propri simboli. Tra il 1989 ed il 1984, le metodologie identificate erano oltre cinquanta ma, solo alcune di queste si affermarono rispetto alle altre, in particolare: la metodologia OMT (Object Modelling Technique) ideata da Rumbaugh che eccelleva nella analisi dei requisiti e della transizione di stato di un sistema, la metodologia di Booch che aveva il suo punto di forza nel disegno, ed infine la metodologia di Jacobsen chiamata OOSE (Object Oriented Software Engineering) che ebbe il merito di introdurre la definizione dei casi duso. Nelle prossime immagini sono riportati alcuni esempi di diagrammi definiti utilizzando le tre differenti metodologie.

Figura 7:

Diagramma OMT

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 19

Figura 8:

Diagramma dei casi-duso di OOSE

Figura 9:

Diagramma di classe di Booch

Nella prossima tabella sono schematizzate alcune delle differenze tra le due notazioni di Booch ed Rumbaugh. Comparazione tra le notazioni OMT e Booch Caratteristica OMT Booch

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 20

Diagrammi di R a p p r e s e n t a t i mediante classe rettangoli solidi con tre sezioni per identificare nome della classe, attributi ed operazioni.

Rappresentati mediante nuvolette tratteggiate contenenti nome, attributi ed operazioni omettendo la suddivisione in aree.

Visibilit di dati +: Public e m e t o d i -: Private membro #: Protected Classi Astratte

null: Public |: Private ||: Protected ||| : implementazione Identificate da molteplicit 0 Parola chiave abstract nellangolo destro in alto della utilizzata come propriet rappresentazione della classe. della classe.

Oggetti (classi Rettangolo solido contenente il Nuvoletta solida con nome allocate) nome della classe racchiusa tra della classe ed attributi. parentesi.

Note

Associazione

Testo racchiuso tra parentesi a Area rettangolare collegata fianco allelemento destinatario. con lelemento destinatario da una linea tratteggiata. Linea solida tra le due classi Linea solida tra le due coinvolte nella relazione ed il classi coinvolte nella nome della relazione sul lato relazione ed il nome della destro della linea. relazione sul lato destro della linea.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 21

Associazione multipla

Esattamente 1 : nulla Zero o Pi : Sfera solida Zero o Uno : sfera vuota

Esattamente 1 : 1 Zero o Pi : Sfera N Zero o Uno : Sfera 0..1

Ereditariet

Struttura ad albero che, a Freccia solida tra partire da un triangolo si sparge s o t t o c l a s s e e a ventaglio dalla superclasse superclasse. alle sottoclassi.

la la

2.3

Unified Modelling Language

Nel 1994, Grady Booch e Jim Rumbaugh diedero vita al progetto di fusione rispettivamente tra le metodologie Booch e OMT rilasciando nellottobre del 1995 le specifiche del predecessore dellattuale UML, allora chiamato UM (Unified Method). Alla fine del 1995 al gruppo si aggiunse Ivar Jacobson e dallo sforzo congiunto dei tre furono prodotte prima nel giugno 1996 ed infine ad ottobre dello stesso anno le versioni 0.9 e 0.9.1 di Unified Modelling Language (UML), ovvero Linguaggio di Modellazione Unificato. Durante lo stesso periodo, aumentavano i consensi degli analisti e del mondo dellindustria che vedeva nel linguaggio UML uno standard prezioso nella definizione delle proprie strategie: alla fine del 1996 fu creato un consorzio di aziende che, contribuendo con lesperienza dei propri analisti facilit nel gennaio del 1997 il rilascio della versione 1.0 del linguaggio.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 22

Figura 10:

Time-Line del linguaggio UML

Il prodotto finale fu quindi un linguaggio ben definito, potente e facilmente applicabile a differenti realt. Nello stesso anno, la OMG (Object Managment Group) decise di adottare UML 1.0 come linguaggio di riferimento.

2.4

Le caratteristiche di UML

Unified Modeling Language un linguaggio per tracciare le specifiche, disegnare e documentare la lavorazione di sistemi informativi complessi, mediante la definizione di modelli in grado di astrarre i vari aspetti del problema da risolvere. Di fatto, il linguaggio UML non intende fornire una metodologia per lapproccio al problema, ma lascia libero lanalista di decidere quale sia la strategia migliore da adottare ed il livello di astrazione di ogni modello. I diagrammi messi a disposizione dal linguaggio UML sono i seguenti: Use Case Diagram; Class Diagram; Behavior Diagrams: o State Diagram; o Activity Diagram; o Sequence Diagram; o Collaboration Diagram; Implementation Diagrams: o Component Diagram; o Deployment Diagram.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 23

Tutti questi diagrammi danno percezione dellintero sistema da diversi punti di vista: in particolare, come il paradigma ad oggetti accentra lattenzione del programmatore verso la rappresentazione di dati e non pi verso le funzioni, lanalisi UML consente di disegnare il sistema defininendo le entit che lo compongono, disegnando quali oggetti concorrono alla definizione di una funzionalit ed infine tutte le possibili interazioni tra gli oggetti. Infine, ogni oggetto pi essere definito in dettaglio tramite gli Activity Diagram o gli State Diagram.

2.5

Genealogia del linguaggio UML

Durante il processo di unificazione, gli autori del linguaggio inserirono allinterno di UML quanto di meglio era proposto dal mondo delle scienze dellinformazione, attingendo da metodologie diverse e spesso, non collegate al paradigma Object Oriented. Di fatto, la notazione del linguaggio UML una miscela di simboli appartenenti a diverse metodologie, con laggiunta di alcuni nuovi, introdotti in modo specifico a completamento del linguaggio. Lalbero genealogico del linguaggio UML quindi veramente complesso, ed brevemente schematizzato nella tabella seguente: Albero genealogico di UML Genealogia Derivati nelle apparenze dai diagrammi della metodologia OOSE. Derivati dai class diagram di OMT, Booch e molte altre metodologie. Basati sugli State Diagram di David Harel con alcune piccole modifiche. Attingono dai diagrammi di flusso comuni a molte metodologie Object Oriented e non. Possono essere ritrovati sotto altro nome in molte metodologie Object Oriented e non. Rappresentano una evoluzione degli object diagram di Booch, degli object interation Graph di Fusion e di altri appartenenti ad altre metodologie. Derivanti dai module diagram e process diagram di Booch.

Caratteristica Use-Case Diagram Class Diagram

State Diagram Activity Diagram Sequence Diagram Collaboration Diagram Implementation Diagrams (Component e Deployment)

2.6

Use Case Diagrams

Uno Use-Case Diagram descrive le attivit del sistema dal punto di vista di un osservatore eterno, rivolgendo lattenzione alle attivit che il sistema svolge,

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 24

prescindendo da come devono essere eseguite. Di fatto, questi diagrammi definisco gli scenari in cui gli attori interagiscono tra di loro. Consideriamo ad esempio lo scenario di una biblioteca pubblica:

Un utente si reca in biblioteca per consultare un libro. Richiede il libro al bibliotecario che, eseguite le dovute ricerche consegna il libro al lettore. Dopo aver consultato il libro, il lettore riconsegna il libro al bibliotecario. Attori ed azioni compiute possono essere schematizzate come segue:
La Biblioteca Pubblica Attori Utente Azioni Richiede un libro al bibliotecario; Ritira il libro; Legge il libro; Restituisce il libro al bibliotecario. Accetta la richiesta dellutente; Ricerca il libro; Consegna il libro; Ritira il libro dallutente;

Bibliotecario

Uno use-case diagram quindi un insieme di attori ed azioni. Un attore lentit che scatena una azione, e rappresentano il ruolo che lentit svolge nello scenario in analisi. Il collegamento tra attore ed azione detto comunicazione. Il diagramma relativo a questo scenario quindi il seguente:

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 25

Figura 11:

Use Case Diagram

I simboli utilizzati allinterno di questi diagrammi sono i seguenti: Notazione per i use case diagrams Descrizione

Simbolo

Attore

Azione

Comunicazione

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 26

Per concludere, il ruolo di questi tipo di diagrammi allinterno del disegno del sistema quello di: 1. Determinare le caratteristiche macroscopiche del sistema; 2 . Generare i casi di test: la collezione degli scenari un ottimo suggerimento per la preparazione dei casi di test del sistema.

2.7

Class Diagrams

Un class-diagram fornisce la fotografia del sistema, schematizzando le classi che lo compongono, e le relazioni tra di loro. I simboli utilizzati nei class-diagram sono i seguenti: Notazione per i class-diagram Descrizione

Simbolo

Classe o Interfaccia : rappresentata da un rettangolo suddiviso in tre parti: nome, attributi ed operazioni. I nomi di classi astratte o di interfacce appaiono in corsivo.

Relazione di generalizzazione : esprime una relazione di ereditariet. La freccia orientata dalla sottoclasse alla superclasse.

Associazioni : relazione tra due oggetti se, un oggetto deve utilizzarne un altro.

Aggregazioni : associazioni in cui una classe appartiene ad una collezione di classi di unaltra.

Package : collezione di oggetti UML logicamente correlati. Un package pu contenere altri package.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 27

Un possibile class-diagram per la biblioteca descritta nel paragrafo precedente pu quindi essere il seguente.

Figura 12:

Class Diagram di biblioteca

Dal diagramma in figura, si nota che, una associazione (aggregazione) pu essere accompagnata da un indice detto molteplicit. La molteplicit di una associazione (aggregazione) rappresenta il numero di oggetti allaltro capo del simbolo che possibile collezionare. Nel nostro esempio, un libro pu essere associato ad un solo utente, ma un utente pu prelevare pi di un libro. Tutte le possibili molteplicit sono schematizzate nella prossima tabella: Molteplicit Simbolo 0..1 oppure 0..* oppure * 1 1..* Descrizione Un oggetto pu collezione 0 o 1 oggetto di un altro tipo. Un oggetto pu collezione 0 o pi oggetti di un altro tipo. Esattamente un oggetto. Almeno un oggetto.

Nel caso in cui sia necessario organizzare diagrammi arbitrariamente complessi, possibile raggruppare le classi di un class-diagram allinterno di package. Ad esempio, possibile raggruppare tutti i libri a seconda della

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 28

tipologia utilizzando un package per ogni genere letterario come mostrato nella prossima figura.

Figura 13:

Organizzazione di classi mediante package

2.8

Sequence diagrams

Un sequence diagram descrive le interazioni tra oggetti, mettendo a fuoco lordine temporale con cui le varie azioni sono eseguite partendo dallalto del diagramma e procedendo verso il basso. Ricordando le classi definite nel class diagram della nostra applicazione biblioteca, e lo scenario descritto nello usecase diagram, nelle due prossime figure sono rappresentati i sequence diagram che forniscono la specifica delle interazioni tra le classi necessarie ad implementare rispettivamente le due funzionalit di richiesta del libro al bibliotecario e la successiva restituzione dopo la lettura.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 29

Figura 14:

Richiesta di un libro

Figura 15:

Lettura e restituzione del libro

La simbologia utilizzata per realizzare sequence diagram descritta nella prossima tabella:

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 30

Simbolo

Notazione per i sequence diagram Descrizione Oggetto: rappresenta un oggetto il cui nome evidenziato in alto in grassetto , ed il cui tipo riportato subito sotto il nome.

(1) Activation bar: definisce la durata della attivazione di una funzionalit. (2) Lifetime: rappresenta il tempo di esistenza di un oggetto.

Message Call : chiamata tra due oggetti o tra operazioni di uno stesso oggetto.

2.9

Collaboration Diagram

Come i sequence diagram, i collaboration diagram descrivono interazioni tra oggetti fornendo le stesse informazioni da un altro punto di vista. Di fatto, se nel caso precedente il soggetto del diagramma era il tempo, ora vengono messe a fuoco le regole di interazione tra gli oggetti. I simboli utilizzati in un collaboration diagram sono i seguenti: Notazione per i collaboration diagram Descrizione Object-Role: o regole, sono rappresentati da aree contenenti il nome delloggetto, il nome della classe o entrambi con il nome della classe preceduto dal carattere :.

Simbolo

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 31

Message Call : chiamata tra due oggetti o tra operazioni di uno stesso oggetto.

I sequenze diagram descritti nel paragrafo precedente, possono essere espressi in forma di collaboration diagram nel seguente modo:

Figura 16:

Collaboration Diagram di biblioteca

Come appare chiaro dal diagramma riportato in figura, in un collaboration diagram ogni invio di messaggio identificato da un numero sequenziale. Le regole con cui viene assegnato il numero sequenziale sono le seguenti: Il messaggio di primo livello ha numero sequenziale 1; Messaggi sullo stesso livello hanno stesso prefisso e suffisso variabile (1,2,3n) dipendentemente dal tempo di occorrenza del messaggio.

2.10 Statechart Diagram


Vedremo nei capitoli successivi che un oggetto, oltre ad avere un aspetto, possiede anche uno stato. Lo stato di un oggetto dipende dallattivit corrente o dalle condizioni al contorno in un determinato istante. Uno statechart diagram schematizza tutti i possibili stati di un oggetto e le transizioni che causano il cambiamento di stato delloggetto. Nella prossima tabella sono schematizzati i simboli utilizzati per realizzare uno statechart diagram.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 32

Simbolo

Notazione per i statechart diagram Descrizione State: rappresenta lo stato di un oggetto ad un determinato istante a seguito di una transizione.

Initial state: rappresenta lo stato iniziale delloggetto. End state: rappresenta lo stato finale delloggetto. Transition: rappresenta la transizione tra due stati o, da uno stato a se stesso (self transition) Supponiamo che il nostro utente della biblioteca pubblica voglia effettuare una pausa desideroso di recarsi a prendere un caffe al distributore automatico. La prima azione che dovr essere compiuta quella di inserire le monete fino a raggiungere la somma richiesta per erogare la bevanda. Nel prossimo esempio, descriveremo il processo di accettazione delle monete da parte del distributore. Il distributore in questione accetta solo monete da 5,10 e 20 centesimi di euro, la bevanda costa 25 centesimi di euro ed il distributore non eroga resto. Lo statechart diagram sar il seguente:

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 33

Figura 17:

Statechart diagram

Nel nostro diagramma, abbiamo un solo stato iniziale ed un solo stato finale. Stato iniziale e finale sono due stati finti (dummy) in quanto sono necessari solo ad indicare linizio e la fine della azione descritta nel diagramma.

2.11 Activity Diagrams


Un activity diagram pu essere paragonato ad uno statechart diagram con la differenza che, se uno statechart diagram focalizza lattenzione dellanalisi sullattivit di un singolo oggetto appartenente ad un processo, un activity diagram descrive le attivit svolte da tutti gli oggetti coinvolti nella realizzazione di un processo, descrivendone le reciproche dipendenze. Torniamo ad occuparci nuovamente della biblioteca comunale, ed analizziamo ancora, questa volta mediante activity diagram le attivit compiute dalla due classi, Utente e Bibliotecario, al momento della richiesta del libro.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 34

Figura 18:

Richiesta del libro al bibliotecario : activity diagram

Un activity diagram pu essere suddiviso mediante simboli detti swimlanes o corsie, che rappresentano loggetto responsabile delle attivit contenute. Una attivit pu essere collegate ad una o pi attivit correlate. I simboli utilizzati per disegnare il diagramma sono descritti nella tabella seguente: Notazione per gli activity diagram Descrizione Activity: rappresenta un a attivit svolta da un oggetto ad un determinato istante a seguito di una transizione.

Simbolo

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 35

Initial state: rappresenta lo stato iniziale. End state: rappresenta lo stato finale. Transition: rappresenta la relazione tra due attivit.

Swimlane: rappresenta loggetto che compie determinate attivit.

Merge: rappresenta una condizione che modifica il flusso delle attivit.

2.12 Component Diagram e Deployment Diagram


Il linguaggio UML definisce componente, un modulo (od oggetto) del codice che comporr lapplicazione finale. Entrambi, component diagram e deployment diagram, rappresentano due viste differenti delle componenti che formano il sistema finale. Un component diagram rappresenta lanalogo fisico di un class diagram, un deployment diagram schematizza la configurazione fisica delle componenti software del sistema. La prossima immagine rappresenta il component diagram costruito a partire dal class diagram della applicazione biblioteca.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 36

Figura 19:

Component diagram di biblioteca

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 37

3 IL LINGUAGGIO JAVA
3.1 Introduzione

Java un linguaggio di programmazione Object Oriented indipendente dalla piattaforma, modellato dal linguaggio C e C++ di cui mantiene alcune caratteristiche. Lindipendenza dalla piattaforma ottenuta grazie alluso di uno strato software chiamato Java Virtual Machine che traduce le istruzioni dei codici binari, indipendenti dalla piattaforma e generati dal compilatore java, in istruzioni eseguibili dalla macchina locale. La natura di linguaggio Object Oriented di Java, consente lo sviluppo applicazioni utilizzando oggetti concettuali piuttosto che procedure e funzioni. La sintassi orientata ad oggetti di Java supporta la creazione di classi concettuali, consentendo al programmatore di scrivere codice stabile e riutilizzabile per mezzo del paradigma Object Oriented, secondo il quale il programma scomposto in concetti piuttosto che funzioni o procedure. La sua stretta parentela con il linguaggio C a livello sintattico fa s che un programmatore che abbia gi fatto esperienza con linguaggi come C, C++, Perl sia facilitato nellapprendimento del linguaggio. In fase di sviluppo, lo strato che rappresenta la virtual machine pu essere creato mediante il comando java anche se molti ambienti sono in grado di fornire questo tipo di supporto. Esistono inoltre compilatori specializzati o jit (Just In Time) in grado di generare codice eseguibile dipendente dalla piattaforma. Infine Java contiene alcune caratteristiche che lo rendono particolarmente adatto alla programmazione di applicazioni web (client-side e server-side).

3.2

La storia di Java

Durante laprile del 1991, un gruppo di impiegati della SUN Microsystem, conosciuti come Green Group iniziarono a studiare la possibilit di creare una tecnologia in grado di integrare le allora attuali conoscenze nel campo del software con lelettronica di consumo: lidea era creare una tecnologia che fosse applicabile ad ogni tipo di strumento elettronico al fine di garantire un alto grado di interattivit, diminuire i tempi di sviluppo ed abbassare i costi di implementazione utilizzando un unico ambiente operativo. Avendo subito focalizzato il problema sulla necessit di avere un linguaggio indipendente dalla piattaforma (il software non doveva essere legato ad un particolare processore) il gruppo inizi i lavori nel tentativo di creare una tecnologia partendo dal linguaggio C++. La prima versione del linguaggio fu chiamata Oak. Attraverso una serie di eventi, quella che era la direzione originale del progetto sub vari cambiamenti, ed il target fu spostato dallelettronica di consumo al world wide web. Nel 1993 il National Center for Supercomputing

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 38

Applications (NCSA) rilasci Mosaic, una applicazione che consentiva agli utenti di accedere mediante interfaccia grafica ai contenuti di Internet allora ancora limitati al semplice testo. Nellarco di un anno, da semplice strumento di ricerca, Internet diventato il mezzo di diffusione che oggi tutti conosciamo in grado di trasportare non solo testo, ma ogni tipo di contenuto multimediale: fu durante il 1994 che la SUN decise di modificare il nome di Oak in Java. La prima versione del linguaggio consentiva lo sviluppo applicazioni standalone e di piccole applicazioni, chiamate applet, in grado di essere eseguite attraverso la rete. Il 23 Maggio del 1995 la SUN ha annunciato formalmente Java. Da quel momento in poi il linguaggio stato adottato da tutti i maggiori produttori di software incluse IBM, Hewlett Packard e Microsoft.

3.3

Indipendenza dalla piattaforma

Le istruzioni binarie di Java, indipendenti dalla piattaforma, sono comunemente conosciute come Bytecode. Il Bytecode di java prodotto dal compilatore e necessita di uno strato di software per essere eseguito. Questapplicazione, detta interprete (Figura 20), nota come Java Virtual Machine (che per semplicit indicheremo con JVM). La JVM un programma scritto mediante un qualunque linguaggio di programmazione, dipendente dalla piattaforma su cui deve eseguire il Bytecode, e traduce le istruzioni Java dalla forma di Bytecode in istruzioni comprensibili dal processore della macchina che ospita lapplicazione.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 39

Figura 20:

Architettura di una applicazione Java

Non essendo il Bytecode legato ad una particolare architettura hardware, questo fa s che per trasferire unapplicazione Java da una piattaforma ad unaltra necessario solamente che la nuova piattaforma sia dotata di unapposita JVM. In presenza di un interprete unapplicazione Java potr essere eseguita su qualunque piattaforma senza necessit di essere ulteriormente compilata. In alternativa, si possono utilizzare strumenti come i Just In Time Compilers, compilatori in grado di tradurre il Bytecode in un formato eseguibile per una specifica piattaforma al momento dellesecuzione del programma Java. I vantaggi nelluso dei compilatori JIT sono molteplici. La tecnologia JIT traduce il Bytecode in un formato eseguibile al momento del caricamento in memoria, ci consente di migliorare le performance dellapplicazione che non dovr pi passare per la virtual machine, e allo stesso tempo preserva la caratteristica di portabilit del codice. Lunico svantaggio nelluso di un JIT sta nella perdita di prestazioni al momento dellesecuzione della applicazione che, deve essere prima compilata e poi eseguita. Un ultimo aspetto interessante di Java quello legato agli sviluppi che la tecnologia sta avendo. Negli ultimi anni, molti produttori di sistemi elettronici hanno iniziato a rilasciare processori in grado di eseguire direttamente il Bytecode a livello di istruzioni macchina senza luso di una virtual machine.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 40

3.4

Uso della memoria

Un problema scottante quando si parla di programmazione la gestione delluso della memoria; quando si progetta unapplicazione, molto complesso affrontare le problematiche inerenti al mantenimento degli spazi di memoria. Una gestione della memoria superficiale o mal pensata spesso causa di un problema noto come memory leak: lapplicazione alloca risorse senza riuscire a rilasciarle completamente determinando la perdita di piccole porzioni di memoria che, se sommate, possono provocare linterruzione anomala dellapplicazione o, nel caso peggiore, allintero sistema che la ospita. E facile immaginare linstabilit di sistemi informativi affetti da questo tipo di problema. Tali applicazioni richiedono spesso lo sviluppo di complesse procedure specializzate nella gestione, nel tracciamento e nel rilascio della memoria allocata. Java risolve il problema alla radice sollevando il programmatore dallonere della gestione della memoria grazie ad un meccanismo detto Garbage Collector . Il Garbage Collector, tiene traccia degli oggetti utilizzati da unapplicazione Java, nonch delle referenze a tali oggetti. Ogni volta che un oggetto non pi referenziato (ovvero utilizzato da altre classi), per tutta la durata di uno specifico intervallo temporale, rimosso dalla memoria e la risorsa liberata nuovamente messa a disposizione dellapplicazione che potr continuare a farne uso. Questo meccanismo in grado di funzionare correttamente in quasi tutti i casi anche se molto complessi, ma non si pu affermare che completamente esente da problemi. Esistono, infatti, dei casi documentati, di fronte ai quali il Garbage Collector non in grado di intervenire. Un caso tipico quello della referenza circolare in cui un oggetto A referenzia un oggetto B e viceversa, ma lapplicazione non sta utilizzando nessuno dei due come schematizzato nella figura 21.

Figura 21:

riferimento circolare

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 41

3.5

Multi-threading

Java un linguaggio multi-threaded. Il multi-threading consente ad applicazioni Java di sfruttare il meccanismo di concorrenza logica: parti separate di un programma possono essere eseguite come se fossero (dal punto di vista del programmatore) eseguite parallelamente. Luso di thread rappresenta un modo semplice di gestire la concorrenza tra processi poich, gli spazi di indirizzamento della memoria dellapplicazione sono condivisi con i thread, eliminando cos la necessit di sviluppare complesse procedure di comunicazione tra processi. Nei capitoli successivi torneremo a parlare in maniera approfondita di questaspetto del linguaggio.

3.6

Dynamic Loading and Linking

Il meccanismo tradizionale per caricare ed eseguire unapplicazione, chiamato static linking, prevede il caricamento in memoria di tutte le procedure e funzioni raggruppate in un unico file eseguibile. Questo tipo di approccio ha alcuni svantaggi: 1 . I programmi contengono sempre tutto il codice ed i dati che potrebbero essere richiesti durante lesecuzione; 2. Tutto il codice ed i dati devono essere caricati in memoria al momento dellesecuzione dellapplicazione anche se solo una piccola parte di questi saranno realmente utilizzati; 3. Lintero codice di unapplicazione deve essere ricaricato quando una procedura o una funzione sono modificate. Java supporta il meccanismo detto di Dynamic Loading and Linking secondo il quale ogni modulo del programma (classe) memorizzato in un determinato file e quando un programma Java eseguito, le classi sono caricate e allocate solo al momento del loro effettivo utilizzo. I vantaggi nelluso di questo modello sono molteplici: 1. Riduce il consumo di memoria. Un oggetto caricato in memoria solo se richiesto da un altro membro della applicazione; 2. Le applicazioni possono controllare facilmente luso della memoria caricando gli oggetti solo su richiesta. Java prevede un gran numero di classi gi compilate che forniscono molte funzionalit: strumenti di scrittura e lettura da file o da altre sorgenti, meccanismi di interfacciamento con le reti, ecc. ecc..

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 42

3.7

Meccanismo di caricamento delle classi da parte della JVM

Il meccanismo di Dynamic Loading and Linking incide sul modo di esecuzione di unapplicazione da parte delle JVM. Di fatto, quando la JVM avviata la prima volta, il primo passo che deve compiere quello di caricare le classi indispensabili allapplicazione affinch possa essere eseguita. Questa fase avviene secondo uno schema temporale ben preciso a carico di un modulo interno chiamato launcher. Le prime ad essere caricate dal launcher sono le classi di base necessarie alla piattaforma Java per fornire lo strato di supporto alla applicazione a cui fornir lambiente di run-time. Il secondo passo caricare le classi Java appartenenti alle librerie di oggetti messi a disposizione dalla SUN ed utilizzate allinterno della applicazione. Infine vengono caricate le classi componenti lapplicazione e definite dal programmatore. Per consentire al launcher di trovare le librerie e le classi utente, necessario specificare esplicitamente la loro posizione sul disco. Per far questo necessario definire una variabile di ambiente chiamata CLASSPATH che viene letta sia in fase di compilazione, sia in fase di esecuzione della applicazione.

3.8

La variabile dambiente CLASSPATH

La variabile di ambiente CLASSPATH, contiene lelenco delle cartelle allinterno delle quali la JVM dovr ricercare le definizioni delle classi java contenenti il Bytecode da eseguire. I file contenenti le definizioni delle classi Java possono essere memorizzati allinterno di una cartella o raggruppati in archivi compressi in formato zip o jar: nel caso in cui la JVM trovi, allinterno della variabile CLASSPATH, un riferimento ad un file con estensione zip o jar, automaticamente naviga allinterno dellarchivio alla ricerca delle classi necessarie. Il formato della variabile dambiente CLASSPATH varia a seconda del sistema operativo che ospita la virtual machine: Nel caso dei sistemi operativi della Microsoft (Windows 95,98,NT,2000,XP) contiene lelenco delle cartelle e degli archivi separati dal carattere ;. Ad esempio la variabile dambiente CLASSPATH = .;.\;c:\jdk\lib\tools.jar:c:\src\myclasses\; fa riferimento alla cartella corrente, allarchivio tools.jar memorizzato nella cartella c:\jdk\lib\, ed alla cartella c:\src\myclasses\.

3.9

Il Java Software Development Kit (SDK)

Java Software Development Kit un insieme di strumenti ed utilit ed messo a disposizione gratuitamente da tanti produttori di software. Linsieme base o standard delle funzionalit supportato direttamente da SUN

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 43

Microsystem ed include un compilatore (javac), una Java Virtual Machine (java), un debugger e tanti altri strumenti necessari allo sviluppo di applicazioni Java. Il Java SDK comprende, oltre alle utilit a linea di comando, un completo insieme di classi gi compilate ed il relativo codice sorgente. La documentazione generalmente distribuita separatamente, rilasciata in formato HTML e copre tutto linsieme delle classi rilasciate con il SDK a cui da ora in poi ci riferiremo come alle Java Core API (Application Program Interface).

3.10 Scaricare ed installare il JDK


Questo testo fa riferimento alla versione dello Java Software Development Kit rilasciata dalla Javasoft nel corso del 2001: lo Java SDK 1.3.1. La SUN ha appena rilasciato va versione 1.4 dello SDK, ma nella trattazione, per la generalit dei suoi contenuti, si far riferimento alla versione 1.3.1. Il java SDK e tutta la sua documentazione possono essere scaricati gratuitamente dal sito della Javasoft allindirizzo:

http://java.sun.com/j2se/1.3/download-solaris.html per sistemi operativi Solaris; http://java.sun.com/j2se/1.3/download-linux.html per il sistema operativo Linux; http://java.sun.com/j2se/1.3/download-windows.html per tutti i sistemi operativi Microsoft.

Linstallazione del prodotto semplice e nel caso di sistemi operativi Microsoft, richiede solo lesecuzione di un file auto-installante. Per semplicit, da questo momento in poi faremo riferimento al sistema operativo windows xp. Al momento dellistallazione, a meno di specifiche differenti, lo JSDK crea sul disco principale del computer la cartella jdk1.3.1_02 allinterno della quale istaller tutto il necessario al programmatore. Dentro questa cartella saranno create le seguenti sottocartelle:

c:\jdk1.3.1_02\bin contenente tutti i comandi java per compilare, le utility di servizio, oltre che ad una quantit di librerie dll di utilit varie. c:\jdk1.3.1_02\demo contenente molti esempi comprensivi di eseguibili e codici sorgenti; c:\jdk1.3.1_02\include contenente alcune librerie per poter utilizzare chiamate a funzioni scritte in C o C++;

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 44

c:\jdk1.3.1_02\lib contenente alcune file di libreria tra cui tools.jar contenente le Java Core API rilasciate da SUN ; c:\jdk1.3.1_02\src contenente il codice sorgente delle classi contenute nellarchivio tools.jar.
Terminata linstallazione dello JSDK, arrivato il momento di dedicarci alla documentazione preventivamente scaricata dal sito della Javasoft. La documentazione archiviata in un file compresso con estensione .zip; sar quindi necessario uno strumento in grado di decomprimerne il contenuto. Uno strumento molto comune per i sistemi operativi Microsoft Winzip, che pu essere scaricato dal sito www.winzip.com. Salviamo il contenuto dellarchivio allinterno della cartella principale di installazione dello JSDK; alla fine del processo i documenti saranno salvati nella sottocartella c:\jdk1.3.1_02\docs.

3.11 Il compilatore Java (javac)


Il compilatore Java (javac ) fornito con lo JSDK, pu essere trovato nella cartella c:\jdk1.3.1_02\bin . Questo programma a linea di comando unapplicazione interamente scritta in Java che, da un file con estensione .java produce un file con estensione .class contenente il Bytecode relativo alla definizione della classe che stiamo compilando. Un file con estensione .java un normale file in formato ASCII contenente del codice Java valido; il file generato dal compilatore Java potr essere caricato ed eseguito dalla Java Virtual Machine. Java un linguaggio case sensitive ovvero sensibile al formato, maiuscolo o minuscolo, di un carattere: di conseguenza necessario passare al compilatore java il parametro contenente il nome del file da compilare rispettandone il formato dei caratteri. Ad esempio, volendo compilare il file MiaPrimaApplicazione.java la corretta esecuzione del compilatore java la seguente:

javac MiaPrimaApplicazione.java
Qualunque altro formato produrr un errore di compilazione, ed il processo di generazione del Bytecode sar interrotto. Un altro accorgimento da rispettare al momento della compilazione riguarda lestensione del file da compilare: la regola vuole che sia sempre scritta esplicitamente su riga di comando. Ad esempio la forma utilizzata di seguito per richiedere la compilazione di una file non corretta e produce linterruzione immediata del processo di compilazione:

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 45

javac MiaPrimaApplicazione
Il processo di compilazione di una file sorgente avviene secondo uno schema temporale ben definito. In dettaglio; quando il compilatore ha bisogno della definizione di una classe java per procedere nel processo di generazione del Bytecode esegue una ricerca utilizzando le informazioni contenute nella variabile di ambiente CLASSPATH. Questo meccanismo di ricerca pu produrre tre tipi di risultati: 1. Trova soltanto un file .class : il compilatore lo utilizza; 2. Trova soltanto un file .java : il compilatore lo compila ed utilizza il file .class; 3. Trova entrambi i file .class e .java : il compilatore java verifica se il file .class sia aggiornato rispetto al relativo file .java comparando le date dei due file. Se il file .class aggiornato il compilatore lo utilizza e procede, altrimenti compila il file .java ed utilizza il file .class prodotto per procedere.

3.12 Opzioni standard del compilatore


Volendo fornire una regola generale, la sintassi del compilatore Java la seguente:

javac [ opzioni ] [ filesorgenti ] [ @listadifiles ]


Oltre al file da compilare definito dallopzione [filesorgenti], il compilatore java accetta dalla riga di comando una serie di parametri opzionali ([opzioni] ) necessari al programmatore a modificare le decisioni adottate dalla applicazione durante la fase di compilazione. Le opzioni pi comunemente utilizzate sono le seguenti: -classpath classpath : ridefinisce o sostituisce il valore della variabile dambiente CLASSPATH. Se nessuno dei due meccanismi viene utilizzato per comunicare al compilatore la posizione della classi necessarie alla produzione del Bytecode, verr utilizzata la cartella corrente; -d cartella: imposta la cartella di destinazione allinterno della quale verranno memorizzati i file contenenti il Bytecode delle classi compilate. Se la classe fa parte di un package3 Java, il compilatore salver i file con estensione .class in una sottocartella che rifletta la struttura del package e creata a partire dalla cartella specificata dallopzione. Ad esempio, se la classe da compilare si chiama esercizi.primo.MiaPrimaApplicazione e viene specificata la cartella c:\src utilizzando lopzione d, il file prodotto si chiamer:
3

Archivi di file Java compilati, di cui parleremo esaustivamente nei capitoli successivi.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 46

c:\src\esercizi\primo\MiaPrimaApplicazione.class
Se questa opzione non viene specificata, il compilatore salver i file contenenti il Bytecode allinterno della cartella corrente. Se lopzione -sourcepath non viene specificata, questa opzione viene utilizzata dal compilatore per trovare sia file .class che file .java. - sourcepath sourcepath: indica al compilatore la lista delle cartelle o dei package contenenti i file sorgenti necessari alla compilazione. Il formato di questo parametro rispecchia quello definito per la variabile di ambiente CLASSPATH.

3.13 Compilare elenchi di classi


Nel caso in cui sia necessario compilare un gran numero di file, il compilatore Java prevede la possibilit di specificare il nome di un file contenente la lista delle definizioni di classe scritte nellelenco una per ogni riga. Questa possibilit utile non solo a semplificare la vita al programmatore, ma raggira il problema delle limitazioni relative alla lunghezza della riga di comando sui sistemi operativi della Microsoft. Per utilizzare questa opzione sufficiente indicare al compilatore il nome del file contenente lelenco delle classi da compilare anteponendo il carattere @. Ad esempio se ElencoSorgenti il file in questione, la riga di comando sar la seguente: javac @ElencoSorgenti

3.14 Compilare una classe java


E finalmente arrivato il momento di compilare la nostra prima applicazione java. Per eseguire questo esempio possibile utilizzare qualsiasi editor di testo come notepad o wordpad per creare la definizione di classe il cui codice riportato di seguito.
public class MiaPrimaApplicazione { public static void main(String[] argv) { System.out.println("Finalmente la mia prima applicazione"); } }

Creiamo sul disco principale la cartella mattone e le due sottocartelle C:\mattone\src e C:\mattone\classes. Salviamo il file nella cartella c:\mattone\src\ facendo attenzione che il nome sia MiaPrimaApplicazione.java. Utilizzando il menu Avvia di windows eseguiamo il command prompt e posizioniamoci nella cartella in cui abbiamo salvato il file (Figura 22).

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 47

Figura 22:

Command Prompt

E arrivato il momento di compilare il file .java con il comando javac classpath .;.\; -d c:\mattone\classes MiaPrimaApplicazione.java dove lopzione classpath .;.\; indica che i file si trovano della cartella corrente e lopzione d c:\mattone\classes indica al compilatore di salvare il file contenente il Bytecode prodotto con il nome c:\mattone\classes\MiaPrimaApplicazione.class La figura 23 mostra i risultati della compilazione ed il contenuto della cartella c:\mattone\classes.

Figura 23:

Contenuto della cartella c:\mattone\classes

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 48

3.15 Linterprete Java


Per eseguire lapplicazione appena compilata necessario utilizzare linterprete Java o JVM che si pu trovare nella cartella c:\jdk1.3.1_02\bin\java. Utilizzando il command prompt di window precedentemente eseguito, posizioniamoci nella cartella c:\mattone\classes ed eseguiamo lapplicazione digitando il comando seguente:

java MiaPrimaApplicazione
La figura seguente riporta il risultato dellesecuzione dellapplicazione.

Figura 24:

Esecuzione della applicazione

Linterprete Java, come il compilatore, sensibile al formato dei caratteri ed quindi indicare il nome dellapplicazione da eseguire rispettando il formato, maiuscolo o minuscolo, dei caratteri che compongono il nome del file. A differenza dal compilatore, la JVM non richiede che sia specificata lestensione del file da eseguire: la riga di comando

java MiaPrimaApplicazione.class
produrr un errore segnalando al programmatore di non riuscire a trovare la classe MiaPrimaApplicazione/class (capiremo meglio in seguito il significato di questo messaggio di errore).

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 49

3.16 Sintassi dellinterprete java


La sintassi generale del comando java la seguente:

java [ opzioni ] class [ argomenti ... ]


Le opzioni consentono al programmatore di influenzare lambiente allinterno del quale lapplicazione verr eseguita. Le opzioni comunemente utilizzate sono le seguenti. -classpath classpath : ridefinisce o sostituisce il valore della variabile dambiente CLASSPATH. Se nessuno dei due meccanismi viene utilizzato per comunicare al compilatore la posizione della classi necessarie alla esecuzione del Bytecode, verr utilizzata la cartella corrente; -verbose:class : Visualizza le informazioni relative ad ogni classe caricata; -verbose:gc : Visualizza informazioni relative agli eventi scatenati dal garbage collector. -version : Visuali le informazioni relative alla versione del prodotto ed esce; -showversion : Visuali le informazioni relative alla versione del prodotto e prosegue; -? | -help : Visualizza le informazioni sulluso della applicazione ed esce; Linterprete java consente inoltre di impostare un elenco di argomenti che possono essere utilizzati dalla applicazione stessa durante lesecuzione. Nel prossimo esempio viene mostrato come trasmettere argomenti ad una applicazione java.
public class TestArgomenti { public static void main(String[] argv) { for(int i=0; i<argv.length; i++) System.out.println("Argomento "+ (i+1) +" = "+ argv[i] ); } }

Dopo aver compilato la classe, eseguendo lapplicazione utilizzando la riga di comando seguente:

java TestArgomenti primo secondo terzo quarto


il risultato prodotto sar il seguente:

Argomento Argomento Argomento Argomento

1 2 3 4

= = = =

primo secondo terzo quarto

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 50

3.17 Inserire commenti


Commentare correttamente il codice sorgente di unapplicazione importante primo, perch un codice ben commentato facilmente leggibile da chiunque; secondo, perch i commenti aiutano il programmatore ad evidenziare aspetti specifici di un algoritmo riducendo sensibilmente gli errori dovuti a distrazioni in fase di scrittura; tuttavia i commenti sono spesso insufficienti e talvolta assenti. Java consente di inserire commenti supportando entrambi i formati di C e C++: il primo include i commenti allinterno di blocchi di testo delineati dalle stringhe /* ed */, il secondo utilizza la stringa // per indicare una linea di documentazione. Nel prossimo esempio abbiamo modificato lapplicazione precedentemente scritta inserendo allinterno commenti utilizzando entrambe le forme descritte:
public class MiaPrimaApplicazione {

/* */

Il metodo main rappresenta il punto di ingresso allinterno della applicazione MiaPrimaApplicazione


public static void main(String[] argv) {

// Visualizza sullo schermo il messaggio // Finalmente la mia prima applicazione


System.out.println("Finalmente la mia prima applicazione"); } }

Al momento della generazione del Bytecode, il compilatore Java legge il codice sorgente ed elimina le righe od i blocchi di testo contenenti commenti. Il Bytecode prodotto sar identico a quello prodotto dalla stessa applicazione senza commenti.

3.18 Javadoc
Un errore che spesso si commette durante lo sviluppo di unapplicazione dimenticarne o tralasciarne volutamente la documentazione tecnica. Documentare il codice del prodotto che si sta sviluppando, richiede spesso giorni di lavoro e di conseguenza costi che pochi sono disposti a sostenere: il risultato un prodotto poco o addirittura completamente non mantenibile. Java fonde gli aspetti descrittivo e documentale, consentendo di utilizzare i commenti inseriti dal programmatore allinterno del codice sorgente dellapplicazione per produrre la documentazione tecnica necessaria ad una corretto rilascio di un prodotto. Oltre ai formati descritti nel paragrafo precedente, Java prevede un terzo formato che utilizza le stringhe /** e */ per delimitare blocchi di commenti

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 51

chiamati doc comments. I commenti che utilizzano questo formato sono utilizzati da uno strumento fornito con il Java SDK per generare automaticamente documentazione tecnica di una applicazione in formato ipertestuale. Questo strumento chiamato javadoc e pu essere trovato allinterno della cartella c:\jdk1.3.1_02\bin\javadoc. Javadoc esegue la scansione del codice sorgente, estrapola le dichiarazioni delle classi ed i doc comments e produce una serie di pagine HTML contenenti informazioni relative alla descrizione della classe, dei metodi e dei dati membri pi una completa rappresentazione della gerarchia delle classi e le relazioni tra loro. Oltre ai commenti nel formato descritto, questo strumento riconosce alcune etichette utili allinserimento, allinterno della documentazione prodotta, di informazioni aggiuntive come lautore di una classe o la versione. Le etichette sono precedute dal carattere @: le pi comuni sono elencate nella tabella seguente: Etichette Javadoc Descrizione Aggiunge una voce see also con un link ad una classe descritta dal parametro. Aggiunge una voce Author alla documentazione. Il parametro descrive il nome dellautore della classe. Aggiunge una voce Version alla documentazione. Il parametro contiene il numero di versione della classe. Aggiunge la descrizione ad un parametro di un metodo . Aggiunge la descrizione relativa al valore di ritorno di un metodo. Aggiunge una voce Since alla documentazione. Il parametro identifica generalmente il numero di versione della classe a partire dalla quale il nuovo requisito stato aggiunto. Imposta un requisito come obsoleto.

Sintassi @see riferimento

@author nome

Applicato Classi, metodi, variabili. Classe.

@version versione

Classe

@param parametro @return descrizione @since testo

Metodo Metodo

Classi, Metodi, variabili

@deprecated testo

Classi, Metodi, variabili

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 52

@throws classe descrizione

@exception classe descrizione

Aggiunge una voce Throws Metodi alla definizione di una metodo contenente il riferimento ad una eccezione generata e la sua descrizione. Vedi @throws. Metodi

Possiamo modificare la classe MiaPrimaApplicazione per esercitarci nelluso di questo strumento: comunque consigliabile modificare ulteriormente lesempio seguente con lo scopo di provare tutte le possibili combinazioni di etichette e verificarne il risultato prodotto.
/** * La classe <STRONG>MiaPrimaApplicazione</STRONG> contiene solo il metodo * main che produce un messaggio a video. * @version 0.1 */ public class MiaPrimaApplicazione { /** * * * * * * * * */ Il metodo main rappresenta il punto di ingresso all'interno della applicazione MiaPrimaApplicazione @since 0.1 <PRE> @param String[] : array contenente la lista dei parametri di input <br> passati per riga di comando. </PRE> @return void

public static void main(String[] argv) {

// Visualizza sullo schermo il messaggio // Finalmente la mia prima applicazione System.out.println("Finalmente la mia prima applicazione");
} }

Lesempio evidenzia un altro aspetto dei doc comment di Java: di fatto, possibile inserire parole chiavi di HTML per modificare il formato della presentazione dei documenti prodotti. Prima di eseguire il comando sulla classe appena modificata, creiamo la cartella C:\mattone\docs\MiaPrimaApplicazione, eseguiamo il "prompt dei comandi" di windows e posizioniamoci nella cartella contenente il file sorgente dellapplicazione da documentare. Eseguiamo il comando javadoc -d C:\mattone\docs\MiaPrimaApplicazione MiaPrimaApplicazione.java come mostrato nella prossima figura.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 53

Figura 25:

Esecuzione del comando javadoc

Al termine della esecuzione del comando aprite il file C:\mattone\docs\MiaPrimaApplicazione\index.html per verificarne i contenuti.

3.19 Sintassi del comando javadoc


Javadoc accetta parametri tramite la riga di comando secondo la sintassi generale:

javadoc [ opzioni] [ packages ] [ filesorgenti ] [ @elenco ]


In dettaglio: le opzioni consentono di modificare le impostazioni standard dello strumento, packages rappresenta un elenco di nomi validi di packages che si desidera documentare separati da uno spazio (ad esempio java.lang o java.math), filesorgenti una la lista di file sorgenti da documentare nella forma nomefile.java separati dal carattere spazio, @elenco rappresenta il nome di un file contenente una lista di nominativi di file sorgenti o di packages, uno per ogni riga. Le opzioni pi comuni sono elencate di seguito: -classpath classpath : ridefinisce o sostituisce il valore della variabile dambiente CLASSPATH. Se nessuno dei due meccanismi viene utilizzato per comunicare al compilatore la posizione della classi necessarie alla esecuzione del Bytecode, verr utilizzata la cartella corrente; -windowtitle titolo: imposta il titolo della documentazione HTML prodotta;

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 54

-d cartella: imposta la cartella di destinazione allinterno della quale verranno memorizzati i documenti prodotti.

3.20 Java Hotspot


Il maggior punto di forza di Java, la portabilit del codice tramite Bytecode, anche il suo tallone dAchille. In altre parole, essendo Java un linguaggio tradotto (ovvero necessita di una virtual machine per essere eseguito), le applicazioni sviluppate con questa tecnologia hanno prestazioni inferiori rispetto ad altri linguaggi di programmazione come il C++. Finalmente la SUN sembra aver colmato la lacuna rilasciando, insieme al nuovo Java SDK 1.3.1, Hotspot, una JVM che, utilizzando tecniche avanzate dottimizzazione del codice durante lesecuzione, affiancate da una pi efficiente gestione della memoria e dal supporto nativo per i thread, fornisce prestazioni paragonabili a quelle di un linguaggio non tradotto. Nella prossima figura viene schematizzata larchitettura di Hotspot:

Figura 26:

Architettura di Hotspot

Dallo schema appare subito evidente la prima grande differenza rispetto alle versioni precedenti: la presenza di due compilatori in grado di ottimizzare il codice a seconda che lapplicazione che si sta compilando sia di tipo client o server. Nel primo caso, il Bytecode ottimizzato per ridurre i tempi di caricamento e di avvio dellapplicazione ed ottimizzare luso della memoria; nel secondo caso, privilegiata la velocit desecuzione e la robustezza dellapplicazione a discapito delle risorse di sistema.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 55

Per selezionare il tipo di ottimizzazione da effettuare, il compilatore java accetta le due opzioni -client e server da inviare dalla riga di comando dellapplicazione.

3.21 Il nuovo modello di memoria


Il nuovo modello per la gestione della memoria di Hotspot, inteso a rendere pi efficiente laccesso alle istanze degli oggetti di cui lapplicazione fa uso, ed a minimizzare la quantit di memoria necessaria al funzionamento della JVM che dovr eseguire il Bytecode. Il primo cambiamento stato introdotto nelle modalit con cui la JVM tiene traccia degli oggetti in memoria: un sistema di puntatori diretti alle classi in uso garantisce tempi di accesso paragonabili a quelli del linguaggio C. Il secondo riguarda invece limplementazione dei descrittori degli oggetti in memoria o object header. Un descrittore di oggetto contiene tutte le informazioni relative alla sua identit, allo stato nel garbage collector, al codice hash: di fatto, a differenza della vecchia JVM che riservava tre parole macchina (32*3 bit) per il descrittore di un oggetto, Hotpost ne utilizza due per gli oggetti e tre solo per gli array, diminuendo significativamente la quantit di memoria utilizzata (la diminuzione media stata stimata di circa 8% rispetto alla vecchia JVM).

3.22 Supporto nativo per i thread


Per rendere pi efficiente la gestione della concorrenza tra thread, Hotspot demanda al sistema operativo che ospita lapplicazione la responsabilit dellesecuzione di questi processi logici, affinch lapplicazione ne tragga benefici in fatto di prestazioni. Per meglio comprendere quanto affermato, soffermiamoci qualche istante sul concetto di concorrenza tra processi logici. Da punto di vista dellutente, un thread un processo eseguito parallelamente ad altri; dal punto di vista del calcolatore, non esistono processi paralleli. Salvo che non possediate una macchina costosissima in grado di eseguire calcoli in parallelo, i processi si alternano tra loro con una velocit tale da dare limpressione di essere eseguiti contemporaneamente. Questo meccanismo detto di concorrenza: i processi attivi concorrono tra loro alluso del processore. Per realizzare il meccanismo di concorrenza tra processi, il sistema operativo organizza i processi attivi in una struttura dati detta coda Round Robin4 (la figura 27 ne offre uno schema semplificato), da cui: 1. Il processo P5 possiede il controllo del processore della macchina per un periodo di tempo prestabilito;
4

Round Robin il nome dellalgoritmo pi frequentemente utilizzato per realizzare la concorrenza logica tra processi o thread.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 56

2. Alla scadenza del periodo di tempo prestabilito, il processo P5 viene inserito nella coda ed il processo P4 viene estratto dalla coda per assumere il controllo del processore; 3. Alla scadenza del periodo di tempo prestabilito, il processo P4 viene inserito nella coda ed il processo P3 viene estratto dalla coda per assumere il controllo del processore ecc. ecc..

Figura 27:

Concorrenza logica tra processi

Il sistema operativo specializzato nella gestione della concorrenza tra processi e thread, ed supportato da componenti elettroniche appositamente progettate per aumentare le prestazioni questi algoritmi. Quando un processo prende il controllo del processore per proseguire nella esecuzione delle proprie istruzioni, necessita di una serie di dati (valori dei registri di sistema, riferimenti allultima istruzione eseguita, ecc. ecc.) salvati in una struttura in memoria sotto il diretto controllo del processore chiamata stack di sistema il cui funzionamento schematizzato nella figura 28: 1. Il processo P3 prende il controllo del processore: il sistema operativo carica i dati di P3 nelle strutture di sistema ed il processo prosegue lesecuzione per un intervallo di tempo prestabilito; 2. Terminato lintervallo di tempo, il processo P3 rilascia il processore: il sistema operativo inserisce il processo in coda, estrae i dati relativi a

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 57

P3 dalle strutture di sistema e li salva per poterli recuperare successivamente; 3. Il processo P2 prende il controllo del processore: il sistema operativo carica i dati di P2 precedentemente salvati ed il processo prosegue lesecuzione per un intervallo di tempo prestabilito.

Figura 28:

Stack di sistema

Questo meccanismo a carico del sistema operativo, e rappresenta un collo di bottiglia in grado di far degradare notevolmente le prestazioni di un processo in esecuzione. Come i processi, anche i thread sono affetti da questo problema. Anche in questo caso lelettronica corre in aiuto del sistema operativo mettendo a disposizione componenti elettroniche appositamente progettate. Concludendo, demandare al sistema operativo la gestione dei thread, non pu che portare benefici incrementando notevolmente le prestazioni della JVM; al contrario la JVM si sarebbe dovuta far carico della implementazione e della gestione delle strutture necessarie a gestire la concorrenza tra questi processi.

3.23 Il garbage Collector di Hotspot


Il garbage collector di Hotspot unaltra grande modica introdotta con la nuova JVM. Le caratteristiche del nuovo gestore della memoria sono tante e complesse; non essendo possibile descriverle tutte, esamineremo le pi utili allo scopo che si propone questo libro.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 58

Parlando del garbage collector, abbiamo gi evidenziato alcuni limiti nel gestire e rilasciare la memoria nei casi in cui sia difficile determinare se un oggetto sia stato effettivamente rilasciato dallapplicazione o no. Questo tipo di gestori della memoria sono comunemente detti conservativi o parzialmente accurati, ed hanno il difetto di non riuscire a rilasciare completamente la memoria allocata da unapplicazione dando origine al problema del memory leak. Per essere pi precisi, un garbage collector di tipo conservativo, non conosce esattamente dove siano posizionati tutti i puntatori agli oggetti attivi in memoria e di conseguenza, deve conservare tutti quegli oggetti che sembrano essere riferiti da altri: ad esempio potrebbe confondere una variabile intera con un puntatore e di conseguenza non rilasciare un oggetto che in realt potrebbe essere rimosso. Questa mancanza di accuratezza nel tener traccia dei puntatori agli oggetti allocati rende impossibile spostare gli oggetti in memoria causando un altro problema che affligge i garbage collector conservativi: la frammentazione della memoria. Di fatto, il ciclo di esecuzione di una applicazione prevede che gli oggetti possano essere allocati o rimossi dalla memoria anche con una certa frequenza.

Figura 29:

Allocazione della memoria per gli oggetti A,B,C

Supponiamo che ad un certo istante del ciclo di esecuzione di una applicazione risultino allocati gli oggetti A,B,C (Figura 29), e che il flusso delle

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 59

operazioni sia tale da rilasciare successivamente tutti i puntatori alloggetto B: il garbage collector rimuover loggetto dalla memoria rilasciando le risorse utilizzate (Figura 30).

Figura 30:

Loggetto B rimosso dalla memoria

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 60

Figura 31:

Frammentazione della memoria

Successivamente lapplicazione richiede risorse per allocare un nuovo oggetto D le cui dimensioni sono maggiori di quelle delloggetto appena rimosso: dal momento che gli oggetti non possono essere spostati in modo da occupare porzioni contigue di memoria, il nuovo oggetto potrebbe essere allocato utilizzando lo schema riportato nella figura 31 in cui vengono riservate due porzioni di memoria non contigue frammentando loggetto in pi parti. La frammentazione della memoria, nel caso di applicazioni molto complesse in cui esiste un gran numero di oggetti che interagiscono tra loro, pu causare un degrado nelle prestazioni tale da necessitare il riavvio dellapplicazione. A differenza degli altri, il garbage collector di Hotspot totalmente accurato. Una garbage collector di questo tipo conosce esattamente la situazione dei puntatori agli oggetti in ogni istante del ciclo di esecuzione della applicazione: questa caratteristica garantisce la corretta gestione delle risorse allocate agli oggetti e soprattutto ne consente la rilocazione evitando il problema della frammentazione.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 61

4 LA SINTASSI DI JAVA
4.1 Introduzione

La sintassi del linguaggio Java eredita in parte quella di C e C++ per la definizione di variabili e strutture complesse, ed introduce il concetto di variabili di dimensioni fisse ovvero non dipendenti dalla piattaforma. Nei prossimi paragrafi studieremo cosa sono le variabili, come utilizzarle e come implementare strutture dati pi complesse mediante luso degli array. Infine, introdurremo le regole sintattiche e semantiche specifiche del linguaggio necessarie alla comprensione del capitolo successivo.

4.2

Variabili

Per scrivere applicazioni Java, un programmatore deve poter creare oggetti. Per far questo, necessario poterne rappresentare i dati. Il linguaggio Java mette a disposizione del programmatore una serie di "tipi semplici" o "primitivi", utili alla definizione di oggetti pi complessi. Per garantire la portabilit del Bytecode da una piattaforma ad unaltra, Java fissa le dimensioni di ogni dato primitivo. Queste dimensioni sono quindi definite e non variano se passiamo da un ambiente ad un altro, cosa che non succede con gli altri linguaggi di programmazione. I tipi numerici sono da considerarsi tutti con segno. La tabella a seguire schematizza i dati primitivi messi a disposizione da Java. Variabili primitive Java Dimensione Val. minimo 1 bit 16 bit Unicode 0 8 bit -128 16 bit -2 15 32 bit -2 31 64 bit -2 63 32 bit IEEE754 64 bit IEEE754 -

Primitiva boolean char byte short int long float double void

Val.Massimo Unicode 2 16 - 1 +127 +2 15 - 1 +2 31 - 1 +2 63 - 1 IEEE754 IEEE754 -

La dichiarazione di un dato primitivo in Java ha la seguente forma:

tipo identificatore;
dove tipo uno tra i tipi descritti nella prima colonna della tabella e definisce il dato che la variabile dovr contenere, e lidentificatore rappresenta

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 62

il nome della variabile. Il nome di una variabile pu contenere caratteri alfanumerici, ma deve iniziare necessariamente con una lettera. E possibile creare pi di una variabile dello stesso tipo utilizzando una virgola per separare tra loro i nomi delle variabili:

tipo identificatore1, identificatore2, . ;


Per convenzione lidentificatore di una variabile Java deve iniziare con la lettera minuscola. Lidentificatore di una variabile ci consente di accedere al valore del dato da qualunque punto del codice esso sia visibile e poich ha valore puramente mnemonico, bene che esso sia il pi possibile descrittivo per consentire di identificare con poco sforzo il contesto applicativo allinterno del quale il dato andr utilizzato. Ad esempio le righe di codice:
int moltiplicatore = 10; int moltiplicando = 20; int risultato; risultato = moltiplicando* moltiplicatore;

rappresentano in maniera comprensibile una operazione di moltiplicazione tra due variabili di tipo intero.

4.3

Inizializzazione di una variabile

Durante lesecuzione del Bytecode dellapplicazione, se la JVM trova la dichiarazione di una variabile, riserva spazio sulla memoria del computer per poterne rappresentare il valore, e ne associa lindirizzo fisico allidentificatore per accedere al dato; di conseguenza, ogni variabile Java richiede che, al momento della dichiarazione, le sia assegnato un valore iniziale. Java assegna ad ogni variabile un valore prestabilito: la tabella riassume il valore iniziale assegnato dalla JVM distinguendo secondo del tipo primitivo rappresentato. Valori prestabiliti per le primitive Tipo primitivo Valore assegnato dalla JVM boolean false Char \u0000 Byte 0 Short 0 Int 0 Long 0L Float 0.0f Double 0.0

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 63

In alternativa, come per il linguaggio C e C++, linizializzazione di una variabile Java pu essere effettuata dal programmatore direttamente al momento della sua dichiarazione. La sintassi la seguente:

tipo identificatore = valore;


dove valore rappresenta un valore legale per il tipo di variabile dichiarata, ed = rappresenta loperatore di assegnamento. Ad esempio possibile dichiarare ed inizializzare una variale intera con una sola riga di codice.
int variabileintera = 100;

4.4

Variabili char e codifica del testo

Parlando della storia del linguaggio Java abbiamo evidenziato come la tecnologia proposta dalla SUN abbia avuto come campo principale di applicazione, Internet. Data la sua natura, era indispensabile dotarlo delle caratteristiche fondamentali a rispondere alle esigenze di internazionalizzazione proprie della pi vasta rete del mondo. La codifica ASCII, largamente utilizzata dalla maggior parte dei linguaggi di programmazione, utilizza una codifica ad otto bit idonea a rappresentare al massimo 28 =256 caratteri e quindi non adatta allo scopo. Fu quindi adottato lo standard internazionale UNICODE, che utilizza una codifica a sedici bit studiata per rappresentare 216= 65536 caratteri di cui i primi 256 (UNICODE 0-255) corrispondono ai caratteri ASCII. Per questo motivo, le variabili Java di tipo char (carattere) utilizzano sedici bit per rappresentare il valore del dato, e le stringhe a loro volta sono rappresentate come sequenze di caratteri a sedici bit. Questa caratteristica di Java rappresenta per una limitazione per tutti i sistemi informativi non in grado di trattare caratteri codificati secondo lo standard UNICODE. Per risolvere il problema, Java utilizza la forma detta sequenza di escape

\uxxxx

(xxxx=sequenza di massimo 4 cifre esadecimali)

per produrre loutput dei caratteri e delle stringhe. La sequenza di escape descritta indica una carattere UNICODE decodificato in ASCII.

4.5

Variabili final

A differenza di altri linguaggi di programmazione, Java non consente la definizione di costanti. Questo aspetto del linguaggio non da considerarsi

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 64

una limitazione perch possibile simulare una costante utilizzando il modificatore final. Le variabili dichiarate final si comportano come una costante e richiedono che sia assegnato il valore al momento della dichiarazione, utilizzando loperatore di assegnamento:

final tipo identificatore = valore;


Le variabili di questo tipo vengono inizializzate solo una volta al momento della dichiarazione e qualsiasi altro tentativo di assegnamento si risolver in un errore di compilazione.

4.6

Operatori

Una volta definito, un oggetto deve poter manipolare i dati. Java mette a disposizione del programmatore una serie di operatori utili allo scopo. Gli operatori Java sono elencati nella tabella seguente, ordinati secondo lordine di precedenza: dal pi alto al pi basso. Operatori Java Funzioni Aritmetiche unarie e booleane Aritmetiche Addizione sottrazione Shift di bit (o concatenazione) e

Operatore ++ -- + * + / %

<< >> >>> (tipo) < <= > >= instanceof == != & ^ | && || !

Operatore di cast Comparazione Uguaglianza e disuguaglianza (bit a bit) AND (bit a bit) XOR (bit a bit) OR AND Logico OR Logico NOT Logico

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 65

expr ? expr :expr = *= /+ %= += -= <<= >>>= n &= ^= |=

Condizione a tre Assegnamento e di combinazione

Gran parte degli operatori Java appartengono allinsieme degli operatori del linguaggio C, cui ne sono stati aggiunti nuovi a supporto delle caratteristiche proprie del linguaggio della SUN. Gli operatori elencati nella tabella funzionano solamente con dati primitivi a parte gli operatori !=, == e = che hanno effetto anche se gli operandi sono rappresentati da oggetti. Inoltre, la classe String utilizza gli operatori + e += per operazioni di concatenazione. Come in C, gli operatori di uguaglianza e disuguaglianza sono == (uguale a) e != (diverso da) di cui si nota la disuguaglianza con gli stessi operatori come definiti dallalgebra: = e <>: luso delloperatore digrafo5 == necessario dal momento che il carattere = utilizzato esclusivamente come operatore di assegnamento, e loperatore != compare in questa forma per consistenza con la definizione delloperatore logico ! (NOT). Anche gli operatori bit a bit e quelli logici derivano dal linguaggio C e nonostante le analogie in comune, sono completamente sconnessi tra loro. Ad esempio loperatore & utilizzato per combinare due interi operando bit per bit e loperatore && utilizzato per eseguire loperazione di AND logico tra due espressioni booleane: quindi, mentre (1011 & 1001) restituir 1001, lespressione (a == a && b != b) restituir false. La differenza con il linguaggio C sta nel fatto che gli operatori logici in Java sono di tipo short-circuit ovvero, se il lato sinistro di unespressione fornisce informazioni sufficienti a completare lintera operazione, il lato destro dellespressione non sar valutato. Per esempio, si consideri lespressione booleana ( a == a ) || ( b == c ) La valutazione del lato sinistro dellespressione fornisce valore true . Dal momento che si tratta di una operazione di OR logico, non c motivo a proseguire nella valutazione del lato sinistro della espressione, cos che b non sar mai comparato con c. Questo meccanismo allapparenza poco utile, si rivela invece estremamente valido nei casi di chiamate a funzioni complesse per controllare la complessit della applicazione. Se infatti scriviamo una chiamata a funzione nel modo seguente : ( A == B ) && ( f() == 2 )

Gli operatori digrafi sono operatori formati dalla combinazione di due simboli. I due simboli debbono essere adiacenti ed ordinati.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 66

dove f() una funzione arbitrariamente complessa, f() non sar eseguita se A non uguale a B. Sempre dal linguaggio C, Java eredita gli operatori unari di incremento e decremento ++ e --: i++ equivale a i=i+1 i-- equivale a i=i-1. Infine gli operatori di combinazione, combinano un assegnamento con unoperazione aritmetica: i*=2 equivale ad i=i*2. Questi operatori anche se semplificano la scrittura del codice lo rendono di difficile comprensione, per questo motivo non sono comunemente utilizzati.

4.7

Operatori di assegnamento

Loperatore di assegnamento = consente al programmatore, una volta definita una variabile, di assegnarle un valore. La sintassi da utilizzare la seguente:

tipo identificatore = espressione;


dove espressione rappresenta una qualsiasi espressione che produce un valore compatibile con il tipo definito da tipo, e identificatore rappresenta la variabile che conterr il risultato. Tornando alla tabella definita nel paragrafo Operatori, vediamo che loperatore di assegnamento ha la priorit pi bassa rispetto a tutti gli altri. La riga di codice Java produrr quindi la valutazione della espressione ed infine lassegnamento del risultato alla variabile. Ad esempio:
int risultato = 5+10;

Esegue lespressione alla destra delloperatore e ne assegna il risultato (15) a risultato.


int risultato = 5;

Oltre alloperatore = Java mette a disposizione del programmatore una serie di operatori di assegnamento di tipo shortcut (in italiano scorciatoia), definiti nella prossima tabella. Questi operatori combinano un operatore aritmetico o logico con loperatore di assegnamento. Operatori di assegnamento shortcut

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 67

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

Utilizzo sx +=dx sx -=dx sx *=dx sx /=dx sx %=dx sx &=dx sx |=dx sx ^=dx sx <<=dx sx >>=dx sx >>>=dx

Equivalente a sx = sx + dx; sx = sx - dx; sx = sx * dx; sx = sx / dx; sx = sx % dx; sx = sx & dx; sx = sx | dx; sx = sx ^ dx; sx = sx << dx; sx = sx >> dx; sx = sx >>> dx;

4.8

Operatore di cast

Loperatore di cast tra tipi primitivi consente di promuovere, durante lesecuzione di unapplicazione, un tipo numerico in uno nuovo. La sintassi delloperatore la seguente:

(nuovo tipo) identificatore|espressione


dove nuovo tipo rappresenta il tipo dopo la conversione, identificatore una qualsiasi variabile numerica o carattere, espressione unespressione che produca un valore numerico. Prima di effettuare unoperazione di cast tra tipi primitivi, necessario assicurarsi che non ci siano eventuali errori di conversione. Di fatto, il cast di un tipo numerico in un altro con minor precisione, ha come effetto di modificare il valore del tipo con precisione maggiore affinch possa essere memorizzato in quello con precisione minore: ad esempio, il tipo long pu rappresentare tutti i numeri interi compresi nellintervallo (-2 63 , +2 63 1), mentre il tipo int quelli compresi nellintervallo (-2 32 , +2 32 1). La prossima applicazione di esempio effettua tre operazioni di cast: il primo tra una variabile di tipo long in una di tipo int evidenziando la perdita del valore del tipo promosso; il secondo di un tipo int in un tipo long; il terzo, di un tipo char in un tipo int memorizzando nella variabile intera il codice UNICODE del carattere A.
public class Cast { public static void main(String[] argv) { long tipolong; int tipoint; char tipochar;

//Cast di un tipo long in un tipo int tipolong = Long.MAX_VALUE; tipoint = (int) tipolong;

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 68

System.out.println("La variabile di tipo long vale: "+tipolong+", La variabile di tipo int vale: "+tipoint);

//Cast di un tipo int in un tipo long tipoint = Integer.MAX_VALUE; tipolong = (long) tipoint; System.out.println("La variabile di tipo long vale: "+tipolong+", La variabile di tipo int vale: "+tipoint); //Cast di un tipo char in un tipo int tipochar='A'; tipoint = (int)tipochar; System.out.println("La variabile di tipo char vale: "+tipochar+", La variabile di tipo int vale: "+tipoint); } }

Il risultato della esecuzione del codice riportato di seguito.


La variabile di tipo long vale: 9223372036854775807, La variabile di tipo int vale: -1 La variabile di tipo long vale: 2147483647, La variabile di tipo int vale: 2147483647 La variabile di tipo char vale: A, La variabile di tipo int vale: 65

4.9

Operatori aritmetici

Java supporta tutti i pi comuni operatori aritmetici (somma, sottrazione, moltiplicazione, divisione e modulo), in aggiunta fornisce una serie di operatori che semplificano la vita al programmatore consentendogli, in alcuni casi, di ridurre la quantit di codice da scrivere. Gli operatori aritmetici sono suddivisi in due classi: operatori binari ed operatori unari. Gli operatori binari (ovvero operatori che necessitano di due operandi) sono cinque e sono schematizzati nella tabella seguente: Operatori Aritmetici Binari Operatore Utilizzo Descrizione + res=sx + dx res = somma algebrica di dx ed sx res= sx - dx res = sottrazione algebrica di dx da sx * res= sx * dx res = moltiplicazione algebrica tra sx e dx / res= sx / dx res = divisione algebrica di sx con dx % res= sx % dx res = resto della divisione tra sx e dx Esaminiamo ora le seguenti righe di codice:
int sx = 1500; long dx = 1.000.000.000 ??? res; res = sx * dx;

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 69

Nasce il problema di rappresentare correttamente la variabile res affinch si possa assegnarle il risultato delloperazione. Essendo 1.500.000.000.000 troppo grande perch sia assegnato ad una variabile di tipo int, sar necessario utilizzarne una di un tipo in grado di contenere correttamente il valore prodotto. Il codice funzioner perfettamente se riscritto nel modo seguente:
int sx = 1500; long dx = 1.000.000.000 long res; res = sx * dx;

Quello che notiamo che, se i due operandi non rappresentano uno stesso tipo, nel nostro caso un tipo int ed un tipo long, Java prima di valutare lespressione trasforma implicitamente il tipo int in long e produce un valore di tipo long. Questo processo di conversione implicita dei tipi, effettuato da Java seguendo alcune regole ben precise:

Il risultato di una espressione aritmetica di tipo long se almeno un operando di tipo long e nessun operando di tipo float o double; Il risultato di una espressione aritmetica di tipo int se entrambi gli operandi sono di tipo int; Il risultato di una espressione aritmetica di tipo float se almeno un operando di tipo float e nessun operando di tipo double; Il risultato di una espressione aritmetica di tipo double se almeno un operando di tipo double;

Gli operatori + e -, oltre ad avere una forma binaria hanno una forma unaria il cui significato definito dalle seguenti regole:

+op : trasforma loperando op in un tipo int se dichiarato di tipo char, byte o short; -op : restituisce la negazione aritmetica di op.

Non resta che parlare degli operatori aritmetici di tipo shortcut (scorciatoia). Questo tipo di operatori consente lincremento o il decremento di uno come riassunto nella tabella: Operatori shortcut Forma shortcut Forma estesa int i=0; int i=0; int j; int j; j=i++; j=i; i=i+1; int i=1; int i=1;

Risultato i=1 j=0

i=0

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 70

int j; j=i--; int i=0; int j; j=++i; int i=1; int j; j=--i;

int j; j=i; i=i-1; int i=0; int j; i=i+1; j=i; int i=1; int j; i=i-1; j=i;

j=1

i=1 j=1

i=0 j=0

4.10 Operatori relazionali


Gli operatori relazionali sono detti tali perch si riferiscono alle possibili relazioni tra valori, producendo un risultato di verit o falsit come conseguenza del confronto. A differenza dei linguaggi C e C++ in cui vero o falso corrispondono rispettivamente con i valori 0 e 0 restituiti da unespressione, Java li identifica rispettivamente con i valori true e false, detti booleani e rappresentati da variabili di tipo boolean. Nella tabella seguente sono riassunti gli operatori relazionali ed il loro significato. Operatori Relazionali Operatore Utilizzo Descrizione > res=sx > dx res = true se e solo se sx maggiore di dx. >= res= sx >= dx res = true se e solo se sx maggiore o uguale di dx. < res= sx < dx res = true se e solo se sx minore di dx. <= res= sx <= dx res = true se e solo se sx minore o uguale di dx. != res= sx != dx res = true se e solo se sx diverso da dx.

4.11 Operatori logici


Gli operatori logici consentono di effettuare operazioni logiche su operandi di tipo booleano, ossia operandi che prendono solo valori true o false. Questi operatori sono quattro e sono riassunti nella tabella seguente. Operatori Condizionali Operatore Utilizzo Descrizione && res=sx && dx AND : res = true se e solo se sx e dx vagono entrambi true, false altrimenti.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 71

|| ! ^

res= sx || dx res= ! sx res= sx ^ dx

OR : res = true se e solo se almeno uno tra sx e dx vale true, false altrimenti. NOT : res = true se e solo se sx vale false, false altrimenti. XOR : res = true se e solo se uno solo dei due operandi vale true, false altrimenti.

Tutti i possibili valori booleani prodotti dagli operatori descritti possono essere schematizzati mediante le tabelle di verit. Le tabelle di verit forniscono, per ogni operatore, tutti i possibili risultati secondo il valore degli operandi. AND ( && ) dx res true true false false true false false false OR ( || ) dx res true true false true true true false false

sx true true false false

sx true true false false

NOT ( ! ) sx res true false false true

sx true true false false

XOR ( ^ ) dx res true false false true true true false false

Ricapitolando:

Loperatore && un operatore binario e restituisce vero soltanto se entrambi gli operandi sono veri; Loperatore || un operatore binario e restituisce vero se almeno uno dei due operandi vero; Loperatore ! un operatore unario che afferma la negazione dellopernado; Loperatore ^ un operatore binario e restituisce vero se solo uno dei due operandi vero;

Come vedremo in seguito, gli operatori relazionali, agendo assieme agli operatori logici, forniscono uno strumento di programmazione molto efficace.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 72

4.12 Operatori logici e di shift bit a bit


Gli operatori di shift bit a bit consentono di manipolare tipi primitivi spostandone i bit verso sinistra o verso destra, secondo le regole definite nella tabella seguente: Operatori di shift bit a bit Operatore Utilizzo Descrizione >> sx >> dx Sposta i bit di sx verso destra di un numero di posizioni come stabilito da dx. << sx << dx Sposta i bit di sx verso sinistra di un numero di posizioni come stabilito da dx. >>> sx >>> dx Sposta i bit di sx verso sinistra di un numero di posizioni come stabilito da dx, ove dx da considerarsi un intero senza segno. Consideriamo il seguente esempio:
public class ProdottoDivisione { public static void main(String args[]) { int i = 100; int j=i; //Applicazione dello shift bit a bit verso destra i=i >> 1; System.out.println("Il risultato di "+j+" >> 1 e': " + i); j=i; i=i >> 1; System.out.println("Il risultato di "+j+" >> 1 e': " + i); j=i; i=i >> 1; System.out.println("Il risultato di "+j+" >> 1 e': " + i); //Applicazione dello shift bit a bit verso sinistra i=100; j=i; i=i << 1; System.out.println("Il risultato di "+j+" << 1 e': " + i); j=i; i=i << 1; System.out.println("Il risultato di "+j+" << 1 e': " + i); j=i; i=i << 1; System.out.println("Il risultato di "+j+" << 1 e': " + i); } }

Lesecuzione della applicazione produrr quanto segue:


Il risultato di 100 >> 1 e': 50 Il risultato di 50 >> 1 e': 25 Il risultato di 25 >> 1 e': 12

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 73

Il risultato di 100 << 1 e': 200 Il risultato di 200 << 1 e': 400 Il risultato di 400 << 1 e': 800

Poich la rappresentazione binaria del numero decimale 100 01100100, lo spostamento dei bit verso destra di una posizione, produrr come risultato il numero binario 00110010 che corrisponde al valore 50 decimale; viceversa, lo spostamento dei bit verso sinistra di una posizione, produrr come risultato il numero binario 11001000 che corrisponde al valore 200 decimale. Appare evidente che le operazioni di shift verso destra o verso sinistra di 1 posizione dei bit di un numero intero, corrispondono rispettivamente alla divisione o moltiplicazione di un numero intero per 2. Ci che rende particolari questi operatori, la velocit con cui sono eseguiti rispetto alle normali operazioni di prodotto o divisione: di conseguenza, questa caratteristica li rende particolarmente appetibili per sviluppare applicazioni che necessitano di fare migliaia di queste operazioni in tempo reale. Oltre ad operatori di shift, Java consente di eseguire operazioni logiche su tipi primitivi operando come nel caso precedente sulla loro rappresentazione binaria. Operatori logici bit a bit Operatore Utilizzo Descrizione & res = sx & dx AND bit a bit | res = sx | dx OR bit a bit ^ res = sx ^ dx XOR bit a bit ~ res = ~sx COMPLEMENTO A UNO bit a bit AND ( & ) dx (bit) 1 0 1 0 OR ( | ) dx (bit) 1 0 1 0

sx (bit) 1 1 0 0

res (bit) 1 0 0 0

sx (bit) 1 1 0 0

res (bit) 1 1 1 0

COMPLEMENTO ( ~ ) sx (bit) res (bit) 1 0 0 1

sx (bit) 1 1 0 0

XOR ( ^ ) dx (bit) 1 0 1 0

res (bit) 0 1 1 0

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 74

Nelle tabelle precedenti sono riportati tutti i possibili risultati prodotti dallapplicazione degli operatori nella tabella precedente. Tutte le combinazioni sono state effettuate considerando un singolo bit degli operandi. Il prossimo un esempio di applicazione degli operatori logici bit a bit a variabili di tipo long:
public class OperatoriLogiciBitaBit { public static void main(String[] args) { long sinistro = 100; long destro = 125; long risultato = sinistro & destro; System.out.println("100 & 125 = "+risultato); risultato = sinistro | destro; System.out.println("100 | 125 = "+risultato); risultato = sinistro ^ destro; System.out.println("100 ^ 125 = "+risultato); risultato = ~sinistro; System.out.println("~ 125 = "+risultato); } }

Il risultato della esecuzione della applicazione sar il seguente:


100 & 125 = 100 100 | 125 = 125 100 ^ 125 = 25 ~125 = -101

Dal momento che la rappresentazione binaria di sx e dx rispettivamente: sx e dx in binario variabile decimale binario sx 100 01100100 dx 125 01111101 il significato dei risultati prodotti dalla applicazione OperatoriLogiciBitaBit schematizzato nella successiva tabella.

OperatoriLogiciBitaBit operatore sinistro destro risultato & 01100100 01111101 01100100 | 01100100 01111101 01111101 ^ 01100100 01111101 00011001 ~ 01100100 ------------ 10011011

decimale 100 125 25 155

Nella prossima applicazione, utilizziamo gli operatori logici bit a bit per convertire un carattere minuscolo nel relativo carattere maiuscolo. I caratteri

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 75

da convertire vengono trasmessi alla applicazione attraverso la riga di comando della Java Virtual Machine.
public class Maiuscolo { public static void main(String[] args) { int minuscolo = args[0].charAt(0); int maiuscolo = minuscolo & 223; System.out.println("Prima della conversione il carattere e: '"+(char)minuscolo+"' ed il suo codice UNICODE e': "+minuscolo); System.out.println("Dopo la conversione il il carattere e: '"+(char)maiuscolo+"' ed il suo codice UNICODE e':"+maiuscolo); } }

Eseguiamo lapplicazione passando il carattere a come parametro di input. Il risultato che otterremo sar:
java ConvertiInMaiuscolo a Prima della conversione il carattere e: a ed il suo codice UNICODE e': 97 Dopo la conversione il il carattere e: A ed il suo codice UNICODE e': 65

Per effettuare la conversione dei carattere nel relativo carattere minuscolo, abbiamo utilizzato loperatore & per mettere a zero il sesto bit della variabile di tipo int, minuscolo , che contiene il codice UNICODE del carattere che vogliamo convertire. Il carattere a rappresentato dal codice UNICODE 97, A dal codice UNICODE 65 quindi, per convertire il carattere minuscolo nel rispettivo maiuscolo, necessario sottrarre 32 al codice UNICODE del primo. La stessa regola vale per tutti i caratteri dellalfabeto inglese: UNICODE( a ) - 32 = UNICODE( A ) UNICODE( b ) - 32 = UNICODE( B ) UNICODE( c ) - 32 = UNICODE( C ) : UNICODE( z ) - 32 = UNICODE( Z ) Dal momento che la rappresentazione binaria del numero 32 : 0000000000100000, ovvio che impostando a 0 il sesto bit sottraiamo 32 al valore della variabile. 97 =
bit

0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

223 = 0 0 0 0 0 0 0 0 1 1 0 1 1 1 1 1
bit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

65=97&223= 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1
bit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 76

4.13 Array
Java, come il C, consente laggregazione dei tipi base e degli oggetti mettendo a disposizione del programmatore gli array. In altre parole, mediante gli array possibile creare collezioni di entit dette tipi base dellarray: i tipi base di un array possono essere oggetti o tipi primitivi ed il loro numero chiamato length o lunghezza dellarray. La sintassi per la dichiarazione di una variabile di tipo array espressa dalla regola seguente:

tipo[] identificatore;
o, per analogia con il linguaggio C,

tipo identificatore[];
dove tipo rappresenta un tipo primitivo od un oggetto, ed identificatore il nome che utilizzeremo per far riferimento ai dati contenuti allinterno dellarray. Nel prossimo esempio, viene dichiarato un array il cui tipo base rappresentato da un intero utilizzando entrambe le forme sintattiche riconosciute dal linguaggio: int[] elencodiNumeriInteri; int elencodiNumeriInteri[]; Dichiarare una variabile di tipo array non basta; non abbiamo ancora definito la lunghezza dellarray e sopratutto, non ne abbiamo creato listanza. La creazione dellistanza di un array in Java, deve essere realizzata utilizzando loperatore new, che discuteremo nel capitolo successivo; al momento dello stanziamento necessario dichiararne la lunghezza. La sintassi completa la seguente:

identificatore = new tipo[lunghezza];


dove, identificatore il nome associato allarray al momento della dichiarazione della variabile, tipo il tipo base dellarray e lunghezza il numero massimo di elementi che larray potr contenere. Riconsiderando lesempio precedente, la sintassi completa per dichiarare ed allocare un array di interi di massimo 20 elementi la seguente: int[] elencodiNumeriInteri; elencodiNumeriInteri = new int[20];

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 77

o, in alternativa, possibile dichiarare e creare larray, simultaneamente, come per le variabili di un qualsiasi tipo primitivo: int[] elencodiNumeriInteri = new int[20];

4.14 Lavorare con gli array


Creare un array vuole dire aver creato un contenitore vuoto, in grado di accogliere un numero massimo di elementi, definito dalla lunghezza, tutti della stessa tipologia definita dal tipo base dellarray (Figura 32). Di fatto, creando un array abbiamo chiesto alla JVM di riservare spazio di memoria sufficiente a contenerne tutti gli elementi.

Figura 32:

Schema base di un array

Come per il linguaggio C, Java accetta la notazione tra parentesi graffe per poter creare ed inizializzate larray nello stesso momento. Nel prossimo esempio viene creato un array di interi di nome listaDiInteri di lunghezza 5 e contenente i numeri 1,2,3,4,5: int[] listaDiInteri = {1,2,3,4,5}; Arrivati a questo punto siamo in grado di dichiarare, creare ed eventualmente inizializzare un array, ma come facciamo ad aggiungere, modificare o estrarre i valori memorizzati allinterno della struttura? A tale scopo ci viene in aiuto loperatore indice [] che, nella forma

identificatore[i]
ci consente di agire direttamente sulli-esimo elemento contenuto nellarray.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 78

E importante ricordare sempre che il primo elemento in un array parte dalla posizione 0 (Figura 33) e non 1, di conseguenza un array di n elementi sar accessibile tramite loperatore indice [0],[2],[3],.[n-1]. Infine, possibile ottenere la lunghezza dellarray tramite la propriet length nel modo seguente:

lunghezza = identificatore.length

Figura 33:

Operatore indice di un array

Nel prossimo esempio, creaiamo un array contenente i primo dieci caratteri dellalfabeto italiano nel formato minuscolo ed utilizziamo un secondo array per memorizzare gli stessi caratteri in formato maiuscolo.
/** * @version 0.1 * @see ConvertiInMaiuscolo */
public class MinuscoloMaiuscolo { public static void main(String[] args) { //Dichiarazione e creazione di un array di caratteri //contenente i primi dieci caratteri minuscoli dell'alafabeto italiano char[] minuscolo = {'a','b','c','d','e','f','g','h','i','l'};

//Dichiarazione e creazione dell'array di caratteri //di lunghezza 10 che conterr i caratteri maiscoli dopo //la copnversione
char[] maiuscolo = new char[10];

//Converto i caratteri in maiuscolo e li inserisco nel nuovo array //utilizzando due variabili int che conterranno il codice UNICODE dei //due caratteri

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 79

int carattereMinuscolo; int carattereMaiuscolo; for (int i = 0; i < 10; i++) { carattereMinuscolo = minuscolo[i];

//Eseguo la conversione in maiuscolo


carattereMaiuscolo = carattereMinuscolo & 223;

//Memorizzo il risultato nel nuovo array maiuscolo[i]=(char)carattereMaiuscolo; System.out.println("minuscolo["+i+"]='" + minuscolo[i] + "', maiuscolo ["+i+"]='" + maiuscolo[i]+"'");
} } }

Lesecuzione del codice produrr il seguente output:


minuscolo[0]='a', maiuscolo [0]='A' minuscolo[1]='b', maiuscolo [1]='B' minuscolo[2]='c', maiuscolo [2]='C' minuscolo[3]='d', maiuscolo [3]='D' minuscolo[4]='e', maiuscolo [4]='E' minuscolo[5]='f', maiuscolo [5]='F' minuscolo[6]='g', maiuscolo [6]='G' minuscolo[7]='h', maiuscolo [7]='H' minuscolo[8]='i', maiuscolo [8]='I' minuscolo[9]='l', maiuscolo [9]='L'

4.15 Array Multidimensionali


Per poter rappresentare strutture dati a due o pi dimensioni, Java supporta gli array multidimensionali o array di array. Per dichiarare un array multidimensionale la sintassi simile a quella per gli array, con la differenza che necessario specificare ogni dimensione, utilizzando una coppia di parentesi []. Un array a due dimensioni pu essere dichiarato nel seguente modo:

tipo[][] identificatore;
in cui tipo ed identificatore rappresentano rispettivamente il tipo base dellarray ed il nome che ci consentir di accedere ai dati in esso contenuti. Nella realt, Java organizza gli array multidimensionali come array di array; per questo motivo, non necessario specificarne la lunghezza per ogni dimensione dichiarata, al momento della creazione: in altre parole, un array multidimensionale non deve essere necessariamente creato utilizzando una singola operazione new. Nel prossimo esempio, rappresentiamo una tavola per il gioco della dama utilizzando un array a due dimensioni il cui tipo base loggetto Pedina .

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 80

Linizializzazione delloggetto viene effettuata riga per riga, un valore null nella posizione [i][j] identifica una casella vuota (Figura 34).

Figura 34:

Gioco della dama

Pedina[][] dama = new Pedina[8][]; Pedina[] riga0 = { new Pedina(bianca),null, new Pedina(bianca),null, new Pedina(bianca),null, new Pedina(bianca),null }; Pedina[] riga1 = { null, new Pedina(bianca), null, new Pedina(bianca), null, new Pedina(bianca), null, new Pedina(bianca) }; Pedina[] riga2 = { new Pedina(bianca),null, new Pedina(bianca),null, new Pedina(bianca),null, new Pedina(bianca),null }; Pedina[] riga3 = { null, null, null, null, null, null, null, null }; Pedina[] riga4 = { null, null, null, null, null, null, null, null }; Pedina[] riga5 = { null, new Pedina(nera), null, new Pedina(nera), null, new Pedina(nera), null, new Pedina(nera) }; Pedina[] riga6 = { new Pedina(nera),null, new Pedina(nera),null, new Pedina(nera),null, new Pedina(nera),null };

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 81

Pedina[] riga7 = { null, new Pedina(nera), null, new Pedina(nera), null, new Pedina(nera), null, new Pedina(nera) };

dama[0] dama[1] dama[2] dama[3] dama[4] dama[5] dama[6] dama[7]

= = = = = = = =

riga0; riga1; riga2; riga3; riga4; riga5; riga6; riga7;

4.16 Espressioni ed istruzioni


Le espressioni rappresentano il meccanismo per effettuare calcoli allinterno della nostra applicazione; combinano variabili e operatori producendo un singolo valore di ritorno. Le espressioni vengono utilizzate per generare valori da assegnare alle variabili o, come vedremo nel prossimo capitolo, per modificare il corso della esecuzione di una applicazione Java: di conseguenza una espressione non rappresenta una unit di calcolo completa in quanto non produce assegnamenti o modifiche alle variabili della applicazione. A differenza delle espressioni, le istruzioni sono unit eseguibili complete terminate dal carattere ; e combinano operazioni di assegnamento, valutazione di espressioni o chiamate ad oggetti (questultimo concetto risulter pi chiaro alla fine del prossimo capitolo), combinate tra loro a partire dalle regole sintattiche del linguaggio.

4.17 Regole sintattiche di Java


Una istruzione rappresenta il mattone per la costruzione di oggetti. La sintassi del linguaggio Java pu essere descritta da tre sole regole di espansione:

istruzione --> espressione OPPURE istruzione --> { } OPPURE istruzione --> controllo_di_flusso istruzione [istruzione]

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 82

istruzione
Queste tre regole hanno natura ricorsiva e la freccia deve essere letta come diventa. Sostituendo una qualunque di queste tre definizioni allinterno del lato destro di ogni espansione, possono essere generati una infinit di istruzioni. Di seguito un esempio. Prendiamo in considerazione la terza regola.

istruzione --> controllo_di_flusso istruzione


E sostituiamo il lato destro utilizzando la seconda espansione ottenendo

istruzione --> controllo_di_flusso --> controllo_di_flusso istruzione 2 { istruzione }


Applicando ora la terza regola di espansione otteniamo :

controllo_di_flusso --> controllo_di_flusso { 3 { istruzione controllo_di_flusso } istruzione }


Prendiamo per buona che listruzione if sia una istruzione per il controllo del flusso della applicazione (controllo_di_flusso), e facciamo un ulteriore sforzo accettando che la sua sintassi sia: if(espressione_booleana) Ecco che la nostra espansione diventer quindi :

istruzione --> --> --> controllo_di_flusso --> if(i>10) 2 3 { { controllo_di_flusso if(I==5) istruzione print(I vale 5); } i++; }

4.18 Blocchi di istruzioni


La seconda regola di espansione:

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 83

istruzione --> { }

istruzione [istruzione]

definisce la struttura di un blocco di istruzioni, ovvero una sequenza di una o pi istruzioni racchiuse allinterno di parentesi graffe.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 84

5 DEFINIZIONE DI OGGETTI
5.1 Introduzione

In questo capitolo saranno trattati gli aspetti specifici del linguaggio Java relativi alla definizione di oggetti: le regole sintattiche base per la creazione di classi, lallocazione di oggetti e la determinazione del punto di ingresso (entry point) di unapplicazione. Per tutta la durata del capitolo sar importante ricordare i concetti base discussi in precedenza, in particolar modo quelli relativi alla definizione di una classe di oggetti. Le definizioni di classe rappresentano il punto centrale dei programmi Java. Le classi hanno la funzione di contenitori logici per dati e codice e facilitano la creazione di oggetti che compongono lapplicazione. Per completezza, il capitolo tratter le caratteristiche del linguaggio necessarie a scrivere piccoli programmi, includendo la manipolazione di stringhe e la generazione di messaggi a video.

5.2

Metodi

Unistruzione rappresenta il mattone per creare le funzionalit di un oggetto. Nasce spontaneo chiedersi: come sono organizzate le istruzioni allinterno degli oggetti? I metodi rappresentano il cemento che tiene assieme tutti i mattoni e raggruppano blocchi di istruzioni riuniti a fornire una singola funzionalit. Essi hanno una sintassi molto simile a quella della definizione di funzioni ANSI C e possono essere descritti con la seguente forma:

tipo_di_ritorno nome(tipo identificatore [,tipo identificatore] ) { istruzione [istruzione] }


tipo_di_ritorno e tipo rappresentano ogni tipo di dato (primitivo o classe) e nome ed identificatore sono stringhe alfanumeriche, iniziano con una lettera e possono contenere caratteri numerici (discuteremo in seguito come avviene il passaggio di parametri). La dichiarazione di una classe Java, deve sempre contenere la definizione di tipo di ritorno prodotto da un metodo: tipo_di_ritorno; se il metodo non ritorna valori, dovr essere utilizzato il tipo speciale void . Se il corpo di un metodo contiene dichiarazioni di variabili, queste saranno visibili solo allinterno del metodo stesso, ed il loro ciclo di vita sar limitato allesecuzione del metodo. Non manterranno il loro valore tra chiamate differenti e non saranno accessibili da altri metodi.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 85

5.3

Definire una classe

Le istruzioni sono organizzate utilizzando metodi che contengono codice eseguibile che pu essere invocato passandogli un numero limitato di valori come argomenti. Daltro canto, Java un linguaggio orientato ad oggetti e come tale, richiede i metodi siano organizzati internamente alle classi. Nel primo capitolo abbiamo associato il concetto di classe a quello di categoria; se trasportato nellambito del linguaggio di programmazione la definizione non cambia, ma importante chiarire le implicazioni che ci comporta. Una classe Java deve rappresentare un oggetto concettuale e per poterlo fare, deve raggruppare dati e metodi assegnando un nome comune. La sintassi la seguente:

class Nome { dichirazione_dei_dati dichirazione_dei_metodi }


I dati ed i metodi contenuti allinterno della definizione sono chiamati membri della classe e devono essere rigorosamente definiti allinterno del blocco di dichiarazione; non possibile in nessun modo dichiarare variabili globali, funzioni o procedure. Questa restrizione del linguaggio Java scoraggia il programmatore ad effettuare una decomposizione procedurale, incoraggiando di conseguenza ad utilizzare lapproccio orientato agli oggetti. Ricordando la classe Libro descritta nel primo capitolo, avevamo stabilito che un libro tale solo se contiene pagine da sfogliare, strappare ecc.. Utilizzando la sintassi di Java potremmo fornire una prima grossolana definizione della nostra classe nel modo seguente:
/** * Questa classe rappresenta una possibile definizione della classe libro. * @version 0.1 */
class Libro {

//dichiarazione dei dati della classe


int numero_di_pagine=100; int pagina_corrente=0; String lingua = "Italiano"; String tipologia = "Testo di letteratura Italiana";

// dichiarazione dei metodi

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 86

/** * Restituisce il numero della pagina che stiamo leggendo * @return int : numero della pagina */ int paginaCorrente() { return pagina_corrente; } /** * Questo metodo restituisce il numero di pagina * dopo aver voltato dalla pagina attuale a quella successiva * @return int : numero della pagina */ int paginaSuccessiva() { pagina_corrente++; return pagina_corrente; } /** * Questo metodo restituisce il numero di pagina * dopo aver voltato dalla pagina attuale a quella precedente * @return int : numero della pagina */ int paginaPrecedente() { pagina_corrente--; return pagina_corrente; } /** * Restituisce la lingua con cui stato scritto il libro * @return String : Lingua con cui il libro stato scritto */ String liguaggio() { return lingua; } /** * Restituisce la tipologia del libro * @return String : Descrizione della tipologia del libro */ String tipologia() { return tipologia; }
}

5.4

Variabili reference

Java fa una netta distinzione tra classi e tipi primitivi. La differenza principale, ma anche la meno evidente, relativa al fatto che un oggetto non allocato dal linguaggio al momento della dichiarazione, come avviene per le variabili di tipo primitivo. Per chiarire questo punto, esaminiamo la seguente dichiarazione:

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 87

int contatore;

La JVM, quando incontra questa riga di codice, crea il puntatore ad una variabile intera chiamata contatore e contestualmente, alloca quattro byte in memoria per limmagazzinamento del dato inizializzandone il valore a 0 (Figura 35).

Figura 35:

dichirazione di una variabile int

Con le classi lo scenario cambia: in questo caso la JVM crea una variabile che conterr il puntatore alloggetto in memoria, ma non alloca risorse per caricare la classe. Di fatto loggetto non viene creato (Figura 36).

Figura 36:

Dichirazione di una variabile reference

Le variabili di questo tipo sono dette variabili reference ed hanno lunica capacit di tracciare oggetti del tipo compatibile: ad esempio una variabile reference di tipo Stack non pu contenere puntatori oggetti di tipo Libro. Oltre che per gli oggetti, Java utilizza lo stesso meccanismo per gli array; questi non sono allocati al momento della dichiarazione, ma la JVM crea una variabile che conterr il puntatore alla corrispondente struttura dati in memoria.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 88

Figura 37:

Dichiarazione di un array

Un array pu essere dichiarato utilizzando la sintassi:

tipo identificatore[];
Una dichiarazione di questo genere, crea una variabile che tiene traccia di un array di dimensione arbitraria (Figura 37). Le variabili reference sono concettualmente molto simili ai puntatori C e C++, ma non consentono la conversione intero/indirizzo o le operazioni aritmetiche; tuttavia le variabili reference possono essere ugualmente utilizzate per la creazione di strutture dati complesse come liste, alberi binari e array multidimensionali. In questo modo eliminano gli svantaggi derivanti dalluso di puntatori, mentre ne mantengono tutti i vantaggi. Unulteriore considerazione concernente la gestione delle variabili in Java, riguarda la differenza nellallocazione della memoria per rappresentare i dati di un qualsiasi tipo primitivo. La rappresentazione di un dato nel linguaggio C e C++ rispecchia il corrispondente dato macchina e questo comporta:

1. Il compilatore riserva solo la memoria sufficiente a rappresentare un tipo di dato (per una variabile di tipo byte saranno riservati 8 bit e per un variabile di tipo int 16 ecc.); 2. Il compilatore C e C++ non garantisce che la precisione di un dato sia quella stabilita dallo standard ANSI e di conseguenza, un tipo int potrebbe essere rappresentato con 16 o 32 bit secondo larchitettura di riferimento.
Java rappresenta i dati allocando sempre la stessa quantit di memoria, tipicamente 32 o 64 bit, indipendentemente dal tipo di dato da rappresentare: di fatto, le variabili si comportano come se: quello che cambia che il programmatore vedr una variabile byte comportarsi come tale ed altrettanto per le altre primitive.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 89

Questo, a discapito di un maggior consumo di risorse, garantisce la portabilit del Bytecode su ogni architettura, assicurando che una variabile si comporter sempre allo stesso modo.

5.5

Scope di una variabile Java

Differentemente da linguaggi come il Pascal, in cui le variabili debbono essere dichiarate allinterno di un apposito blocco di codice, Java come il C e C++ lascia al programmatore la libert di dichiarare le variabili in qualsiasi punto del codice del programma; altra caratteristica del linguaggio che due o pi variabili possono essere identificate dallo stesso nome. La flessibilit offerta dal linguaggio richiede, per, che siano definite alcune regole per stabilire i limiti di una variabile evitando sovrapposizioni pericolose. Lo scope di una variabile Java la regione di codice allinterno della quale essa pu essere referenziata utilizzando il suo identificatore e ne determina il ciclo di vita individuando quando la variabile debba essere allocata o rimossa dalla memoria. I blocchi di istruzioni ci forniscono il meccanismo necessario a determinare i confini dello scope di una variabile: di fatto, una variabile referenziabile solo allinterno del blocco di istruzioni che contiene la sua dichiarazione ed ai sottoblocchi contenuti. In dettaglio, le regole di definizione dello scopo di una variabile possono essere schematizzate nel modo seguente.

Le variabili dichiarate allinterno del blocco di dichiarazione di una classe sono dette variabili membro. Lo scope di una variabile membro determinato dallintero blocco di dichiarazione della classe (Figura 38 ).

Figura 38:

Scope delle variabili membro

Le variabili definite allinterno del blocco di dichiarazione di un metodo sono dette variabili locali. Lo scope di una variabile locale determinato dal blocco

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 90

di codice allinterno della quale dichiarata ed ai suoi sottoblocchi (Figura 39).

Figura 39:

Scope di una variabile locale

Per esempio, il codice seguente produce un errore di compilazione poich si tenta di utilizzare la variabile somma di tipo intero allesterno del suo blocco di dichiarazione.
class scope1 { int scopeErrato() { int i; for (i=0 ; i<10; i++) { int somma = somma+i; } //Questa riga di codice contiene un errore System.out.println(La somma vale: +somma); } int scopeCorretto { int i; for (i=0 ; i<10; i++) { int somma = somma+i; //Questa riga di codice corretta System.out.println(La somma vale: +somma); } } }

Lo scope di una variabile Java ne definisce il ciclo di vita. Una variabile non esiste fino a che si entra nel blocco che ne delimita lo scope e distrutta quando la JVM esce dal blocco. Di conseguenza, le variabili locali non possono

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 91

mantenere

il

loro

valore

tra

due

chiamate

differenti.

class scope2 { public static void main (String[] args) { for (int i=0;i<2;i++) { int j = 5; System.out.println("Entro nel blocco che definisce lo scope della varabile j"); System.out.println("La variabile J vale: "+j); System.out.println("Sommo 5 alla variabile j."); j = j+5; System.out.println("Ora la variabile J vale: "+j); System.out.println("esco dal blocco che definisce lo scope della varabile j"); System.out.println("----------------------------------------------------------------"); } } }

Lesecuzione dellesempio dimostra come la variabile j definita allinterno del blocco di codice dellistruzione for, sia creata, modificata e distrutta perdendo il valore tra due chiamate differenti.

Entro nel blocco che definisce lo scope della varabile j La variabile J vale: 5 Sommo 5 alla variabile j. Ora la variabile J vale: 10 esco dal blocco che definisce lo scope della varabile j -------------------------------------------------------------------Entro nel blocco che definisce lo scope della varabile j La variabile J vale: 5 Sommo 5 alla variabile j. Ora la variabile J vale: 10 esco dal blocco che definisce lo scope della varabile j --------------------------------------------------------------------

Metodi differenti possono contenere dichiarazioni di variabili con identificatore uguale. Le variabili locali possono avere lo stesso identificatore delle variabili membro. In questo caso, sar necessario specificare esplicitamente quale variabile si voglia referenziare utilizzandone lidentificatore.
Nel prossimo esempio viene mostrato come referenziare una variabile membro o una variabile locale aventi lo stesso identificatore.
public class Scope3 { //dichiarazione della variabile membro appoggio int appoggio = 5;

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 92

void somma(int valore) { //dichiarazione della variabile locale appoggio int appoggio = 3;

//Sommo valore alla variabile locale appoggio = appoggio + valore; System.out.println("La variabile locale dopo la somma vale: "+appoggio); //sommo valore alla variabile membro this.appoggio = this.appoggio + valore; System.out.println("La variabile membro dopo la somma vale: "+this.appoggio); //sommo la variabile locale alla variabile membro this.appoggio = this.appoggio + appoggio; System.out.println("Dopo la somma delle due variabili, la variabile membro vale:
"+this.appoggio); } public static void main(String[] args) { Scope3 Scope = new Scope3(); Scope.somma(5); } } La variabile locale dopo la somma vale: 8 La variabile membro dopo la somma vale: 10 Dopo la somma delle due variabili, la variabile membro vale: 18

Concludendo, nonostante la flessibilit messa a disposizione dal linguaggio Java, buona regola definire tutte le variabili membro allinizio del blocco di dichiarazione della classe e tutte le variabili locali al principio del blocco che ne delimiter lo scope. Questi accorgimenti non producono effetti durante lesecuzione dellapplicazione, ma migliorano la leggibilit del codice sorgente consentendo di identificare facilmente quali saranno le variabili utilizzate per realizzare una determinata funzionalit.

5.6

L oggetto null

Come per i tipi primitivi, ogni variabile reference richiede che, al momento della dichiarazione, le sia assegnato un valore iniziale. Per questo tipo di variabili, il linguaggio Java prevede il valore speciale null che, rappresenta un oggetto inesistente ed utilizzato dalla JVM come valore prestabilito: quando unapplicazione tenta di accedere ad una variabile reference contenente un riferimento a null, il compilatore Java produrr un messaggio di errore in forma di oggetto di tipo NullPointerException6. Oltre ad essere utilizzato come valore prestabilito per le variabili reference, loggetto null gioca un ruolo importante nellambito della programmazione per la gestione delle risorse: quando ad una variabile reference assegnato il
6

Le eccezioni verranno discusse in un capitolo successivo

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 93

valore null, loggetto referenziato rilasciato e se non utilizzato sar inviato al garbage collector che si occuper di rilasciare la memoria allocata rendendola nuovamente disponibile. Altro uso che pu essere fatto delloggetto null riguarda le operazioni di comparazione come mostrato nel prossimo esempio in cui creeremo la definizione di classe per una struttura dati molto comune: la Pila o Stack.

Figura 40:

Pila

Una Pila una struttura dati gestita con la metodologia LIFO (Last In First Out), in altre parole lultimo elemento ad essere inserito il primo ad essere estratto (Figura 40). Loggetto che andremo a definire, conterr al massimo 20 numeri interi e avr i due metodi: void push(int) int pop() Il metodo push ritorna un tipo void e prende come parametro un numero intero da inserire sulla cima della pila, il metodo pop non accetta parametri, ma restituisce lelemento sulla cima della Pila. I dati iseriti nella Pila sono memorizzati allinterno di un array che deve essere inizializzato allinterno del metodo push(int). Si pu controllare lo stato dello stack ricordando che una variabile reference appena dichiarata contiene un riferimento alloggetto null.
/** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO * @version 0.1 */

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 94

class Pila { int[] dati; int cima;

/** * Il metodo push ritorna un tipo void e prende come * parametro un numero intero da inserire sulla cima della pila * @return void * @param int dato : elemento da inserire sulla cima della pila */ void push(int dato) { if(dati == null) { cima = 0; dati = new int[20]; } if(cima < 20) { dati[cima] = dato; cima ++; } } /** * il metodo pop non accetta parametri e restituisce * lelemento sulla cima della Pila * @return int : dato sulla cima della pila */ int pop() { if(cima > 0) { cima--; return dati[cima]; } return 0; // Bisogna tornare qualcosa }
}

Allinterno della definizione del metodo void push(int i), per prima cosa viene controllato se larray stato inizializzato utilizzando loperatore di uguaglianza con il valore null, ed eventualmente allocato come array di venti numeri interi.
if(dati == null) { cima = 0; dati = new int[20]; }

A seguire, lalgoritmo esegue un controllo per verificare se possibile inserire elementi allinterno dellarray. In particolare, essendo venti il numero massimo di interi contenuti, mediante listruzione if il metodo accerta che la posizione puntata dalla variabile cima sia minore della lunghezza massima dellarray (ricordiamo che in un array le posizioni sono identificate a partire da 0): se

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 95

lespressione cima < 20 restituisce il valore true, il numero intero passato come parametro di input viene inserito nellarray nella posizione cima. La variabile cima viene quindi aggiornata in modo che punti alla prima posizione libera nellarray.
if(cima < 20) { dati[cima] = dato; cima ++; }

Il metodo int pop() estrae il primo elemento della pila e lo restituisce allutente. Per far questo, il metodo controlla se il valore di cima sia maggiore di zero e di conseguenza, che esista almeno un elemento allinterno dellarray: se la condizione restituisce un valore di verit, cima modificata in modo da puntare allultimo elemento inserito, il cui valore restituito mediante il comando return. In caso contrario il metodo ritorna il valore zero.
int pop() { if(cima > 0) { cima--; return dati[cima]; } return 0; // Bisogna tornare qualcosa }

Facciano attenzione i programmatori C, C++. Il valore null nel nostro caso non equivale al valore 0, ma rappresenta un oggetto nullo.

5.7

Creare istanze

Definire una variabile reference non basta a creare un oggetto ma, deve essere necessariamente caricato in memoria dinamicamente. Loperatore new fa questo per noi, riservando la memoria necessaria al nuovo oggetto e restituendone il riferimento, che pu quindi essere memorizzato in una variabile reference del tipo appropriato. La sintassi delloperatore n e w per creare il nuovo oggetto dalla sua definizione di classe la seguente:

new Nome() ;
Le parentesi sono necessarie ed hanno un significato particolare che sveleremo presto, Nome il nome di una definizione di classe appartenente alle API di Java oppure definita dal programmatore. Per creare un oggetto di tipo Pila faremo uso dellistruzione:
Pila Istanza_Di_Pila = new Pila();

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 96

La riga di codice utilizzata, dichiara una variabile Istanza_Di_Pila di tipo Pila, crea loggetto utilizzando la definizione di classe e memorizza il puntatore alla memoria riservata nella variabile reference (Figura 41). Un risultato analogo pu essere ottenuto anche nel modo seguente:
Pila Istanza_Di_Pila = null; Istanza_Di_Pila = new Pila();

Figura 41:

Ordine di esecuzione per loperatore new

Ricordando quanto detto nel capitolo precedente, notiamo come gli array vengano creati in maniera simile alle classi Java:
int Array_Di_Elementi[] = new int[20];

o, analogamente al caso precedente


int Array_Di_Elementi [] = null; Array_Di_Elementi = new int[20];

In questo caso, la JVM crea una variabile reference di tipo intero, riserva memoria per venti interi e ne memorizza il puntatore in Array_Di_Elementi. Lanalogia con le classi evidente e dipende dal fatto che un array Java un oggetto particolare, costruito da una classe base che non contiene definizioni di metodi e consente lutilizzo delloperatore indice []. Da qui la necessit di creare un array utilizzando loperatore new.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 97

5.8

Oggetti ed Array Anonimi

Capita spesso di dover creare oggetti referenziati solo allinterno di singole istruzioni od espressioni. Nel prossimo esempio utilizziamo un oggetto di tipo Integer per convertire il valore una variabile di primitiva int in una stringa.
public class OggettiAnonimi { public static void main(String[] args) { int i = 100; Integer appoggio = new Integer(i); String intero = appoggio.toString(); System.out.println(intero); } }

Loggetto di tipo Integer, utilizzato soltanto per la conversione del dato primitivo e mai pi referenziato. Lo stesso risultato pu essere ottenuto nel modo seguente:
public class OggettiAnonimiNew { public static void main(String[] args) { int i = 100; String intero = new Integer(i).toString(); System.out.println(intero); } }

Nel secondo caso, loggetto di tipo Integer utilizzato senza essere associato a nessun identificatore e di conseguenza, detto oggetto anonimo. Pi in generale, un oggetto anonimo quando creato utilizzando loperatore new omettendo la specifica del tipo delloggetto ed il nome dellidentificatore. Come gli oggetti, anche gli array possono essere utilizzati nella loro forma anonima. Anche in questo caso un array viene detto anonimo quando viene creato omettendo il nome dellidentificatore.

5.9

Loperatore punto .

Loperatore punto, fornisce laccesso alle variabili membro di una classe, tramite il suo identificatore. Per comprendere meglio quanto detto, esaminiamo attentamente il prossimo esempio:
/** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO (Last In First Out) * @version 0.2 */
class Pila { int[] dati;

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 98

int cima;

/** * Il metodo push ritorna un tipo void e prende come * parametro un oggetto di tipo Elemento da cui estrarre * il valore da inserire sulla cima della pila * @return void * @param Elemento dato : elemento da inserire sulla cima della pila */ void push(Elemento elem) { if(dati == null) { cima = 0; dati = new int[20]; } if(cima < 20) { // Inserisco sulla cima della pila // il valore intero rappresentato dal dato // membro delloggetto Elemento. dati[cima] = elem.valore; cima ++; } } /** * il metodo pop non accetta parametri e restituisce * lelemento sulla cima della Pila * @return Elemento : dato sulla cima della pila */ Elemento pop() { if(cima > 0) { cima--; //Creo una nuova istanza delloggetto Elemento //da utilizzare per trasportare il valore sulla cima dello stack Elemento elem = new Elemento(); //imposto il valore del dato da trasportare elem.valore = dati[cima]; return elem; } return null; }
} class Elemento { int valore; }

In questa nuova versione, la classe Pila stata modificata affinch i metodi push e pop utilizzino oggetti di tipo Elemento invece di numeri interi: tali oggetti, la cui definizione segue quella della classe Pila, contengono un solo dato membro di tipo int.

http://www.4it.it/ - http://www.javamattone.4it.it - info@4it.it

pag. 99

Utilizzando loperatore punto, il metodo push estrae il valore del dato membro e lo inserisce allinterno dellarray, viceversa, il metodo pop ne imposta il valore e torna un oggetto di tipo Elemento. Operazioni analoghe possono essere utilizzate per inserire nuovi elementi nella pila e riaverne il valore come mostrato nellesempio seguente:
//Creiamo un oggetto Elemento ed inizializziamo il dato membro
Elemento elem = new Elemento(); elem.valore = 10;

//Creiamo un oggetto Pila Pila pila = new Pila(); //inseriamo il valore in cima alla Pila pila.push(elem); //Eseguiamo una operazione di //pop sulla Pila elem = s.pop();

Oltre ai dati membro, anche i metodi di una classe Java sono accessibili mediante loperatore punto, come mostrato nel prossimo esempio in cui introduciamo una nuova modifica alle classi Pila ed Elemento.
class Elemento { int valore;

// questo metodo inizializza il dato membro della classe void impostaIlValore(int val) { valore = val; }
int restituisciIlValore() { return valore; } }

/** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO * @version 0.3 */
class Pila { int[] dati; int cima;

/** * Il metodo push ritorna un tipo void e prende come

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

100

* parametro un oggetto di tipo Elemento da cui estrarre * il valore da inserire sulla cima della pila * @return void * @param Elemento dato : elemento da inserire sulla cima della pila */ void push(Elemento elem) { if(dati == null) { cima = 0; dati = new int[20]; } if(cima < 20) { // Inserisco sulla cima della pila // il valore intero rappresentato dal dato // membro delloggetto Elemento. dati[cima] = elem.restituisciIlValore(); cima ++; } } /** * il metodo pop non accetta parametri e restituisce * lelemento sulla cima della Pila * @return Elemento : dato sulla cima della pila */ Elemento pop() { if(cima > 0) { cima--; //Creo una nuova istanza delloggetto Elemento //da utilizzare per trasportare il valore sulla cima dello stack Elemento elem = new Elemento(); //imposto il valore del dato da trasportare elem. impostaIlValore(dati[cima]); return elem; } return null; }
}

5.10 Auto referenza esplicita


Loperatore punto, oltre a fornire la via di accesso ai dati membro od ai metodi di un oggetto attraverso una variabile reference, consente ai metodi di accedere ai dati membro della classe di definizione. Una nuova versione della classe Elemento, definita nel paragrafo precedente, ci pu aiutare a comprendere meglio quanto affermato:
class Elemento { int valore;

// questo metodo inizializza il dato membro della classe

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

101

void impostaIlValore(int valore) { ???. valore = valore; } int restituisciIlValore() { return valore; } }

Il metodo impostaIlValore prende come parametro un intero, tramite il parametro identificato da valore ed utilizza loperatore punto per memorizzare il dato allinterno della variabile membro valore . Dal momento che il riferimento alla variabile valore ambiguo, necessario chiarire il modo con cui un oggetto possa referenziare se stesso (evidenziato nellesempio dai punti interrogativi ???). Java prevede un modo di auto referenza particolare identificabile con la variabile reference this. Di fatto, il valore di this modificato automaticamente dalla Java Virtual Machine affinch, ad ogni istante, sia sempre riferito alloggetto attivo, intendendo per oggetto attivo listanza della classe in esecuzione durante la chiamata al metodo corrente. La nostra classe diventa quindi:
class Elemento { int valore;

// questo metodo inizializza il dato membro della classe void impostaIlValore(int valore) { this.valore = valore; }
int restituisciIlValore() { return valore; } }

Questa modalit di accesso viene detta auto referenza esplicita ed applicabile ad ogni tipo di dato e metodo membro di una classe.

5.11 Auto referenza implicita


Poich, come abbiamo detto, ogni metodo deve essere definito allinterno di una definizione di classe, il meccanismo di auto referenza esplicita molto comune in applicazioni Java; se per un riferimento non ambiguo, Java

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

102

consente di utilizzare un ulteriore meccanismo detto di auto referenza implicita, per mezzo del quale possibile accede a dati membro o metodi di una classe senza necessariamente utilizzare esplicitamente la variabile reference this.
class Elemento { int val;

// questo metodo inizializza il dato membro della classe void impostaIlValore(int valore) { val = valore; }
int restituisciIlValore() { return val; } }

Il meccanismo su cui si basa lauto referenza implicita legato alla visibilit di una variabile. Ricordando che la visibilit di una variabile in Java limitata al blocco ed ai sottoblocchi di codice in cui stata effettuata la sua dichiarazione, Java ricerca una variabile non qualificata risalendo a ritroso tra i diversi livelli dei blocchi di codice. Inizialmente, Java ricerca la dichiarazione della variabile allinterno del blocco di istruzioni corrente:

1. se la variabile non un parametro appartenente al blocco, risale tra i vari livelli del codice fino ad arrivare alla lista dei parametri del metodo corrente. 2. Se neanche la lista dei parametri del metodo soddisfa la ricerca, Java legge il blocco di dichiarazione delloggetto corrente utilizzando implicitamente la variabile reference this. 3 . Nel caso in cui la variabile non neanche un dato membro delloggetto, un codice di errore generato al momento della produzione del Bytecode dal compilatore.
Anche se luso implicito di variabili facilita la scrittura di codice, riducendo la quantit di caratteri da digitare, abusare di questa tecnica pu essere causa di ambiguit allinterno della definizione della classe. Tipicamente, la situazione a cui si va incontro la seguente:
class Elemento { int val;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

103

void impostaIlValore(int valore) { int val ; val = valore; } int restituisciIlValore() { return val; } }

Lassenza delluso esplicito della variabile reference this la causa della perdita del dato passato come parametro al metodo impostaIlValore(int): di fatto, il valore memorizzato in una variabile visibile solo allinterno del blocco di istruzioni del metodo e di conseguenza con ciclo di vita limitato al tempo necessario allesecuzione del metodo. Meno ambiguo invece luso dellauto referenza implicita se impiegata nella chiamata ai metodi della classe. In questo caso Java applicher soltanto il terzo punto dellalgoritmo descritto per la determinazione dei riferimenti alle variabili. Di fatto, un metodo non pu essere definito allinterno di un altro, ne pu essere utilizzato come argomento per il passaggio di parametri.

5.12 Stringhe
Abbiamo gi anticipato che Java mette a disposizione del programmatore molti tipi predefiniti: String uno di questi, definisce il concetto di stringa e come vedremo tra breve, dotato di molte caratteristiche particolari. A differenza del linguaggio C, in cui una stringa gestita mediante array di caratteri terminati dal valore null, Java rappresenta le stringhe come oggetti e come tali, dotati di tutte le caratteristiche previste. Ci che rende le stringhe oggetti particolari sono:

la possibilit di creare listanza di un oggetto String usando semplicemente una notazione con doppi apici omettendo loperatore new; luso delloperatore speciale + per la concatenazione come mostrato nel prossimo esempio.

String prima = Hello; String seconda = world; String terza = prima + seconda;

Il codice descritto equivale al seguente:


String prima = new String(Hello); String seconda = new String(world);

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

104

String terza = prima.concat(seconda);

I metodi messi a disposizione dalloggetto S t r i n g consentono al programmatore di eseguire funzioni base quali lindividuazione di caratteri o sequenze di caratteri allinterno della stringa, la sostituzione di caratteri minuscoli con i relativi maiuscoli e viceversa, ma non consentono di modificare la stringa rappresentata. Questa caratteristica rappresenta unaltra particolarit degli oggetti di tipo String : una stringa immutabile. Una volta creata, una stringa non pu essere modificata in nessun modo, a meno di utilizzare altri tipi di oggetti (StringBuffer) preposti alla loro modificazione.

5.13 Stato di un oggetto Java


Gli oggetti Java rappresentano spesso tipi di dati molto complessi ed il cui stato, a differenza di un tipo primitivo, non pu essere definito semplicemente dal valore della variabile reference. In particolare, definiamo stato di un oggetto Java il valore in un certo istante dei dati membro rilevanti della classe. Di fatto, non tutti i dati membro di una classe concorrono alla definizione dello stato di un oggetto bens solo quelli sufficienti a fornire, ad un determinato istante, informazioni sufficienti a fotografarne la condizione esatta. Ad esempio, lo stato del tipo Elemento rappresentato dal valore del dato membro valore di tipo int, mentre quello del tipo Pila dai valori dellarray che contiene gli elementi della struttura dati e dal dato membro cima di tipo int che contiene il puntatore allelemento sulla cima della pila.

5.14 Comparazione di oggetti


La comparazione di oggetti Java leggermente differente rispetto ad altri linguaggi di programmazione e dipende dal modo in cui Java manipola gli oggetti. Di fatto, unapplicazione Java non usa oggetti, ma variabili reference come oggetti. Una normale comparazione effettuata utilizzando loperatore == metterebbe a confronto il riferimento agli oggetti in memoria e non il loro stato, producendo un risultato true solo se le due variabili reference puntano allo stesso oggetto e non se i due oggetti distinti di tipo uguale, sono nello stesso stato.
String a = Java Mattone dopo Mattone; String b = Java Mattone dopo Mattone; (a == b) -> false

Molte volte per, ad unapplicazione Java, potrebbe tornare utile sapere se due istanze separate di una stessa classe sono uguali tra loro ovvero,

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

105

ricordando la definizione data nel paragrafo precedente, se due oggetti java dello stesso tipo si trovano nello stesso stato al momento del confronto. Java prevede un metodo speciale chiamato equals() che confronta lo stato di due oggetti: tutti gli oggetti in Java, anche quelli definiti dal programmatore, possiedono questo metodo poich ereditato da una classe base particolare che analizzeremo in seguito parlando di ereditariet. Il confronto delle due stringhe descritte nellesempio precedente assume quindi la forma seguente:
String a = Java Mattone dopo Mattone; String b = Java Mattone dopo Mattone; a.equals(b) -> true

5.15 Metodi statici


Finora abbiamo mostrato segmenti di codice dando per scontato che siano parte di un processo attivo: in tutto questo c una falla. Per sigillarla necessario fare alcune considerazioni: primo, ogni metodo deve essere definito allinterno di una classe (questo incoraggia ad utilizzare il paradigma Object Oriented). Secondo, i metodi devono essere invocati utilizzando una variabile reference inizializzata in modo che faccia riferimento ad un oggetto in memoria. Questo meccanismo rende possibile lauto referenza poich, se un metodo invocato in assenza di un oggetto attivo, la variabile reference this non sarebbe inizializzata. Il problema quindi che, in questo scenario, un metodo per essere eseguito richiede un oggetto attivo, ma fino a che non c qualcosa in esecuzione un oggetto non pu essere caricato in memoria. Lunica possibile soluzione quindi quella di creare metodi speciali che, non richiedano lattivit da parte delloggetto di cui sono membro cos che possano essere utilizzati in qualsiasi momento. La risposta nei metodi statici, ossia metodi che appartengono a classi, ma non richiedono oggetti attivi. Questi metodi possono essere creati utilizzando il qualificatore static a sinistra della dichiarazione del metodo come mostrato nella dichiarazione di static_method() nellesempio che segue:
class Euro { static double valoreInLire(){ return 1936.27; } }

Un metodo statico esiste sempre a prescindere dallo stato delloggetto; tuttavia, la locazione o classe che incapsula il metodo deve sempre essere ben qualificata. Questa tecnica chiamata scope resolution e pu essere realizzata in svariati modi. Uno di questi consiste nellutilizzare il nome della classe come se fosse una variabile reference:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

106

euro.valoreInLire();

Oppure si pu utilizzare una variabile reference nel modo che conosciamo:


Euro eur = new Euro(); eur. valoreInLire();

Se il metodo statico viene chiamato da un altro membro della stessa classe non necessario alcun accorgimento. E importante tener bene a mente che un metodo statico non inizializza la variabile reference this; di conseguenza un oggetto statico non pu utilizzare membri non statici della classe di appartenenza come mostrato nel prossimo esempio.
class Euro { double lire = 1936.27; static double valoreInLire() { //questa operazione non consentita return lire; } }

5.16 Il metodo main


Affinch la Java Virtual Machine possa eseguire unapplicazione, necessario che abbia ben chiaro quale debba essere il primo metodo da eseguire. Questo metodo detto entry point o punto di ingresso dellapplicazione. Come per il linguaggio C, Java riserva allo scopo lidentificatore di membro main. Ogni classe pu avere il suo metodo main(), ma solo quello della classe specificata alla Java Virtual Machine sar eseguito allavvio del processo. Questo significa che ogni classe di unapplicazione pu rappresentare un potenziale punto di ingresso, che pu quindi essere scelto allavvio del processo scegliendo semplicemente la classe desiderata.
class Benvenuto { public static void main(String args[]) { System.out.println(Benvenuto); } }

Vedremo in seguito come una classe possa contenere pi metodi membro aventi lo stesso nome, purch abbiano differenti parametri in input. Affinch il metodo main() possa essere trovato dalla Java Virtual Machine, necessario che abbia una lista di argomenti formata da un solo array di stringhe: proprio grazie a questarray che il programmatore pu inviare ad una applicazione informazioni aggiuntive, in forma di argomenti da inserire sulla riga di comando: il numero di elementi allinterno dellarray sar uguale al

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

107

numero di argomenti inviati. Se non vengono inseriti argomenti sulla riga di comando, la lunghezza dellarray zero. Infine, per il metodo main() necessario utilizzare il modificatore public7 che accorda alla virtual machine il permesso per eseguire il metodo. Tutto questo ci porta ad unimportante considerazione finale: tutto un oggetto, anche unapplicazione.

5.17 Classi interne


Le classi Java possiedono di unimportante caratteristica, ereditata dai linguaggi strutturati come il Pascal: una classe pu essere dichiarata a qualsiasi livello del codice e la sua visibilit limitata al blocco di codice che ne contiene la definizione. Tali classi sono dette classi interne o inner classes. Ad esempio, possiamo dichiarare la classe Contenitore allinterno della classe Pila:
class Pila { class Contenitore { .. } . }

La classe Pila detta incapsulante per la classe Contenitore . Per comprendere meglio il funzionamento di una classe interna, completiamo lesempio proposto analizzando di volta in volta gli aspetti principali di questo tipo di oggetti:
/** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO */ class Pila { int[] dati; int cima; /** * Dichiarazione della classe contenitore. Questa classe * definisce il contenitore utilizzato dalla pila per memorizzarne i dati. * @version 0.1 */ class Contenitore { void inserisci(int dato) { if (dati == null) { cima = 0; dati = new int[20]; } dati[cima] = dato;
7

Capiremo meglio il suo significato successivamente

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

108

cima++; } int ElementoSullaCima() { if (cima > 0) { cima--; return dati[cima]; } return 0; // Bisogna tornare qualcosa } int Cima() { return cima; } } Contenitore stack = new Contenitore();

/** * Il metodo push ritorna un tipo void e prende come parametro un numero intero da inserire sulla * cima della pila * @return void * @param int dato : elemento da inserire sulla cima della pila */ void push(int dato) { if (stack.Cima() < 20) { stack.inserisci(dato); } } /** * il metodo pop non accetta parametri e restituisce lelemento sulla cima della Pila * @return int : dato sulla cima della pila */ int pop() { return stack.ElementoSullaCima(); }
}

La classe Contenitore, definita allinterno del blocco principale di definizione della classe Pila, soggetta alle stesse regole che ne definiscono lo scope di metodi e variabili: dai metodi della classe Pila possiamo accedere ai dati membro ed ai metodi della classe Contenitore, utilizzando loperatore punto attraverso il suo nome e viceversa, dai metodi della classe Contenitore possiamo utilizzare i metodi ed i dati membro della classe incapsulante. Una caratteristica delle classi interne su cui porre laccento quella riguardante il loro ciclo di vita: una classe interna esiste solo se associata in maniera univoca ad unistanza della classe incapsulante. Questa caratteristica non solo consente di poter accedere ai metodi ed ai dati membro della classe incapsulante, ma garantisce che ogni riferimento alla classe incapsulante sia relativo solo ad una determinata istanza.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

109

Le classi interne possono essere definite a qualsiasi livello del codice, di conseguenza possibile creare concatenazioni di classi come mostrato nel prossimo esempio:
class PagineGialle { class ElencoAbbonati { class NumeriTelefonici { } } }

5.18 Classi locali


Le classi interne, possono essere definite anche nei blocchi di definizione dei metodi di una classe incapsulante e sono dette classi locali.
class PagineGialle { String trovaNumeroTelefonico(final String nome, final String cognome) { class Abbonato { String numero() { return find(nome,cognome); } String find(String nome, String cognome) { String numero=null; //Trova il numero di telefono return numero; } } Abbonato abbonato = new Abbonato(); return abbonato.numero(); } }

In accordo con le regole di visibilit, la classe ElencoAbbonati pu accedere a tutte le variabili dichiarate allinterno del blocco di definizione del metodo, compresi i suoi argomenti; laccorgimento che tutte le variabili utilizzate dalla classe interna e condivise con un metodo della classe incapsulante debbono essere dichiarate final a causa dei possibili problemi di sincronizzazione che potrebbero verificarsi. Di fatto, poich Java non consente lutilizzo di vaiabili globali, lutilizzo del modificatore final rappresenta lunico mezzo per prevenire eventuali errori nelluso condiviso di una variabile locale da parte di due o pi classi.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

110

5.19 Classi Interne ed autoreferenza


Parlando di autoreferenza, abbiamo definito il concetto di istanza corrente di una classe definendo oggetto attivo listanza della classe in esecuzione durante la chiamata ad un metodo. La natura delle classi interne estende necessariamente questo concetto; di fatto, una classe interna possiede pi di unistanza corrente. La classe Elenco definita nellesercizio precedente ha due istanze correnti: la propria e quella della classe ElencoTelefonico.

Figura 42:

Istanze correnti di una classe interna

Pi esplicitamente:

1 . Durante lesecuzione del codice della classe interna istanze correnti di A,B,C; 2 . Durante lesecuzione del codice della classe interna istanze correnti di A,B; 3 . Durante lesecuzione del codice della classe A, non istanze correnti differenti da quella delloggetto attivo; 4 . Nel caso di esecuzione di un metodo statico della esistono istanze correnti di nessun tipo.

C, esistono le B, esistono le esistono altre classe A, non

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

111

In generale, durante lesecuzione di un metodo qualsiasi appartenente ad una classe interna Ci esistono: listanza corrente della di Ci e tutte le istanze correnti delle classi che incapsulano Ci sino ad arrivare alla classe di primo livello. Detto questo, sorge spontaneo domandarsi come le classi interne influiscono sullutilizzo della variabile reference this. Come tutte le classi, anche le classi interne utilizzano il meccanismo di autoreferenza implicita per determinare quale metodo o variabile utilizzare in caso di chiamata. Ma, se fosse necessario fare riferimento ad un particolare tipo di istanza corrente, deve essere utilizzata la variabile reference this preceduta dal nome della classe da riferire. Nella figura precedente: 1. Durante lesecuzione del codice della classe interna C, B.this e A.this rappresentano i riferimenti rispettivamente alle istanze correnti delle classi A e B; 2 . Durante lesecuzione del codice della classe interna B, A.this rappresenta il riferimenti alla istanza correnti della classe A. Infine, la forma sintattica nome_della_classe.this permessa poich il linguaggio Java non consente di dichiarare classi interne con lo stesso nome della classe incapsulante.

5.20 Loggetto System


Unaltra delle classi predefinite in Java la classe System. Questa classe ha una serie di metodi statici e rappresenta il sistema su cui la applicazione Java in esecuzione. Due dati membro statici di questa classe sono System.out e System.err che rappresentano rispettivamente lo standard output e lo standard error dellinterprete Java. Usando il loro metodo statico println(), una applicazione Java in grado di inviare stringhe sullo standard output o sullo standard error.
System.out.println(Scrivo sullo standard output); System.err.println(Scrivo sullo standard error);

Il metodo statico System.exit(int number) causa la terminazione della applicazione Java producendo il codice di errore passato come argomento al metodo. Loggetto System fornisce anche il meccanismo per ottenere informazioni relative al sistema ospite mediante il metodo statico System.getProperty(String), che ritorna il valore della propriet di sistema richiesta o, in caso di assenza, ritorna il valore null. Le propriet, accessibili mediate questo metodo, possono variare a seconda del sistema su cui

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

112

lapplicazione in esecuzione; la tabella seguente elenca il nome delle propriet la cui definizione garantita indipendentemente dalla Java Virtual Machine a partire dalla versione 1.2: Propriet di sistema Nome Descrizione Separatore di file dipendente dalla piattaforma (Ad esempio file.separator \ per Windows e / per LINUX). java.class.path Valore della variabile dambiente CLASSPATH. java.class.version Versione delle Java API. java.home Directory in cui stato installato il Java Development Kit. java.version Versione dellinterprete Java. java.vendor Informazioni relative al produttore dellinterprete Java. java.vendor.url Indirizzo internet del produttore dellinterprete Java. Separatore di riga dipendente dalla piattaforma (Ad esempio line.separator \r\n per Windows e \n per LINUX). os.name Nome del sistema operativo os.arch Nome dellarchitettura os.version Versione del sistema operativo Separatore di PATH dipendente dalla piattaforma (Ad path.separator esempio ; per Windows e : per LINUX). user.dir Cartella di lavoro corrente. user.home Cartella Home dellutente corrente. user.name Nome dellutente connesso. Nel prossimo esempio, utilizziamo il metodo in esame per ottenere tutte le informazioni disponibili relative al sistema che stiamo utilizzando:
class Sistema { public static void main(String[] argv) { System.out.println("file.separator= "+System.getProperty("file.separator") ); System.out.println("java.class.path= "+System.getProperty("java.class.path") ); System.out.println("java.class.version= "+System.getProperty("java.class.version") ); System.out.println("java.home= "+System.getProperty("java.home") ); System.out.println("java.version= "+System.getProperty("java.version") ); System.out.println("java.vendor= "+System.getProperty("java.vendor") ); System.out.println("java.vendor.url= "+System.getProperty("java.vendor.url") ); System.out.println("os.name= "+System.getProperty("os.name") ); System.out.println("os.arch= "+System.getProperty("os.arch") ); System.out.println("os.version= "+System.getProperty("os.version") ); System.out.println("path.separator= "+System.getProperty("path.separator") ); System.out.println("user.dir= "+System.getProperty("user.dir") ); System.out.println("user.home= "+System.getProperty("user.home") ); System.out.println("user.name= "+System.getProperty("user.name") );

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

113

} }

Dopo lesecuzione della applicazione sul mio computer personale, le informazioni ottenute sono elencate di seguito:
file.separator= \ java.class.path= E:\Together6.0\out\classes\mattone;E:\Together6.0\lib\javax.jar; java.class.version= 47.0 java.home= e:\Together6.0\jdk\jre java.version= 1.3.1_02 java.vendor= Sun Microsystems Inc. java.vendor.url= http://java.sun.com/ os.name= Windows 2000 os.arch= x86 os.version= 5.1 path.separator= ; user.dir= D:\PROGETTI\JavaMattone\src user.home= C:\Documents and Settings\Massimiliano user.name= Massimiliano

Per concludere, il metodo descritto fornisce un ottimo meccanismo per inviare informazioni aggiuntive ad una applicazione senza utilizzare la riga di comando come mostrato nel prossimo esempio:
class Argomenti1 { public static void main(String[] argv) { System.out.println("user.nome= "+System.getProperty("user.nome") ); System.out.println("user.cognome= "+System.getProperty("user.cognome") ); } } java -Duser.nome=Massimiliano -Duser.cognome=Tarquini Argomenti1 user.nome= Massimiliano user.cognome= Tarquini

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

114

6 CONTROLLO DI FLUSSO E DISTRIBUZIONE DI OGGETTI


6.1 Introduzione

Java eredita da C e C++ lintero insieme di istruzioni per il controllo di flusso, apportando solo alcune modifiche. In aggiunta, Java introduce alcune nuove istruzioni necessarie alla manipolazione di oggetti. Questo capitolo tratta le istruzioni condizionali, le istruzioni cicliche, quelle relative alla gestione dei package per lorganizzazione di classi e listruzione import per risolvere la posizione delle definizioni di classi in altri file o package. I package Java sono strumenti simili a librerie e servono come meccanismo per raggruppare classi o distribuire oggetti. Listruzione import unistruzione speciale utilizzata dal compilatore per determinare la posizione su disco delle definizioni di classi da utilizzare nellapplicazione corrente. Come C e C++, Java un linguaggio indipendente dagli spazi, in altre parole, lindentazione del codice di un programma ed eventualmente luso di pi di una riga di testo sono opzionali.

6.2

Istruzioni per il controllo di flusso

Espressioni booleane ed istruzioni per il controllo di flusso forniscono al programmatore il meccanismo per comunicare alla Java Virtual Machine se e come eseguire blocchi di codice, condizionatamente ai meccanismi decisionali. Istruzioni per il controllo di flusso Istruzione Descrizione if Esegue o no un blocco di codice a seconda del valore restituito da una espressione booleana. if-else Determina quale tra due blocchi di codice sia quello da eseguire, a seconda del valore restituito da una espressione booleana. switch Utile in tutti quei casi in cui sia necessario decidere tra opzioni multiple prese in base al controllo di una sola variabile. for Esegue ripetutamente un blocco di codice. while Esegue ripetutamente un blocco di codice controllando il valore di una espressione booleana. do-while Esegue ripetutamente un blocco di codice controllando il valore di una espressione booleana.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

115

Le istruzioni per il controllo di flusso sono riassunte nella tabella precedente ed hanno sintassi definita dalle regole di espansione definite nel terzo capitolo e riassunte qui di seguito:

istruzione --> { }
oppure

istruzione [istruzione]

istruzione --> controllo_di_flusso istruzione

6.3

Listruzione if

Listruzione per il controllo di flusso if consente alla applicazione di decidere, in base ad una espressione booleana, se eseguire o no un blocco di codice. Applicando le regole di espansione definite, la sintassi di questa istruzione la seguente:

if (condizione) istruzione1; istruzione2;

-->

if (condizione) { istruzione; [istruzione] } istruzione; [istruzione]

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

116

Figura 43:

Diagramma di attivit dellistruzione IF

Nella regola definita, condizione rappresenta unistruzione booleana valida. Di fatto, se lespressione restituisce il valore true , sar eseguito il blocco di istruzioni immediatamente successivo, in caso contrario il controllo passer alla prima istruzione successiva al blocco if, come schematizzato nella Figura 43. Un esempio di istruzione if il seguente:
int x; .. if(x>10) { x=0; } x=1;

Nellesempio, se il valore di x strettamente maggiore di 10, verr eseguito il blocco di istruzioni di if ed il valore di x verr impostato a 0 e successivamente ad 1. In caso contrario, il flusso delle istruzioni salter direttamente al blocco di istruzioni immediatamente successivo al blocco if ed il valore della variabile x verr impostato direttamente a 1.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

117

6.4

Listruzione if-else

Una istruzione if pu essere opzionalmente affiancata da una istruzione else. Questa forma particolare dellistruzione if, la cui sintassi descritta di seguito, consente di decidere quale, tra due blocchi di codice, eseguire.

if (condizione) istruzione1 else istruzione2 istruzione3

-->

if (condizione) { istruzione; [istruzione] } else { istruzione; [istruzione] } istruzione; [istruzione]

Figura 44:

Diagramma di attivit dellistruzione IF-ELSE

Se condizione restituisce il valore true, sar eseguito il blocco di istruzioni di if, altrimenti il controllo sar passato ad else e sar eseguito il secondo blocco di istruzioni. Al termine, il controllo di flusso passa alla istruzione3. Di seguito un esempio:
if(y==3) {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

118

y=12; } else { y=0; }

In questo caso, se lespressione booleana ritorna valore true, allora saranno eseguite le istruzioni contenute nel blocco di istruzioni di if, altrimenti saranno eseguite le istruzioni contenute nel blocco di else.

6.5

Istruzioni if, if-else annidate

Unistruzione if annidata, rappresenta una forma particolare di controllo di flusso in cui unistruzione if o if-else controllata da unaltra istruzione if o if-else . Utilizzando le regole di espansione, in particolare intrecciando ricorsivamente la terza regola con le definizioni di if e if-else, otteniamo la forma sintattica:

istruzione -> controllo_di_flusso controllo_di_flusso istruzione controllo_di_flusso ->if(condizione) istruzione


oppure

controllo_di_flusso ->if(condizione) istruzione else istruzione


Da cui deriviamo una possibile regola sintattica per costruire blocchi if annidati:

if(condizione1) { istruzione1 if (condizione2) { istruzione2 if (condizione3) { istruzione3 }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

119

} istruzione4
La figura 45, schematizza lesecuzione del blocco definito:

Figura 45:

Istruzioni If annidate

6.6

Catene if-else-if

La forma pi comune di if annidati rappresentata dalla sequenza o catena if-else-if . Questo tipo di concatenazione valuta una serie arbitraria di istruzioni booleane procedendo dallalto verso il basso: se almeno una delle condizioni restituisce il valore true sar eseguito il blocco di istruzioni relativo.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

120

Se nessuna delle condizioni si dovesse verificare, allora sarebbe eseguito il blocco else finale.

Figura 46:

Catene If-Else-If annidate

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

121

La figura 46 schematizza la sequenza delle attivit svolte dalla seguente catena if-else-if:

if(condizione1) { istruzione1 } else if (condizione2) { istruzione2 } else if (condizione3) { istruzione3 } else { istruzione4 } istruzione5
Nel prossimo esempio, utilizziamo una catena if-else-if per stampare a video il valore di un numero intero da zero a dieci in forma di stringa. Il valore da stampare passato allapplicazione dalla riga di comando. Nel caso in cui il numero sia maggiore di dieci, lapplicazione stampa a video la stringa: Impossibile stampare un valore maggiore di dieci
/** * Questa classa stampa a video il valore del numero intero passato * sulla riga di comando in forma di stringa. */ class ValueOf { public static void main(String[] argv) { int intero = Integer.parseInt(argv[0]); if (intero == 0) { System.out.println("Zero"); } else if (intero == 1) { System.out.println("Uno"); } else if (intero == 2) { System.out.println("Due"); } else if (intero == 3) { System.out.println("Tre"); } else if (intero == 4) { System.out.println("Quattro");

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

122

} else if (intero == 5) { System.out.println("Cinque"); } else if (intero == 6) { System.out.println("Sei"); } else if (intero == 7) { System.out.println("Sette"); } else if (intero == 8) { System.out.println("Otto"); } else if (intero == 9) { System.out.println("Nove"); } else if (intero == 10) { System.out.println("Dieci"); } else { System.out.println("Impossibile stanpare un valore maggiore di dieci"); } } }

6.7

Listruzione switch

Lesempio del paragrafo precedente rende evidente quanto sia complesso scrivere o, leggere, codice java utilizzando catene if-else-if arbitrariamente complesse. Per far fronte al problema, Java fornisce al programmatore unistruzione di controllo di flusso che, specializzando la catena if-else-if rende pi semplice la programmazione di unapplicazione. Listruzione switch utile in tutti quei casi in cui sia necessario decidere tra scelte multiple, prese in base al controllo di una sola variabile. La sintassi dellistruzione la seguente:

switch (espressione) { case espressione_costante1: istruzione1 break_opzionale case espressione_costante2: istruzione2 break_opzionale case espressione_costante3: istruzione3 break_opzionale default: istruzione4 }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

123

istruzione5
Nella forma di Backus-Naur definita, espressione rappresenta ogni espressione valida che produca un intero ed espressione_costante unespressione che pu essere valutata completamente al momento della compilazione. Questultima, per funzionamento, pu essere paragonata ad una costante. Istruzione ogni istruzione Java come specificato dalle regole di espansione e break_opzionale rappresenta linclusione opzionale della parola chiave break seguita da ; . Nella Figura 47, schematizzati il diagramma delle attivit della istruzione switch.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

124

Figura 47:

Listruzione switch

In generale, dopo la valutazione di espressione, il controllo dellapplicazione salta al primo blocco case tale che

espressione == espressione_costante
ed esegue il relativo blocco di codice. Nel caso in cui il blocco termini con una istruzione break, lapplicazione abbandona lesecuzione del blocco switch saltando alla prima istruzione successiva al blocco, altrimenti il controllo viene

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

125

eseguito sui blocchi case successivi. Se nessun blocco case soddisfa la condizione, ossia

espressione != espr_costante
la virtual machine controlla lesistenza della label default ed esegue, se presente, solo il blocco di codice relativo ed esce da switch. Lesempio del paragrafo precedente pu essere riscritto nel modo seguente:
/** * Questa classa stampa a video il valore del numero * intero passato sulla riga di comando in forma di stringa utilizzando * l'istruzione switch in sostituzione di una catena if-else-if. */ class ValueOf2 { public static void main(String[] argv) { int intero = Integer.parseInt(argv[0]); switch (intero) { case 0: System.out.println("Zero"); break; case 1: System.out.println("Uno"); break; case 2: System.out.println("Due"); break; case 3: System.out.println("Tre"); break; case 4: System.out.println("Quattro"); break; case 5: System.out.println("Cinque"); break; case 6: System.out.println("Sei"); break; case 7: System.out.println("Sette"); break; case 8: System.out.println("Otto"); break; case 9: System.out.println("Nove"); break; case 10: System.out.println("Dieci"); break; default: System.out.println("Impossibile stanpare un valore maggiore di dieci"); } } }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

126

6.8

Listruzione while

Unistruzione while permette lesecuzione ripetitiva di un blocco di istruzioni, utilizzando unespressione booleana per determinare se eseguirlo o no, eseguendolo quindi fino a che lespressione booleana non restituisce il valore false. La sintassi per questa istruzione la seguente:

while (espressione){ istruzione1 } istruzione2


dove, espressione una espressione valida che restituisce un valore booleano.

Figura 48:

Listruzione while

In dettaglio, unistruzione while controlla il valore dellespressione booleana: se il risultato restituito true sar eseguito il blocco di codice di while. Alla fine dellesecuzione nuovamente controllato il valore dellespressione

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

127

booleana, per decidere se ripetere lesecuzione del blocco di codice o passare il controllo dellesecuzione alla prima istruzione successiva al blocco while. Applicando le regole di espansione, anche in questo caso otteniamo la forma annidata:

while (espressione) while (espressione) istruzione


Il codice di esempio utilizza la forma annidata dellistruzione while:
int i=0; while(i<10) { j=10; while(j>0) { System.out.println(i=+i+e j=+j); j--; } i++; }

6.9

Listruzione do-while

Unalternativa allistruzione while rappresentata dallistruzione do-while che, a differenza della precedente, controlla il valore dellespressione booleana alla fine del blocco di istruzioni. La sintassi di do-while la seguente:

do { istruzione1; } while (espressione); istruzione2;


A differenza dellistruzione while, ora il blocco di istruzioni sar eseguito sicuramente almeno una volta, come appare evidente nella prossima figura.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

128

Figura 49:

Listruzione do-while

6.10 Listruzione for


Quando definiamo un ciclo di istruzioni, accade spesso la situazione in cui tre operazioni distinte concorrono allesecuzione del blocco di istruzioni. Consideriamo il ciclo di 10 iterazioni:
i=0; while(i<10) { faiQualcosa(); i++; }

Nellesempio, come prima operazione, lapplicazione inizializza una variabile per il controllo del ciclo, quindi viene eseguita unespressione condizionale per decidere se eseguire o no il blocco di istruzioni dellistruzione while, infine la variabile aggiornata in modo tale che possa determinare la fine del ciclo. Tutte queste operazioni sono incluse nella stessa istruzione condizionale dellistruzione for:

for(inizializzazione ; condizione ; espressione){ istruzione1

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

129

} istruzione2
che ha forma annidata:

for(inizializzazione ; condizione ; espressione){ for(inizializzazione ; condizione ; espressione){ istruzione1 } } istruzione2


dove: inizializzazione rappresenta la definizione della variabile per il controllo del ciclo, condizione lespressione condizionale che controlla lesecuzione del blocco di istruzioni ed espressione contiene le regole per laggiornamento della variabile di controllo.

Figura 50:

Listruzione for

In una istruzione for, la condizione viene sempre controllata allinizio del ciclo. Nel caso in cui restituisca un valore false, il blocco di istruzioni non verr mai eseguito altrimenti, viene eseguito il blocco di istruzioni, viene aggiornato il valore della variabile di controllo e infine viene nuovamente valutata la condizione come mostrato nella Figura 50. Il ciclo realizzato nel precedente esempio utilizzando il comando while, pu essere riscritto utilizzando il comando for nel seguente modo:
for (int i=0 ; i<10 ; i++) faiQualcosa();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

130

6.11 Istruzione for nei dettagli


Listruzione for, in Java come in C e C++, unistruzione molto versatile poich consente di scrivere cicli di esecuzione utilizzando molte varianti alla forma descritta nel paragrafo precedente. In pratica, il ciclo for consente di utilizzare zero o pi variabili di controllo, zero o pi istruzioni di assegnamento ed altrettanto vale per le espressioni booleane. Nella sua forma pi semplice il ciclo for pu essere scritto nella forma:

for( ; ; ){ istruzione1 } istruzione2


Questa forma non utilizza n variabili di controllo, n istruzioni di assegnamento n tanto meno espressioni booleane. In un contesto applicativo realizza un ciclo infinito. Consideriamo ora il seguente esempio:
for (int i=0, j=10 ; (i<10 && j>0) ; i++, j--) { faiQualcosa(); }

Il ciclo descritto utilizza due variabili di controllo con due operazioni di assegnamento distinte. Sia la dichiarazione ed inizializzazione delle variabili di controllo, che le operazioni di assegnamento utilizzando il carattere , come separatore. Per concludere, la sintassi di questistruzione pu essere quindi descritta della regola:

for([inizializzazione][, inizializzazione] ; [condizione]; [espressione] [,espressione] ) { istruzione1 } istruzione2

6.12 Istruzioni di ramificazione


Il linguaggio Java consente luso di tre parole chiave che consentono di modificare, in qualunque punto del codice, il normale flusso di esecuzione dellapplicazione con effetto sul blocco di codice in esecuzione o sul metodo corrente. Queste parole chiave sono tre (come schematizzato nella tabella seguente) e sono dette istruzioni di branching o ramificazione.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

131

Istruzione break

continue return

Istruzioni di ramificazione Descrizione Interrompe lesecuzione di un ciclo evitando ulteriori controlli sulla espressione condizionale e ritorna il controllo alla istruzione successiva al blocco attuale. Salta un blocco di istruzioni allinterno di un ciclo e ritorna il controllo alla espressione booleana che ne governa lesecuzione. Interrompe lesecuzione del metodo attuale e ritorna il controllo al metodo chiamante.

6.13 Listruzione break


Listruzione break consente di forzare luscita da un ciclo aggirando il controllo sullespressione booleana e provocandone luscita immediata, in modo del tutto simile a quanto gi visto parlando dellistruzione switch. Per comprenderne meglio il funzionamento, esaminiamo il prossimo esempio:
int controllo = 0; while(controllo<=10) { controllo ++; }

Nellesempio, definito il semplice ciclo while utilizzato per sommare 1 alla variabile di controllo stessa, fino a che il suo valore non sia uguale a 10. Lo stesso esempio, pu essere riscritto nel seguente modo utilizzando listruzione break:
int controllo = 0; while(true) { controllo ++; if(controllo==10) break; }

Luso di questistruzione, tipicamente, legato a casi in cui sia necessario poter terminare lesecuzione di un ciclo a prescindere dai valori delle variabili di controllo utilizzate. Queste situazioni occorrono in quei casi in cui impossibile utilizzare un parametro di ritorno come operando allinterno dellespressione booleana che controlla lesecuzione del ciclo, ed pertanto necessario implementare allinterno del blocco meccanismi specializzati per la gestione di questi casi.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

132

Un esempio tipico quello di chiamate a metodi che possono generare eccezioni8, ovvero notificare errori di esecuzione in forma di oggetti. In questi casi utilizzando il comando break, possibile interrompere lesecuzione del ciclo non appena sia catturato lerrore.

6.14 Listruzione continue


A differenza del caso precedente, listruzione continue non interrompe lesecuzione del ciclo di istruzioni, ma al momento della chiamata produce un salto alla parentesi graffa che chiude il blocco restituendo il controllo allespressione booleana che ne determina lesecuzione. Un esempio pu aiutarci a chiarire e idee:
int i=-1; int pairs=0; while(i<20) { i++; if((i%2)!=0) continue; pairs ++; }

Lesempio calcola quante occorrenze di numeri pari ci sono in una sequenza di interi compresa tra 1 e 20 memorizzando il risultato in una variabile di tipo int chiamata pairs. Il ciclo while controllato dal valore della variabile i inizializzata a 1. L'istruzione riportata sulla sesta riga del codice, effettua un controllo sul valore di i: nel caso in cui i rappresenti un numero intero dispari, viene eseguito il comando continue, ed il flusso ritorna alla riga tre. In caso contrario viene aggiornato il valore di pairs. Nella prossima immagine viene schematizzato il diagramma delle attivit del blocco di codice descritto nellesempio.

Le eccezioni verranno trattate in dettaglio a breve.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

133

Figura 51:

Loperazione continue

6.15 Listruzione return


Return, rappresenta lultima istruzione di ramificazione ed utilizzata per terminare lesecuzione del metodo corrente, tornando il controllo al metodo chiamante. Return pu essere utilizzata in due forme:

return valore; return;


La prima forma utilizzata per consentire ad un metodo di ritornare valori al metodo chiamante, e pertanto deve ritornare un valore compatibile con quello dichiarato nella definizione del metodo. La seconda pu essere utilizzata per interrompere lesecuzione di un metodo qualora il metodo ritorni un tipo void.

6.16 Package Java


I package sono raggruppamenti di definizioni di classi sotto uno stesso nome e rappresentano il meccanismo utilizzato da Java per localizzare le definizioni di classi, durante la compilazione o durante lesecuzione di unapplicazione.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

134

Per questo motivo, i package Java possono essere paragonati a quelle che, per altri linguaggi di programmazione, sono chiamate librerie. I package Java organizzano le classi secondo una struttura gerarchica per lassegnamento dei nomi: questa caratteristica impedisce che due programmatori assegnino lo stesso nome a due differenti definizioni di classe. Di fatto, utilizzare i package comporta molti vantaggi:

1 . le classi possono essere mascherate allinterno dei package di appartenenza, facilitando lincapsulamento anche a livello di file; 2. le classi di un package possono condividere dati e metodi con classi di altri package; 3. i package forniscono un meccanismo efficace per distribuire oggetti.
In questo capitolo sar mostrato in dettaglio solamente il meccanismo di raggruppamento. Gli altri aspetti saranno trattati nei capitoli successivi.

6.17 Assegnamento di nomi a package


Nei capitoli precedenti abbiamo affermato che, la definizione della classe MiaClasse deve essere salvata nel file MiaClasse.java e ogni file con estensione .java deve contenere una sola classe. Ogni definizione di classe detta unit di compilazione. I package combinano unit di compilazione in un unico archivio, la cui struttura gerarchica rispetta quella del file system del computer: il nome completo di una unit di compilazione appartenente ad un package determinato dal nome della classe, anteceduto dai nomi dei package appartenenti ad un ramo della gerarchia, separati tra loro dal carattere punto. Ad esempio, se la classe MiaClasse appartiene alla gerarchia definita nella figura seguente, il suo nome completo :

esempi.capitolosei.classes.MiaClasse.
Le specifiche del linguaggio Java identificano alcuni requisiti utili alla definizione dei nomi dei package:

1. I nomi dei package debbono contenere solo caratteri minuscoli; 2 . Tutti i package che iniziano con java. Contengono le classi appartenenti alle Java Core API; 3. Package conteneti classi di uso generale debbono iniziare con il nome della azienda proprietaria del codice.
Una volta definito il nome di un package, affinch ununit di compilazione possa essere archiviata al suo interno, necessario aggiungere unistruzione package allinizio del codice sorgente della definizione di classe contenente il

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

135

nome completo del package che la conterr. Per esempio, allinizio di ogni file c o n t e nent e la d efi n i zi one di una c lasse del pac kage esempi.capitolosei.classes, necessario aggiungere la riga: package esempi.capitolosei.classes; Questa istruzione non deve assolutamente essere preceduta da nessuna linea di codice. In generale, se una classe non viene definita come appartenente ad un package, il linguaggio per definizione assegna la classe ad un particolare package senza nome.

6.18 Creazione dei package


Dopo aver definito il nome di un package, deve essere creata su disco la struttura di cartelle che rappresenti la gerarchia definita dai nomi. Ad esempio, le classi appartenenti al package esempi.capitolosei.classes devono essere memorizzate in una gerarchia di directory che termina con esempi/capitolosei/classes localizzata in qualunque punto del disco (es. C:/esempi/capitolosei/classes schematizzato nella prossima figura).

Figura 52:

definizione di un package

Mediante la variabile di ambiente CLASSPATH sar possibile comunicare alla Java Virtual Machine come poter trovare il package definito inserendo il percorso fino alla cartella di primo livello nella gerarchia dei package. Nel nostro esempio: CLASSPATH =c:\

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

136

6.19 Distribuzione di classi


Organizzare classi Java mediante package non ha come unico beneficio quello di organizzare definizioni di oggetti secondo una struttura logica, piuttosto i package rappresentano un meccanismo efficace per la distribuzione di unapplicazione Java. Per capire le ragioni di questaffermazione, dobbiamo fare un salto indietro nel tempo, a quando gli analisti del Green Group tracciarono le prime specifiche del linguaggio Java. Essendo Java un linguaggio nato per la creazione di applicazioni internet, si rese necessario studiare un meccanismo in grado di ridurre al minimo i tempi di attesa dellutente durante il trasferimento delle classi dal server al computer dellutente. Le prime specifiche del linguaggio, stabilirono che i package Java potessero essere distribuiti in forma di archivi compressi in formato zip, riducendo drasticamente i tempi necessari al trasferimento dei file attraverso la rete. Se allinizio doveva essere uno stratagemma per ridurre le dimensioni dei file, con il passare del tempo la possibilit di distribuire applicazioni Java di tipo enterprise mediante archivi compressi si rivelata una caratteristica alquanto vantaggiosa, tanto da stimolare la SUN nella definizione di un formato di compressione chiamato JAr o Java Archive che, basandosi sullalgoritmo zip, inserisce allinterno dellarchivio compresso un file contente informazioni relative allutilizzo delle definizioni di classe. Creare un archivio secondo questo formato, possibile utilizzando lapplicazione jar.exe che pu essere trovata nella cartella bin allinterno della cartella di installazione dello Java SDK. La applicazione jar.exe pu essere eseguita tramite la riga di comando ed ha la seguente sintassi:

jar [opzioni] [file_manifest] destinazione file_di_input [file_di_input]


Oltre ai file da archiviare definiti dallopzione [file_di_input], lapplicazione jar accetta dalla riga di comando una serie di parametri opzionali ([opzioni] ) necessari al programmatore a modificare le decisioni adottate dalla applicazione durante la creazione dellarchivio compresso. Non essendo scopo del libro quello di scendere nei dettagli di questa applicazione, esamineremo solo le opzioni pi comuni, rimandando alla documentazione distribuita con il Java SDK la trattazione completa del comando in questione. c: crea un nuovo archivio vuoto; v: Genera sullo standard error del terminale un output molto dettagliato. f: Largomento destinazione del comando jar si riferisce al nome dellarchivio jar che deve essere elaborato. Questa opzione indica alla applicazione che destinazione si riferisce ad un archivio che deve essere creato.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

137

x: estrae tutti i file contenuti nellarchivo definito dallargomento identificato da destinazione. Questa opzione indica alla applicazione che destinazione si riferisce ad un archivio che deve essere estratto. Passiamo quindi ad un esempio pratico. Volendo comprimere il package Java creato nellesempio del capitolo precedente, il comando da eseguire dovr essere il seguente:
C:\>jar cvf mattone.jar esempi aggiunto manifesto aggiunta in corso di: esempi/(in = 0) (out= 0)(archiviato 0%) aggiunta in corso di: esempi/capitolosei/(in = 0) (out= 0)(archiviato 0%) aggiunta in corso di: esempi/capitolosei/classes/(in = 0) (out= 0)(archiviato 0%)

Per concludere, necessaria qualche altra informazione relativamente alluso della variabile dambiente CLASSPATH. Nel caso in cui una applicazione Java sia distribuita mediante uno o pi package Java compressi in formato jar, sar comunque necessario specificare alla JVM il nome dellarchivio o degli archivi contenenti le definizioni di classi necessarie. Nel caso dellesempio precedente, la variabile di ambiente CLASSPATH dovr essere impostata nel modo seguente: CLASSPATH =c:\mattone.jar

6.20 Il modificatore public


Le specifiche del linguaggio Java stabiliscono che un oggetto possa essere utilizzato solo da oggetti appartenenti al suo stesso package. Per comprendere meglio il concetto, utilizziamo un esempio concreto definendo due classi: la prima, chiamata Autore appartenente al package mattone.esempi.provapublic.autori . La seconda, chiamata EsempioPublic appartenente al package mattone.esempi.provapublic. Nella figura seguente riportato il component diagram che descrive la struttura dei package che utilizzeremo nel nostro esempio.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

138

Figura 53:

Component Diagram del package mattone.esempi.provapublic

Le due definizioni di classe sono riportate di seguito.


package mattone.esempi.provapublic.autori; class Autore { String Nome() { return "Mario"; } String Cognome() { return "Rossi"; } } package mattone.esempi.provapublic; public class EsempioPublic { mattone.esempi.provapublic.autori.Autore AutoreDelLibro = new Autore(); public void StampaNomeAutore() {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

139

System.out.println(AutoreDelLibro.Nome()); } }

La classe EsempioPublic alloca un oggetto di tipo Autore e nel metodo StampaNomeAutore() visualizza il valore della stringa tornata dal metodo Nome() della classe allocata. Se tentassimo di eseguire il compilatore java, il risultato della esecuzione sarebbe il seguente:
javac -classpath .;.\; mattone/esempi/provapublic/EsempioPublic.java mattone/esempi/provapublic/EsempioPublic.java:5: mattone.esempi.provapublic.autori.Autore is not public in mattone.esempi.provapublic.autori; cannot be accessed from outside package import mattone.esempi.provapublic.autori.Autore; ^ mattone/esempi/provapublic/EsempioPublic.java:7: mattone.esempi.provapublic.autori.Autore is not public in mattone.esempi.provapublic.autori; cannot be accessed from outside package Autore AutoreDelLibro = new Autore(); ^ mattone/esempi/provapublic/EsempioPublic.java:7: mattone.esempi.provapublic.autori.Autore is not public in mattone.esempi.provapublic.autori; cannot be accessed from outside package Autore AutoreDelLibro = new Autore(); ^ mattone/esempi/provapublic/EsempioPublic.java:7: A u t o r e ( ) is not public in mattone.esempi.provapublic.autori.Autore; cannot be accessed from outside package Autore AutoreDelLibro = new Autore(); ^ mattone/esempi/provapublic/EsempioPublic.java:10: Nome() is not public in mattone.esempi.provapublic.autori.Autore; cannot be accessed from outside package System.out.println(AutoreDelLibro.Nome()); ^ 5 errors

Le righe evidenziate segnalano che si sono verificati degli errori, dovuti al fatto che si sta tentando di utilizzare una classe inaccessibile al di fuori del suo package di definizione. Modifichiamo ora la classe Autore aggiungendo il modificare public prima della dichiarazione del nome della classe e dei due metodi membro.
package mattone.esempi.provapublic.autori; public class Autore { public String Nome() { return "Mario"; } public String Cognome() { return "Rossi"; } }

La compilazione ora avr buon fine: la classe autore ora visibile anche al di fuori del suo package di definizione.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

140

Come gi accennato, le specifiche del linguaggio richiedono che il codice sorgente di una classe sia memorizzato in un file avente lo stesso nome della classe (incluse maiuscole e minuscole), ma con estensione .java. Per essere precisi, la regola completa deve essere enunciata come segue:

Il codice sorgente di una classe pubblica deve essere memorizzato in un file avente lo stesso nome della classe (incluse maiuscole e minuscole), ma con estensione .java.
Come conseguenza alla regola, pu esistere solo una classe pubblica per ogni file sorgente. Questa regola rinforzata dal compilatore che scrive il bytecode di ogni classe in un file avente lo stesso nome della classe (incluse maiuscole e minuscole), ma con estensione .class. Lo scopo di questa regola quello di semplificare la ricerca di sorgenti da parte del programmatore, e quella del bytecode da parte della JVM. Supponiamo, come esempio, di aver definito le tre classi A, B e C in un file unico. Se A fosse la classe pubblica (solo una lo pu essere), il codice sorgente di tutte e tre le classi dovrebbe trovarsi allinterno del file A.java. Al momento della compilazione del file A.java, il compilatore creer una classe per ogni definizione contenuta nel file: A.class, B.class e C.class . Questa impostazione, per quanto bizarra ha un senso logico. Se come detto, la classe pubblica lunica a poter essere eseguita da altre classi allesterno del package, le classi B e C rappresentano solo limplementazione di dettagli e quindi, non necessarie al di fuori del package. Per concludere non mi resta che ricordare che, anche se una classe non pubblica pu essere definita nello stesso file di una classe pubblica, questo, non solo non strettamente necessario, ma dannoso dal punto di vista della leggibilit del codice sorgente. Sar comunque compito del programmatore scegliere in che modo memorizzare le definizioni delle classi allinterno di un package.

6.21 Listruzione import


Il runtime di Java fornisce un ambiente completamente dinamico. Di fatto, le classi non sono caricate fino a che non sono referenziate per la prima volta durante lesecuzione dellapplicazione. Questo meccanismo consente di ricompilare singole classi senza dover necessariamente ricaricare intere applicazioni. Poich il bytecode di ogni definizione di classe Java memorizzato in un unico file avente lo stesso nome della classe, ma con estensione .class, la virtual machine pu trovare i file binari appropriati cercando nelle cartelle specificate nella variabile di ambiente CLASSPATH; inoltre, poich le classi possono essere organizzate in package, necessario specificare a quale package una classe appartenga pena lincapacit della virtual machine di trovarla.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

141

Un modo per indicare il package di appartenenza di una classe, quello di specificarne il nome ad ogni chiamata alla classe. In questo caso diremo che stiamo utilizzando nomi qualificati9, come mostrato nel codice sorgente della classe EsempioPublic che fa riferimento alla classe Autore utilizzandone il nome qualificato mattone.esempi.provapublic.autori.Autore.
package mattone.esempi.provapublic; public class EsempioPublic { mattone.esempi.provapublic.autori.Autore AutoreDelLibro = new Autore(); public void StampaNomeAutore() { System.out.println(AutoreDelLibro.Nome()); } }

Luso di nomi qualificati non sempre comodo, soprattutto quando i package sono organizzati con gerarchie a molti livelli. Per venire incontro al programmatore, Java consente di specificare una volta per tutte il nome qualificato di una classe allinizio del file contenente la definizione di classe, utilizzando la parola chiave import. Listruzione import ha come unico effetto quello di identificare univocamente una classe e quindi di consentire al compilatore di risolvere nomi di classe senza ricorrere ogni volta a nomi qualificati. Utilizzando questa istruzione, la classe EsempioPublic pu essere riscritta nel modo seguente:
import mattone.esempi.provapublic.autori.Autore; package mattone.esempi.provapublic; public class EsempioPublic { Autore AutoreDelLibro = new Autore(); public void StampaNomeAutore() { System.out.println(AutoreDelLibro.Nome()); } }

La classe sar in grado di risolvere il nome di Autore ogni volta che sia necessario, semplicemente utilizzando il nome di classe Autore. Capita spesso di dover per utilizzare un gran numero di classi appartenenti ad un unico package. Per questi casi listruzione import supporta luso del carattere fantasma * che identifica tutte le classi pubbliche appartenenti ad un package.
import mattone.esempi.provapublic.autori.*;

Tecnicamente, in Java un nome qualificato un nome formato da una serie di identificatori separati da punto per identificare univocamente una classe.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

142

package mattone.esempi.provapublic; public class EsempioPublic { Autore AutoreDelLibro = new Autore(); public void StampaNomeAutore() { System.out.println(AutoreDelLibro.Nome()); }

La sintassi dellistruzione import, non consente altre forme e non pu essere utilizzata per caricare solo porzioni di package. Per esempio, la forma
import mattone.esempi.provapublic.autori.Au*;

non consentita.

7 INCAPSULAMENTO
7.1 Introduzione
Lincapsulamento di oggetti il processo di mascheramento dei dettagli implementativi di un oggetto ad altri oggetti, con lo scopo di proteggere porzioni di codice o dati critici. I programmi scritti con questa tecnica, risultano molto pi leggibili e limitano i danni dovuti alla propagazione di errori od anomalie all'interno dellapplicazione. Unanalogia con il mondo reale rappresentata dalle carte di credito. Chiunque sia dotato di carta di credito pu eseguire determinate operazioni bancarie attraverso lo sportello elettronico. Una carta di credito, non mostra allutente gli automatismi necessari a mettersi in comunicazione con lente bancario o quelli necessari ad effettuare transazioni sul conto corrente, semplicemente si limita a farci prelevare la somma richiesta tramite uninterfaccia utente semplice e ben definita. In altre parole, una carta di credito maschera il sistema allutente che potr prelevare denaro semplicemente conoscendo luso di pochi strumenti come la tastiera numerica ed il codice pin. Limitando luso della carta di credito ad un insieme limitato di operazioni, si pu: primo, proteggere il nostro conto corrente. Secondo, impedire allutente di modificare in modo irreparabile i dati della carta di credito o addirittura dellintero sistema. Uno degli scopi primari di un disegno Object Oriented, dovrebbe essere proprio quello di fornire allutente un insieme di dati e metodi che danno il senso delloggetto in questione. Questo possibile farlo senza esporre le modalit con cui loggetto tiene traccia dei dati ed implementa il corpo (metodi) delloggetto. Nascondendo i dettagli, possiamo assicurare a chi utilizza loggetto che ci che sta utilizzando sempre in uno stato consistente a meno di errori di programmazione delloggetto stesso. Uno stato consistente uno stato permesso dal disegno di un oggetto.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

143

E per importante notare che uno stato consistente non corrisponde sempre a quanto aspettato dallutente delloggetto. Se infatti lutente trasmette alloggetto parametri errati, loggetto si trover in uno stato consistente, ma non in quello desiderato.

7.2

Modificatori public e private

Java fornisce supporto per lincapsulamento a livello di linguaggio, tramite i modificatori public e private da utilizzare al momento della dichiarazione di variabili e metodi. I membri di una classe, o lintera classe, se definiti public sono liberamente accessibili da ogni oggetto componente lapplicazione. I membri di una classe definiti private possono essere utilizzati sono dai membri della stessa classe. I membri privati mascherano i dettagli dellimplementazione di una classe. Membri di una classe non dichiarati public o private sono per definizione accessibili solo alle classi appartenenti allo stesso package. Questi membri o classi sono comunemente detti package friendly.

Modificatori public e private

7.3

Il modificatore private

Il modificatore private realizza incapsulamento a livello di definizione di classe e serve a definire membri che devono essere utilizzati solo da altri membri della stessa classe di definizione. Di fatto, lintento di nascondere porzioni di codice della classe che non devono essere utilizzati da altre classi.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

144

Un membro privato pu essere utilizzato da un qualsiasi membro statico, e non, della stessa classe di definizione con laccorgimento che i membri statici possono accedere solamente ad altri membri statici od oggetti di qualunque tipo purch esplicitamente passati per parametro. Per dichiarare un membro privato si utilizza la parola chiave private anteposta alla dichiarazione di un metodo o di un dato:

private tipo nome;


Oppure, nel caso di metodi:

private tipo_di_ritorno nome(tipo identificatore [,tipo identificatore] ) { istruzione [istruzione] }

7.4

Il modificatore public

Il modificatore public consente di definire classi o membri di una classe visibili a qualsiasi oggetto definito allinterno dello stesso package e non. Questo modificatore deve essere utilizzato per definire linterfaccia che loggetto mette a disposizione dellutente. Tipicamente metodi membro public utilizzano membri private per implementare le funzionalit delloggetto. Per dichiarare una classe od un membro pubblico si utilizza la parola chiave public anteposta alla dichiarazione :

private tipo nome;


Oppure, nel caso di metodi:

private tipo_di_ritorno nome(tipo identificatore [,tipo identificatore] ) { istruzione [istruzione] }


Infine, nel caso di classi:

public class Nome { dichirazione_dei_dati dichirazione_dei_metodi

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

145

7.5

Il modificatore protected

Un altro modificatore messo a disposizione dal linguaggio Java protected. I membri di una classe dichiarati protected possono essere utilizzati sia dai membri della stessa classe che da altre classi purch appartenenti allo stesso package. Per dichiarare un membro protected si utilizza la parola chiave protected anteposta alla dichiarazione :

protected tipo nome;


Oppure, nel caso di metodi:

protected tipo_di_ritorno nome(tipo identificatore [,tipo identificatore] ) { istruzione [istruzione] }


Nonostante possa sembrare un modificatore ridondante rispetto ai precedenti due, tuttavia di questo modificatore torneremo a parlarne nei dettagli nel prossimo capitolo dove affronteremo il problema della ereditariet.

Figura 54:

Modificatore protected

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

146

7.6

Un esempio di incapsulamento

Le classi che utilizzeremo nel prossimo esempio sono rappresentate nella figura successiva che ne rappresenta il class-diagram. La classe Impiegato contiene la definizione di due dati membro privati, chiamati nome e affamato, e di tre metodi pubblici chiamati haiFame(), nome(), vaiAPranzo(). La classe D a t o r e D i L a v o r o contiene lunica definizione del metodo membro siiCorrettoConImpiegato() di tipo public. Per identificare la visibilit di un metodo od un dato membro, il linguaggio UML antepone al nome, i caratteri - e + rispettivamente per identificare membri private e public. Il nome di ogni membro seguito dal carattere : e di seguito il tipo del membro.

Figura 55:

Class Diagram Datore-Impiegato

/** * Definisce il concetto di Impiegato. * Un datore di lavoro non pu cambiare il nome di un ipiegato ne, * pu evitare che, se affamato, l'piegato vada a pranzo */
package src.esercizi.modificatori; public class Impiegato { private String nome = "Massimiliano"; private boolean affamato=true; public boolean haiFame() { return affamato; } public String nome() { return nome; } public void vaiAPranzo(String luogo)

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

147

{ } }

// mangia affamato = false;

/** * Un datore di lavoro che voglia * essere corretto con un suo impiegato, * deve mandarlo a pranzo quando quest'ultimo affamato. */
package src.esercizi.modificatori; public class DatoreDiLavoro { public void siiCorrettoConImpiegato(Impiegato impiegato) { if (impiegato.haiFame()) { impiegato.vaiAPranzo("Ristorante sotto l''ufficio"); } } }

Aver definito i dati nome e affamato, membri privati della classe Impiegato, previene la lettura o, peggio, la modifica del valore dei dati da parte di DatoreDiLavoro. Daltra parte, la classe dotata di metodi pubblici (haiFame() e nome()), che consentono ad altre classi di accedere al valore dei dati privati. Nel codice sorgente, luso dei modificatori crea una simulazione ancora pi realistica, limitando lazione di DatoreDiLavoro alle attivit schematizzate nel prossimo sequenze-diagram: un datore di lavoro non pu cambiare il nome di un impiegato, ne pu convincerlo di non avere fame arrivata lora di pranzo.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

148

Figura 56:

Sequenze Diagram Datore Impiegato

7.7

Loperatore new

Abbiamo gi accennato che, per creare un oggetto dalla sua definizione di classe, Java mette a disposizione loperatore new responsabile del caricamento delloggetto in memoria e della successiva creazione di un riferimento (indirizzo di memoria), che pu essere memorizzato in una variabile di tipo reference ed utilizzato per accederne ai membri. Questoperatore quindi paragonabile alla malloc in C, ed identico al medesimo operatore in C++. La responsabilit del rilascio della memoria allocata per loggetto non pi in uso del il Garbage Collector. Per questo motivo, a differenza di C++ Java non prevede nessun meccanismo esplicito per distruggere un oggetto creato. Quello che non abbiamo detto che, questo operatore ha la responsabilit di consentire lassegnamento dello stato iniziale delloggetto allocato. Di fatto, la sintassi delloperatore new prevede un tipo seguito da un insieme di parentesi. Le parentesi indicano che, al momento della creazione in memoria delloggetto verr chiamato un metodo speciale detto costruttore, responsabile proprio della inizializzazione del suo stato. Le azioni compiute da questo operatore, schematizzate nella prossima figura, sono le seguenti:

1 . Richiede alla JVM di caricare la definizione di classe utilizzando le informazioni memorizzate nella variabile dambiente CLASSPATH.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

149

Terminata questoperazione, stima la quantit memoria necessaria a contenere loggetto e chiede alla JVM di riservarla. La variabile reference this non ancora inizializzata. 2 . Esegue il metodo costruttore delloggetto caricato per consentirne linizializzazione dei dati membro. Inizializza la variabile reference this. 3 . Restituisce il riferimento alla locazione di memoria allocata per loggetto. Utilizzando loperatore di assegnamento possibile memorizzare il valore restituito in una variabile reference dello stesso tipo delloggetto caricato.

Figura 57:

Loperatore new

7.8

Metodi costruttori

Tutti i programmatori, esperti e non, conoscono il pericolo costituito da una variabile non inizializzata. In unapplicazione Object Oriented, un oggetto unentit pi complessa di un tipo primitivo e lerrata inizializzazione dello stato di un oggetto pu essere causa della terminazione prematura dellapplicazione o della generazione di errori intermittenti difficilmente controllabili. In molti altri linguaggi di programmazione, il responsabile dellinizializzazione delle variabili il programmatore. In Java, questo impossibile poich potrebbero essere membri privati di un oggetto, e quindi inaccessibili allutente. I costruttori sono metodi speciali chiamati in causa dalloperatore new al momento della creazione di un nuovo oggetto e servono ad impostarne lo stato iniziale. Questi metodi, hanno lo stesso nome della classe di cui sono membri e non restituiscono nessun tipo (void compreso). Sfogliando a ritroso tra gli esempi precedenti, dovrebbe sorgere spontaneo chiedersi come mai non abbiamo mai definito il metodo costruttore delle classi. Di fatto, una

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

150

classe non mai sprovvista di costruttore. Nel caso in cui il costruttore non sia definito dallutente, uno costruttore speciale che non fa nulla, verr aggiunto dal compilatore Java al momento della creazione del bytecode. Dal momento che Java garantisce lesecuzione del metodo costruttore di un nuovo oggetto, un costruttore scritto intelligentemente garantisce che tutti i dati membro vengano inizializzati. Nella nuova versione della classe Impiegato, il costruttore viene dichiarato esplicitamente dal programmatore e si occupa di impostare lo stato iniziale dei dati membro privati:
/** * Definisce il concetto di Impiegato. * Un datore di lavoro non pu cambiare il nome di un ipiegato ne, * pu evitare che, se affamato, l'piegato vada a pranzo */
package src.esercizi.modificatori; public class Impiegato { private String nome; private boolean affamato; public Impiegato() { affamato=true; nome=Massimiliano; } public boolean haiFame() { return affamato; } public String nome() { return nome; } public void vaiAPranzo(String luogo) { afamato = false; } }

7.9

Un esempio di costruttori

In questesempio, modificheremo la definizione delloggetto Pila inserendo il metodo costruttore che, assegna il valore 10 al dato membro privato dimensionemassima che rappresenta il numero massimo di elementi contenuti nella Pila, inizializza il dato che tiene traccia della cima della pila, ed infine crea larray che conterr i dati inseriti.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

151

package src.esercizi.costruttori;

/** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO * @version 0.1 */
public class Pila { private int[] dati; private int cima; private int dimensionemassima;

/** * Il metodo costruttore della classe Pila, non ritorna nessun tipo * di dato, imposta la dimensione massima dell'array che * conterr i dati della pila e crea l'array. */
public Pila() { dimensionemassima = 10; dati = new int[dimensionemassima]; cima=0; }

/** * Il metodo push ritorna un tipo void e prende come * parametro un numero intero da inserire sulla cima della pila * @return void * @param int dato : elemento da inserire sulla cima della pila */
public void push(int dato) { if(cima < dimensionemassima) { dati[cima] = dato; cima ++; } }

/** * il metodo pop non accetta parametri e restituisce * lelemento sulla cima della Pila * @return int : dato sulla cima della pila */
public int pop() { if(cima > 0)

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

152

{ cima--; return dati[cima]; } return 0; // Bisogna tornare qualcosa } }

Rispetto alla prima definizione della classe Pila, non pi necessario creare larray di interi al momento della prima chiamata al metodo push(int) rendendo di conseguenza inutile il controllo sullo stato dellarray ad ogni sua chiamata:
if (data == null) { first = 0; data = new int[20]; }

Di fatto, utilizzando il costruttore saremo sempre sicuri che lo stato iniziale della classe correttamente impostato.

7.10 Overloading dei costruttori


Java supporta molte caratteristiche per i costruttori, ed esistono molte regole per la loro creazione. In particolare, al programmatore consentito scrivere pi di un costruttore per una data classe, secondo le necessit di disegno delloggetto. Questa caratteristica permette di passare alloggetto diversi insiemi di dati di inizializzazione, consentendo di adattarne lo stato ad una particolare situazione operativa. Nellesempio precedente, abbiamo ridefinito loggetto Pila affinch contenga un massimo di dieci elementi. Un modo per generalizzare loggetto definire un costruttore che, prendendo come parametro un intero, inizializza la dimensione massima della Pila secondo le necessit dellapplicazione. La nuova definizione della classe potrebbe essere la seguente:
package src.esercizi.costruttori;

/** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO * @version 0.1 */
public class Pila { private int[] dati; private int cima; private int dimensionemassima;

/** * Il metodo costruttore della classe Pila, non ritorna nessun tipo * di dato, imposta la dimensione massima dell'array che

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

153

* conterr i dati della pila e crea l'array. */


public Pila() { dimensionemassima = 10; dati = new int[dimensionemassima]; cima=0; }

/** * Questo metodo costruttore della classe Pila, * consente di impostare la dimensione massima della Pila * accettando un parametro di tipo int */
public Pila(int dimensionemassima) { this.dimensionemassima = dimensionemassima; dati = new int[dimensionemassima]; cima=0; }

/** * Il metodo push ritorna un tipo void e prende come * parametro un numero intero da inserire sulla cima della pila * @return void * @param int dato : elemento da inserire sulla cima della pila */
public void push(int dato) { if(cima < dimensionemassima) { dati[cima] = dato; cima ++; } }

/** * il metodo pop non accetta parametri e restituisce * lelemento sulla cima della Pila * @return int : dato sulla cima della pila */
public int pop() { if(cima > 0) { cima--; return dati[cima]; } return 0; // Bisogna tornare qualcosa } }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

154

Lutilizzo dei due costruttori, ci consente di creare oggetti Pila di dimensioni fisse utilizzando il primo costruttore, o variabili utilizzando il costruttore che prende come parametro di input un tipo int.
/** * Questo metodo costruttore della classe Pila, * consente di impostare la dimensione massima della Pila * accettando un parametro di tipo int */
public Pila(int dimensionemassima) { this.dimensionemassima = dimensionemassima; dati = new int[dimensionemassima]; cima=0; }

Per creare una istanza della classe Pila invocando il nuovo costruttore, baster utilizzare loperatore new come segue:

int dimensioni=10; Stack s = new Stack(dimensioni);

In definitiva, la sintassi completa delloperatore new la seguente:

new Nome([lista_parametri]) ;

7.11 Restrizione sulla chiamata ai costruttori


Java consente una sola chiamata al costruttore di una classe. Di fatto, un metodo costruttore pu essere invocato solo dalloperatore new al momento della creazione di un oggetto. Nessun metodo costruttore pu essere eseguito nuovamente dopo la creazione delloggetto. Il frammento seguente di codice Java, produrr un errore di compilazione.
int dimensioni=10; Pila s = new Pila(dimensioni); //Questa chiamata illegale Pila.Stack(20);

7.12 Chiamate incrociate tra costruttori


Un metodo costruttore ha la possibilit di effettuare chiamate ad altri costruttori appartenenti alla stessa definizione di classe. Questo meccanismo utile perch i costruttori generalmente hanno funzionalit simili e un

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

155

costruttore che assegna alloggetto uno stato comune, potrebbe essere richiamato da un altro per sfruttare il codice definito nel primo. Per chiamare un costruttore da un altro, necessario utilizzare la sintassi speciale:

this(lista_dei_parametri);
dove, lista_dei_parametri rappresenta la lista di parametri del costruttore che si intende chiamare. Una chiamata incrociata tra costruttori, deve essere la prima riga di codice del costruttore chiamante. Qualsiasi altra cosa sia fatta prima, compresa la definizione di variabili, non consente di effettuare tale chiamata. Il costruttore corretto determinato in base alla lista dei parametri. Java paragona lista_dei_parametri con la lista dei parametri di tutti i costruttori della classe. Tornando alla definizione di Pila, notiamo che i due costruttori eseguono operazioni simili. Per ridurre la quantit di codice, possiamo chiamare un costruttore da un altro come mostraton el prossimo esempio.
package src.esercizi.costruttori;

/** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO * @version 0.1 */
public class Pila { private int[] dati; private int cima; private int dimensionemassima;

/** * Il metodo costruttore della classe Pila, non ritorna nessun tipo * di dato, imposta la dimensione massima dell'array che * conterr i dati della pila e crea l'array. */
public Pila() { this(10); }

/** * Questo metodo costruttore della classe Pila, * consente di impostare la dimensione massima della Pila * accettando un parametro di tipo int */
public Pila(int dimensionemassima) { this.dimensionemassima = dimensionemassima; dati = new int[dimensionemassima]; cima=0; }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

156

/** * Il metodo push ritorna un tipo void e prende come * parametro un numero intero da inserire sulla cima della pila * @return void * @param int dato : elemento da inserire sulla cima della pila */
public void push(int dato) { if(cima < dimensionemassima) { dati[cima] = dato; cima ++; } }

/** * il metodo pop non accetta parametri e restituisce * lelemento sulla cima della Pila * @return int : dato sulla cima della pila */
public int pop() { if(cima > 0) { cima--; return dati[cima]; } return 0; // Bisogna tornare qualcosa } }

7.13 Un nuovo esempio


Nel prossimo esempio, definiremo un oggetto di tipo Insiema che, rappresenta un insieme di numeri interi assicurandoci di incapsulare loggetto utilizzando i modificatori public o private appropriati. Linsieme deve avere al massimo tre metodi: 1. boolean membro(int valore): ritorna true se il numero nellinsieme; 2. void aggiungi (int valore) : aggiunge un numero allinsieme ; 3. void stampa(): stampa a video il contenuto dellinsieme nel formato {elemento, elemento, .... , elemento} Di seguito, una possibile soluzione allesercizio proposto.
package src.esercizi.costruttori;

/** * Questa classe e' la definizione * dell'oggetto che rappresenta

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

157

* un insiema di numeri interi. */


public class Insieme { public Insieme() { setDimensioneCorrente(0); elementi = new int[100]; } private int getDimensioneCorrente(){ return dimensioneCorrente; } private void setDimensioneCorrente(int dimensioneCorrente){ this.dimensioneCorrente = dimensioneCorrente; }

/** * Verica l'appartenenza del numero intero passato come * parametro all'insieme corrente, tornando true se elemento * appartiene all'insieme. * @param int elemento : elemento di cui verificare l'appartenenza all'insieme. * @return boolean : true se elemento appartiene all'insieme, false altrimenti */
public boolean membro(int elemento) { int indice = 0; while(indice < getDimensioneCorrente()) { if(elementi[indice]==elemento) return true; indice++; } return false; }

/** * Produce la stampa dell'insieme su terminale * nella foema {elemento, elemento, ...., elemento} * @return void */
public void stampa() { int indice = 0; System.out.print("{"); while(indice < getDimensioneCorrente()) { System.out.println(elementi[indice] +" , "); indice++; } System.out.print("}"); }

/** * Aggiunge un elemento all'insieme corrente. * L'elemento non deve essere contenuto * nell'insieme corrente.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

158

* @return void * @param int elemento : elemento da ggiungere all'insieme corrente. L'elemento viene inserito solo se non risulta gi contenuto all'interno dell'insieme. */
public void aggiungi(int elemento) { //Verifica l'appartenenza dell'elemento all'insieme. //Se l'elemento appartiene all'insieme, il metodo //ritorna senza eseguire alcuna operazione. if (!membro(elemento)) { //Se l'array pieno il metodo ritorna senza //eseguire alcuna operazione. if(getDimensioneCorrente() == elementi.length) return;

//Aggiorno l'elemento all'insieme


elementi[getDimensioneCorrente()] = elemento;

//Aggiorno il valore di DimensioneCorrente


setDimensioneCorrente(getDimensioneCorrente()+1); } }

/** * Array contenente la lista degli * elementi inseriti all'interno dell'insieme. */


private int[] elementi;

/** * Propriet che rappresenta le * dimensioni correnti dell'insieme */ private int dimensioneCorrente;
}

Utilizzando il modificatore private, dichiariamo i due dati membro della nostra classe rendendoli visibili soltanto ai metodi membri della classe. Il primo, int elemento[], rappresenta un array di interi che conterr i dati memorizzati allinterno dellinsieme. Il secondo, int dimensioneCorrente, rappresenta la dimensione o cardinalit attuale dellinsieme. Focalizziamo per qualche istante lattenzione del dato membro dimensioneCorrente. Allinterno della definizione della classe Insieme, sono definiti i due metodi
private int getDimensioneCorrente(){ return dimensioneCorrente; }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

159

private void setDimensioneCorrente(int dimensioneCorrente){ this.dimensioneCorrente = dimensioneCorrente; }

detti rispettivamente metodi getter e setter. Il primo, responsabile della restituzione del valore rappresentato dal dato membro dimensioneCorrente; il secondo invece responsabile dellaggiornamento del valore del dato. In generale, un dato affiancato da due metodi getter e setter detto propriet di una classe Java. Le regole per definire la propriet di una classe sono le seguenti:

1. La propriet un qualsiasi dato che rappresenta una classe od un tipo semplice. In genere, tali membri sono dichiarati come privati. 2 . Il nome del metodo getter determinato dal nome del dato anteposto dalla stringa get; 3. Il nome del metodo setter dal nome del dato anteposto dalla stringa set. 4. I metodi getter e setter possono essere dichiarati pubblici o privati.
In un class-diagram UML, la propriet di una classe rappresentata dal nome della variabile. I metodi getter e setter non compaiono allinterno del simbolo che rappresenta la classe.

Figura 58:

Class Diagram della classe Insieme

Aver dichiarato private i due dati membro, abbiamo incapsulato allinterno della classe i dettagli dellimplementazione dellinsieme, evitando errori dovuti allinserimento diretto di elementi allinterno dellarray. Se, infatti, ci fosse possibile, potremmo scrivere il metodo main dellapplicazione affinch inserisca direttamente gli elementi dellinsieme allinterno dellarray come da esempio seguente:
public class TestInsieme

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

160

{ public static void main(String args[]) { Insieme insieme = new Insieme (); //la prossima chiamata illegale Insieme.elementi[1]=0; } }

In questo caso, non sarebbe aggiornata la cardinalit dellinsieme rendendo inconsistente lo stato delloggetto, o ancora peggio, potremmo sovrascrivere elementi dellinsieme gi esistenti. I tre metodi dichiarati pubblici, rappresentano linterfaccia che il programmatore ha a disposizione per utilizzare in modo corretto linsieme di interi.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

161

8 EREDITARIET
8.1 Introduzione

Lereditariet la caratteristica dei linguaggi Object Oriented che consente di utilizzare definizioni di classe come base per la definizione di nuove che ne specializzano il concetto. Lereditariet inoltre, offre un ottimo meccanismo per aggiungere funzionalit ad un programma con rischi minimi nei confronti di quelle gi esistenti, nonch un modello concettuale che rende un programma Object Oriented auto-documentante rispetto ad un analogo scritto con linguaggi procedurali. Per utilizzare correttamente lereditariet, il programmatore deve conoscere a fondo gli strumenti forniti in supporto dal linguaggio. Questo capitolo introduce al concetto di ereditariet in Java, alla sintassi per estendere classi, alloverloading e overriding di metodi. Infine, in questo capitolo introdurremo ad una particolarit rilevante del linguaggio Java che, include sempre la classe Object nella gerarchia delle classi definite dal programmatore.

8.2

Disegnare una classe base

Disegnando una classe, dobbiamo sempre tenere a mente che, con molta probabilit, ci sar qualcuno che in seguito potrebbe aver bisogno di utilizzarla tramite il meccanismo di ereditariet. Ogni volta che si utilizza una classe per ereditariet, ci si riferisce a questa come alla classe base o superclasse. Il termine ha come significato che la classe stata utilizzata come fondamenta per una nuova definizione. Quando definiamo nuovi oggetti utilizzando lereditariet, tutte le funzionalit della classe base sono trasferite alla nuova classe detta classe derivata o sottoclasse. Facendo uso del meccanismo della ereditariet, necessario tener sempre ben presente alcuni concetti. Lereditariet consente di utilizzare una classe come punto di partenza per la scrittura di nuove classi. Questa caratteristica pu essere vista come una forma di riciclaggio del codice: i membri della classe base sono concettualmente copiati nella nuova classe. Come conseguenza diretta, lereditariet consente alla classe base di modificare la superclasse. In altre parole, ogni aggiunta o modifica ai metodi della superclasse, sar applicata solo alla classe derivata. La classe base sar quindi protetta dalla generazione di nuovi eventuali errori, che rimarranno circoscritti alla classe derivata. La classe derivata per ereditariet, supporter tutte le caratteristiche della classe base. In definitiva, tramite questa tecnica possibile creare nuove variet di entit gi definite mantenendone tutte le caratteristiche e le funzionalit. Questo significa che se una applicazione in grado di utilizzare una classe base, sar in grado di utilizzarne la derivata allo stesso modo. Per questi motivi,

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

162

importante che una classe base rappresenti le funzionalit generiche delle varie specializzazioni che andremo a definire. Il modello di ereditariet proposto da Java un detto di "ereditariet singola". A differenza da linguaggi come il C++ in cui una classe derivata pu ereditare da molte classi base (ereditariet multipla), Java consente di poter ereditare da una sola classe base come mostrato. Nelle due prossime figure sono illustrati rispettivamente i due modelli di ereditariet multipla e singola.

Figura 59:

Modello ad ereditariet multipla

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

163

Figura 60:

Modello ad ereditariet singola in Java

Proviamo a disegnare una classe base e per farlo, pensiamo ad un veicolo generico: questo potr muoversi, svoltare a sinistra o a destra o fermarsi. Di seguito sono riportate le definizioni della classe base Veicolo, e della classe contenete il metodo main dellapplicazione Autista. Nelle due prossime figure sono schematizzati rispettivamente il class-diagram della applicazione ed il relativo sequenze-diagram.

Figura 61:

Class-Diagram di autista

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

164

Figura 62:

Sequenze-Diagram di autista

package src.esercizi.ereditarieta; public class Autista { public static void main(String args[]) { Veicolo v = new Veicolo(); v.muovi(); v.sinistra(); v.diritto(); v.ferma(); } } package src.esercizi.ereditarieta; public class Veicolo { /** * Nome del veicolo */ String nome;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

165

/** * velocit del veicolo espressa in Km/h */ int velocita; /** * Direzione di marcia del veicolo */ int direzione; /** * Costante che indica la direzione "dritta" di marcia del veicolo */ final static int DRITTO = 0; /** * Costante che indica la svola a sinistra del veicolo */ final static int SINISTRA = -1; /** * Costante che indica la svola a destra del veicolo */ final static int DESTRA = 1;
public Veicolo() { velocita = 0; direzione = DRITTO; nome = "Veicolo generico"; }

/** * Simula la messa in marcia del veicolo alla velocit di 1 km/h */ public void muovi() { velocita = 1; System.out.println(nome + " si sta movendo a: " + velocita + " Kmh"); } /** * Simula la frenata del veicolo */ public void ferma() { velocita = 0; System.out.println(nome + " si fermato"); } /** * Simula la svola a sinistra del veicolo */ public void sinistra() { direzione = SINISTRA; System.out.println(nome + " ha sterzato a sinistra"); } /** * Simula la svola a destra del veicolo */ public void destra() {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

166

direzione = DESTRA; System.out.println(nome + " ha sterzato a destra"); }

/** * Simula la conclusione di una svola a sinistra o a destra del veicolo */ public void diritto() { direzione = DRITTO; System.out.println(nome + " sta procedendo in linea retta"); }
}

8.3

Overload di metodi

Per utilizzare a fondo lereditariet, necessario introdurre unaltra importante caratteristica di Java: quella di consentire loverloading di metodi. Fare loverloading di un metodo significa, in generale, dotare una classe di metodi aventi stesso nome ma con parametri differenti. Esaminiamo il metodo muovi() della classe Veicolo definito nellesempio precedente: il metodo simula la messa in moto del veicolo alla velocit di 1 Km/h. Apportiamo ora qualche modifica alla definizione di classe:
package src.esercizi.ereditarieta; public class Veicolo { /** * Nome del veicolo */ String nome;

/** * velocit del veicolo espressa in Km/h */ int velocita; /** * Direzione di marcia del veicolo */ int direzione; /** * Costante che indica la direzione "dritta" di marcia del veicolo */ final static int DRITTO = 0; /** * Costante che indica la svola a sinistra del veicolo */ final static int SINISTRA = -1; /** * Costante che indica la svola a destra del veicolo */ final static int DESTRA = 1;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

167

public Veicolo() { velocita = 0; direzione = DRITTO; nome = "Veicolo generico"; }

/** * Simula la messa in marcia del veicolo alla velocit di 1 km/h */ public void muovi() { velocita = 1; System.out.println(nome + " si sta movendo a: " + velocita + " Kmh"); } /** Simula la messa in marcia del veicolo specificando la velocit in km/h * @return void * @param int velocita : velocita del veicolo */
public void muovi(int velocita) { this.velocita = velocita; System.out.println(nome + " si sta movendo a: " + velocita + " Kmh"); }

/** * Simula la frenata del veicolo */ public void ferma() { velocita = 0; System.out.println(nome + " si fermato"); } /** * Simula la svola a sinistra del veicolo */ public void sinistra() { direzione = SINISTRA; System.out.println(nome + " ha sterzato a sinistra"); } /** * Simula la svola a destra del veicolo */ public void destra() { direzione = DESTRA; System.out.println(nome + " ha sterzato a destra"); } /** * Simula la conclusione di una svola a sinistra o a destra del veicolo */ public void diritto() { direzione = DRITTO; System.out.println(nome + " sta procedendo in linea retta"); }
}

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

168

Avendo a disposizione anche il metodo muovi(int velocita), possiamo migliorare la nostra simulazione facendo in modo che il veicolo possa accelerare o decelerare ad una determinata velocit. Loverloading di metodi possibile poich, il nome di un metodo, non definisce in maniera univoca un membro di una classe. Ci che consente di determinare in maniera univoca quale sia il metodo correntemente chiamato di una classe Java la sua firma (signature). In dettaglio, si definisce firma di un metodo, il suo nome assieme alla lista dei suoi parametri. Per concludere, ecco alcune linee guida per utilizzare correttamente loverloading di metodi. Primo, come conseguenza diretta della definizione precedente, non possono esistere due metodi aventi nomi e lista dei parametri contemporaneamente uguali. Secondo, i metodi di cui si fatto loverloading devono implementare vari aspetti di una medesima funzionalit. Nellesempio, aggiungere un metodo muovi() che provochi la svolta della macchina non avrebbe senso.

8.4

Estendere una classe base

Definita la classe base Veicolo , sar possibile definire nuovi tipi di veicoli estendendo la classe generica. La nuova classe, manterr tutti i dati ed i metodi membro della superclasse, con la possibilit di aggiungerne di nuovi o modificare quelli esistenti. La sintassi per estendere una classe a partire dalla classe base la seguente:

class nome(lista_parametri) extends nome_super_classe


Lesempio seguente mostra come creare un oggetto Macchina a partire dalla classe base Veicolo.
package src.esercizi.ereditarieta; public class Macchina extends Veicolo { public Macchina() { velocita=0; direzione = DRITTO; nome = "Macchina"; } }

package src.esercizi.ereditarieta; public class Autista { public static void main(String args[]) {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

169

Macchina fiat = new Macchina(); fiat.muovi(); fiat.sinistra(); fiat.diritto(); fiat.ferma(); } }

Estendendo la classe Veicolo, ne ereditiamo tutti i dati membro ed i metodi. Lunico cambiamento che abbiamo dovuto apportare quello di creare un costruttore specializzato per la nuova classe. Il nuovo costruttore semplicemente modifica il contenuto della variabile nome affinch lapplicazione stampi i messaggi corretti. Come mostrato nel codice della nuova definizione di classe per Autista , utilizzare il nuovo veicolo equivale ad utilizzare il Veicolo generico definito nei paragrafi precedenti.

8.5

Ereditariet ed incapsulamento

Nasce spontaneo domandarsi quale sia leffetto dei modificatori public, private, e protected definiti nel capitolo precedente, nel caso di classi legate tra loro da relazioni di ereditariet. Nella tabella seguente sono schematizzati i livelli di visibilit dei tre modificatori. Modificatori ed ereditariet Visibilit con le sottoclassi SI NO SI

Modificatore public private protected

Le direttive che regolano il rapporto tra i tre modificatori e lereditariet sono le seguenti: il modificatore public consente di dichiarare dati e metodi membro visibili e quindi utilizzabili da uneventuale sottoclasse; il modificatore private nasconde completamente dati e metodi membro dichiarati tali. E invece necessario soffermarci sul modificatore protected al quale dedicato il prossimo esempio. Creiamo il package src.esercizi.modificatori.protect la cui struttura schematizzata nel prossimo class-diagram.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

170

Figura 63:

Modificatore protected

Il package contiene le due seguenti definizioni di classe:


package src.esercizi.modificatori.protect; public class Produttore { protected String dato_protetto ="Questo dato e di tipo protected"; protected void stampaDatoProtetto() { System.out.println(dato_protetto); } } package src.esercizi.modificatori.protect; public class Consumatore { /** * @label base */ private Produttore base = new ClasseBase(); void metodoConsumatore() { base.stampaDatoProtetto(); } }

La definizione della classe Produttore contiene due membri di tipo protected, identificati nel class-diagram dal nome dei membri anteposti dal carattere #. Il primo, un attributo di tipo String chiamato dato_protetto, il secondo un metodo chiamato stampaDatoProtetto che, torna un tipo void e produce la stampa a terminale del valore del primo membro.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

171

La classe Consumatore, ha un riferimento alla classe precedente, tramite il dato membro producer e ne effettua la chiamata al metodo stampaDatoProtetto. Poich le due classi appartengono allo stesso package, alla classe C o n s u m a t o r e consentito lutilizzo del metodo p r o t e c t e d stampaDatoProtetto() di Produttore senza che il compilatore Java segnali errori. Analogamente, i membri protected della classe Produttore saranno visibili alla classe NuovoProduttore , definita per mezzo della ereditariet allinterno dello stesso package, come schematizzato nella prossima figura.

Figura 64:

Modificatore protected ed ereditariet

package src.esercizi.modificatori.protect; public class NuovoProduttore extends Produttore { public NuovoProduttore() { dato_protetto = "Il valore di dato_protetto viene modificato"; } public void StampaDato() { stampaDatoProtetto(); } }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

172

Consideriamo ora la classe TerzoProduttore, definita per ereditariet a partire dalla classe base ClasseBase, ma appartenente ad un sotto package di quello corrente:
package src.esercizi.modificatori.protect.sub; import src.esercizi.modificatori.protect.Produttore; public class TerzoProduttore extends Produttore { public TerzoProduttore() { dato_protetto = "Il valore di dato_protetto viene modificato"; } public void StampaDato(Produttore producer) { stampaDatoProtetto(); //Questa chiamata legale producer.stampaDatoProtetto(); //Questa chiamata illegale } }

In questo caso, la nuova classe potr utilizzare il metodo stampaDatoProtetto() ereditato dalla superclasse, anche se non appartenente al medesimo package, ma non potr utilizzare lo stesso metodo se chiamato direttamente come metodo membro delloggetto di tipo Produttore referenziato dalla variabile producer passata come parametro al metodo stampaDato . Se, infatti, provassimo a compilare la classe precedente, il compilatore produrrebbe il seguente messaggio di errore:
src\esercizi\modificatori\protect\sub\TerzoProduttore.java:15: stampaDatoProtetto() has protected access in src.esercizi.modificatori.protect.Produttore producer.stampaDatoProtetto(); ^ 1 error *** Compiler reported errors

Di fatto, il modificatore protected consente laccesso ad un metodo o dato membro di una classe:

1. a tutte le sue sottoclassi, definite o no allinterno dello stesso package; 2. alle sole classi appartenenti allo stesso package, se riferite tramite una variabile reference.
In tutti gli altri casi non sar possibile utilizzare metodi e dati membro definiti protected.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

173

8.6

Conseguenze dellincapsulamento nella ereditariet

Negli esempi precedenti si nota facilmente che, la definizione del metodo costruttore della classe Veicolo molto simile a quella del costruttore della classe Macchina. Conseguentemente, potrebbe tornare utile utilizzare il costruttore della classe base per effettuare almeno una parte delle operazioni di inizializzazione. Facciamo qualche considerazione:

Cosa succederebbe se sbagliassimo qualche cosa nella definizione del costruttore della classe derivata? Cosa succederebbe se nella classe base ci fossero dei dati privati che il costruttore della classe derivata non pu aggiornare?
Consideriamo lesempio seguente:
package src.esercizi.incapsulamento;

/** * Definizione della figura geometrica cerchio */ public class Cerchio {


public Cerchio() { PiGreco = 3.14; } public double getRaggio() { return raggio; } public void setRaggio(double raggio) { this.raggio = raggio; }

/** * Calcola la circonferenza del cerchio il * cui raggio rappresentato dalla propriet "raggio" * @return double : circonferenza del cerchio */ public double circonferenza() { return (2*PiGreco)*getRaggio(); } /** * Calcola ll'area del cerchio il * cui raggio rappresentato dalla propriet "raggio" * @return double : area del cerchio */ public double area() { return (getRaggio()*getRaggio())*PiGreco; }
private double PiGreco; private double raggio; }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

174

La classe base Cerchio, contiene la definizione di un dato membro privato di tipo double che rappresenta la costante matematica e mette a disposizione due metodi che consentono di calcolare rispettivamente la lunghezza della circonferenza e larea del cerchio il cui raggio pu essere impostato utilizzando il metodo setter della propriet raggio. Il costruttore della classe, ha la responsabilit di inizializzare al valore corretto il dato membro PiGreco. Definiamo ora la classe Ruota per mezzo del meccanismo di ereditariet a partire dalla classe Cerchio.
package src.esercizi.incapsulamento;

/** * Una ruota pu essere definita * a partire da un Cerchio generico */ public class Ruota extends Cerchio { public Ruota(raggio) { setRaggio(raggio); //La prossima riga produce un errore di compilazione PiGreco = 3.14; } }

Il metodo costruttore della nuova classe, oltre ad impostare il raggio della ruota utilizzando il metodo setRaggio di Cerchio avendolo ereditato dalla classe base, tenta di impostare il valore del dato privato PiGreco causando un errore durante la compilazione. E quindi necessario eliminare il problema. La classe Ruota corretta avr la forma seguente:
package src.esercizi.incapsulamento;

/** * Una ruota pu essere definita * a partire da un Cerchio generico */ public class Ruota extends Cerchio { public Ruota(raggio) { setRaggio(raggio); } }

La nuova definizione di classe verr compilata correttamente, ma i risultati forniti dalla esecuzione dei metodi circonferenza e area potrebbero essere valori inattendibili a causa della mancata inizializzazione del dato privato PiGreco della classe Cerchio. In altre parole, Java applica lincapsulamento anche a livello di ereditariet. In questo modo la classe base gode di tutti i benefici derivanti da un uso corretto dellincapsulamento; daltra parte, poich linterfaccia di Cerchio non

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

175

prevede metodi pubblici in grado di modificare il valore di PiGreco , lunico modo per assegnarle il valore corretto utilizzare il costruttore della classe base. Java deve quindi poter garantire chiamate a costruttori risalendo nella catena delle classi di una gerarchia.

8.7

Ereditariet e costruttori

Il meccanismo utilizzato da Java per assicurare la chiamata di un costruttore per ogni classe di una gerarchia, si basa su alcuni principi di base. Primo, ogni classe deve avere un costruttore. Se il programmatore non ne implementa alcuno, Java per definizione, assegner alla classe un costruttore vuoto e senza lista di parametri che chiameremo per comodit costruttore nullo. Il costruttore nullo viene attribuito automaticamente dal compilatore; la classe
public class ClasseVuota { }

equivale alla classe:


public class ClasseVuota { public ClasseVuota(){ } }

Secondo, se una classe derivata da unaltra lutente pu effettuare una chiamata al costruttore della classe base immediatamente precedente nella gerarchia, utilizzando la sintassi:

super(lista_degli_argomenti);
dove lista_degli_argomenti rappresenta la lista dei parametri del costruttore da chiamare. Una chiamata esplicita al costruttore della classe base deve essere effettuata prima di ogni altra operazione, incluso la dichiarazione di variabili. Per comprendere meglio il meccanismo, modifichiamo lesempio visto nei paragrafi precedenti aggiungendo alla classe Cerchio un metodo costruttore che ci consente di impostare il valore del raggio della circonferenza al momento della creazione delloggetto:
package src.esercizi.incapsulamento;

/** * Definizione della figura geometrica cerchio */ public class Cerchio {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

176

public Cerchio() { PiGreco = 3.14; } public Cerchio(double raggio) { PiGreco = 3.14; setRaggio(raggio); } public double getRaggio() { return raggio; } public void setRaggio(double raggio) { this.raggio = raggio; }

/** * Calcola la circonferenza del cerchio il * cui raggio rappresentato dalla propriet "raggio" * @return double : circonferenza del cerchio */ public double circonferenza() { return (2*PiGreco)*getRaggio(); } /** * Calcola ll'area del cerchio il * cui raggio rappresentato dalla propriet "raggio" * @return double : area del cerchio */ public double area() { return (getRaggio()*getRaggio())*PiGreco; }
private double PiGreco; private double raggio; }

Inseriamo allinterno del costruttore della classe Cerchio, la chiamata esplicita al costruttore senza argomenti della classe base:
package src.esercizi.incapsulamento;

/** * Una ruota pu essere definita * a partire da un Cerchio generico */ public class Ruota extends Cerchio { public Ruota() { super(); setRaggio(20); } }

Ora siamo sicuri che lo stato della classe base inizializzato in modo corretto, compreso il valore dellattributo PiGreco, come mostrato dalla applicazione Circonferenze.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

177

package src.esercizi.incapsulamento; public class Circonferenze { public static void main(String[] argv) { Cerchio c = new Cerchio(); c.setRaggio(20); System.out.println("L'area di un cerchio di raggio 20 : "+c.area()); System.out.println("La circonferenza di un cerchio di raggio 20 : "+c.circonferenza()); Ruota r = new Ruota(); System.out.println("L'area di una ruota di raggio 20 : "+r.area()); System.out.println("La circonferenza di una ruota di raggio 20 : "+r.circonferenza()); } } java src.esercizi.incapsulamento.Circonferenze L'area di un cerchio di raggio 20 : 1256.0 La circonferenza di un cerchio di raggio 20 : 125.60000000000001 L'area di una ruota di raggio 20 : 1256.0 La circonferenza di una ruota di raggio 20 : 125.60000000000001

In generale, la chiamata esplicita al costruttore senza argomenti soggetta alle seguente restrizione:

Se, la classe base non contiene la definizione del costruttore senza argomenti ma, contiene la definizione di costruttori specializzati con liste di argomenti, la chiamata esplicita super() provoca errori al momento della compilazione.
Torniamo ora alla prima versione delloggetto Ruota , ed eseguiamo nuovamente lapplicazione Circonferenze.
package src.esercizi.incapsulamento;

/** * Una ruota pu essere definita * a partire da un Cerchio generico */ public class Ruota extends Cerchio { public Ruota() { setRaggio(20); } }
java src.esercizi.incapsulamento.Circonferenze L'area di un cerchio di raggio 20 : 1256.0 La circonferenza di un cerchio di raggio 20 : 125.60000000000001 L'area di una ruota di raggio 20 : 1256.0 La circonferenza di una ruota di raggio 20 : 125.60000000000001

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

178

Notiamo subito che, nonostante il metodo costruttore di Ruota non contenga la chiamata esplicita al costruttore della superclasse, il risultato dellesecuzione di Circonferenze identico al caso analizzato in precedenza. Lanomalia dovuta al fatto che, se il programmatore non effettua una chiamata esplicita al costruttore senza argomenti della classe base, Java esegue implicitamente tale chiamata. Infine, possiamo modificare ulteriormente il costruttore della classe Ruota, affinch utilizzi il nuovo costruttore della classe Cerchio evitando di chiamare esplicitamente il metodo setter per impostare il valore della propriet raggio.
/** * Una ruota pu essere definita * a partire da un Cerchio generico */ public class Ruota extends Cerchio { public Ruota() { super(20); } }

8.8

Aggiungere nuovi metodi

Quando estendiamo una classe base, possiamo aggiungere nuovi metodi alla classe derivata. Ad esempio, una Macchina generalmente possiede un avvisatore acustico. Aggiungendo il metodo segnala() , continueremo a mantenere tutte le vecchie funzionalit, ma ora la macchina in grado di emettere segnali acustici. Definire nuovi metodi allinterno di una classe derivata ci consente quindi di definire quelle caratteristiche particolari non previste nella definizione generica del concetto, e necessarie a specializzare le nuove classi.
package src.esercizi.ereditarieta; public class Macchina extends Veicolo { public Macchina() { velocita=0; direzione = DRITTO; nome = "Macchina"; }

/** * Simula l'attivazione del segnalatore acustico della macchina. * @return void */ public void segnala() { System.out.println(nome + "ha attivato il segnalatore acustivo ");

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

179

} }

package src.esercizi.ereditarieta; public class Autista { public static void main(String args[]) { Macchina fiat = new Macchina(); fiat.muovi(); fiat.sinistra(); fiat.diritto(); fiat.ferma(); fiat.segnala(); } }

8.9

Overriding di metodi

Capita spesso che, un metodo ereditato da una classe base non sia adeguato rispetto alla specializzazione della classe. Per ovviare al problema, Java ci consente di ridefinire il metodo originale semplicemente riscrivendo il metodo in questione, nella definizione della classe derivata. Anche in questo caso, definendo nuovamente il metodo nella classe derivata, non si corre il pericolo di manomettere la superclasse. Il nuovo metodo sar eseguito al posto del vecchio, anche se la chiamata fosse effettuata da un metodo ereditato dalla classe base. La definizione della classe Macchina, potrebbe ridefinire il metodo muovi(int) affinch controlli che la velocit, passata come attributo, non superi la velocit massima di 120 Km/h consentiti al mezzo. Modifichiamo la definizione della classe Macchina:
package src.esercizi.ereditarieta; public class Macchina extends Veicolo { public Macchina() { velocita=0; direzione = DRITTO; nome = "Macchina"; }

/** * Simula l'attivazione del segnalatore acustico della macchina. * @return void */ public void segnala() { System.out.println(nome + "ha attivato il segnalatore acustivo "); }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

180

/** Simula la messa in marcia del veicolo consentendo di specificare * la velocit in km/h. * Questo metodo ridefinisce il metodo analogo * definito nella classse base Veicolo. * Per adattare il metodo al nuovo oggetto, il metodo * verifica che la velocit passata come argomento * sia al massimo pari alla velocit massima della macchina. * @return void * @param int velocita : velocita del veicolo */ public void muovi(int velocita) { if(velocita<120) this.velocita= velocita; else this.velocita = 120; System.out.println(nome + "si sta movendo a: "+ velocita+" Kmh"); }
} package src.esercizi.ereditarieta; public class Autista { public static void main(String args[]) { Macchina fiat = new Macchina(); fiat.muovi(); //Lautista accelera fino al massimo della velocit fiat.muovi(120); //Lautista deve effettuare una serie di curve. //Prima di curvare frena la macchina fiat.muovi(60); fiat.sinistra(); fiat.diritto(); fiat.segnala(); fiat.ferma(); } }

Nel metodo main della applicazione Autista, quando viene eseguito metodo muovi(int), lapplicazione far riferimento al metodo della classe Macchina e non a quello definito allinterno della classe base Veicolo.

8.10 Chiamare metodi della classe base


La parola chiave super, pu essere utilizzata anche nel caso in cui sia necessario eseguire un metodo della superclasse, evitando che la JVM esegua il rispettivo metodo ridefinito nella classe derivata con il meccanismo di overriding.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

181

Grazie alla parola chiave super, il programmatore non deve necessariamente riscrivere un metodo completamente, ma libero di utilizzare parte delle funzionalit definite allinterno del metodo della classe base. Esaminiamo ancora la definizione della classe Macchina ed in particolare sulla definizione del metodo muovi(int):
/** Simula la messa in marcia del veicolo consentendo di specificare * la velocit in km/h. * Questo metodo ridefinisce il metodo analogo * definito nella classse base Veicolo. * Per adattare il metodo al nuovo oggetto, il metodo * verifica che la velocit passata come argomento * sia al massimo pari alla velocit massima della macchina. * @return void * @param int velocita : velocita del veicolo */ public void muovi(int velocita) { if(velocita<120) this.velocita= velocita; else velocita = 120; System.out.println(nome + "si sta movendo a: "+ velocita+" Kmh"); }

Questo nuova versione del metodo implementata nella definizione della classe Macchina , effettua il controllo del valore della variabile passata come argomento, per limitare la velocit massima del mezzo. Al termine della operazione, esegue un assegnamento identico a quello effettuato nello stesso metodo definito nella superclasse Veicolo e riportato di seguito:
/** Simula la messa in marcia del veicolo specificando la velocit in km/h * @return void * @param int velocita : velocita del veicolo */
public void muovi(int velocita) { this.velocita = velocita; System.out.println(nome + " si sta movendo a: " + velocita + " Kmh"); }

Il metodo muovi() della classe Macchina, pu quindi essere riscritto affinch sfrutti quanto gi implementato nella superclasse nel modo seguente:
/** Simula la messa in marcia del veicolo consentendo di specificare * la velocit in km/h. * Questo metodo ridefinisce il metodo analogo * definito nella classse base Veicolo. * Per adattare il metodo al nuovo oggetto, il metodo * verifica che la velocit passata come argomento * sia al massimo pari alla velocit massima della macchina. * @return void

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

182

* @param int velocita : velocita del veicolo */ public void muovi(int velocita) { if(velocita<120) super.muovi(velocita); else super.muovi(120); System.out.println(nome + "si sta movendo a: "+ velocita+" Kmh"); }

E importante notare che, a differenza della chiamata ai metodi costruttori della superclasse, da eseguire facendo uso solo della parola chiave super ed eventualmente la lista degli argomenti trasmessi metodo, ora necessario utilizzare loperatore ., specificando il nome del metodo da chiamare. In questo caso, super ha lo stesso significato di una variabile reference, come this creato dalla JVM al momento della creazione delloggetto, e con la differenza che super fa sempre riferimento alla superclasse della classe attiva ad un determinato istante.

8.11 Compatibilit tra variabili reference


Una volta che una classe Java stata derivata, Java consente alle variabili reference che rappresentano il tipo della classe base di referenziare ogni oggetto derivato da essa, nella gerarchia definita dalla ereditariet.
Veicolo veicolo = new Macchina();

//Eseguo il metodo muovi(int) definito nella classe Macchina veicolo.muovi(10);

La ragione alla base di questa funzionalit che, gli oggetti derivati hanno sicuramente almeno tutti i metodi della classe base (li hanno ereditati), e quindi non ci dovrebbero essere problemi nellutilizzarli. Nel caso in cui un metodo sia stato ridefinito mediante overriding, questo tipo di riferimento effettuer una chiamata al nuovo metodo. Quanto detto ci consente di definire il concetto di compatibilit e compatibilit stretta tra variabili reference.

Una variabile reference A si dice strettamente compatibile con la variabile reference B se, A fa riferimento ad un oggetto definito per ereditariet dalloggetto riferito da B. Viceversa, la variabile reference B si dice compatibile con la variabile reference A se, B fa riferimento ad un oggetto che rappresenta la classe base per loggetto riferito da A.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

183

Nellesempio successivo, la variabile reference ferrari di tipo Macchina , strettamente compatibile con la variabile reference carro di tipo Veicolo, dal momento che Macchina definita per ereditariet da Veicolo. Viceversa, la variabile reference carro di tipo Veicolo compatibile con la variabile reference ferrari di tipo Macchina.
Macchina ferrari = new Macchina(); Veicolo carro = new Veicolo();

8.12 Run-time e compile-time


Prima di procedere oltre, necessario fermarci qualche istante per introdurre i due concetti di tipo a run-time e tipo a compile-time.

Il tipo a compile-time di una espressione, il tipo dellespressione come dichiarato formalmente nel codice sorgente. Il tipo a run-time il tipo dellespressione determinato durante l esecuzione della applicazione. I tipi primitivi (int, float, double etc. ) rappresentano sempre lo stesso tipo sia al run-time che al compile-time.
Il tipo a compile-time sempre costante, quello a run-time variabile. Ad esempio, la variabile reference veicolo di tipo Veicolo, rappresenta il tipo Veicolo a compile-time, e il tipo Macchina a run-time.
Veicolo veicolo = new Macchina();

Volendo fornire una regola generale, diremo che:

Il tipo rappresentato al compile-time da una espressione specificato nella sua dichiarazione, mentre quello a run-time il tipo attualmente rappresentato.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

184

Figura 65:

run-time e compile-time

Per riassumere, ecco alcuni esempi: 1. V ha tipo Veicolo a compile-time e Macchina al run-time
Veicolo V = new Macchina();

2. Il tipo a run-time di v cambia in Veicolo


v = new Veicolo();

3. i rappresenta un tipo int sia a run-time che a compile-time


int i=0;

E comunque importante sottolineare che, una variabile reference potr fare riferimento solo ad oggetti il cui tipo in qualche modo compatibile con il tipo rappresentato al compile-time. Questa compatibilit, come gi definito nel paragrafo precedente, rappresentata dalla relazione di ereditariet: tipi derivati sono sempre compatibili con le variabili reference dei predecessori.

8.13 Accesso a metodi attraverso variabili reference


Tutti i concetti finora espressi, hanno un impatto rilevante sulle modalit di accesso ai metodi di un oggetto. Consideriamo ad esempio la prossima applicazione:
public class MacchinaVSVeicolo { public static void main(String args[]) {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

185

Macchina fiat = new Macchina(); Veicolo v = fiat; fiat.segnala(); //Questa una chiamata illegale v.segnala(); } }

Se proviamo a compilare il codice, il compilatore produrr un messaggio di errore relativo alla riga otto del codice sorgente. Dal momento che, il tipo rappresentato da una variabile al run-time pu cambiare, il compilatore assumer che la variabile reference v sta facendo riferimento alloggetto del tipo rappresentato al compile-time (Veicolo). In altre parole, anche se una classe Macchina possiede un metodo suona(), questo non sar utilizzabile tramite una variabile reference di tipo Veicolo. La situazione non si verifica se modifichiamo lapplicazione nel modo seguente:
public class MacchinaVSVeicolo { public static void main(String args[]) { Veicolo v = new Veicolo(); Macchina fiat = v; fiat.muovi(120); } }

Le conclusioni che possiamo trarre, possono essere riassunte come segue:

1. Se A una variabile reference strettamente compatibile con la variabile reference B, allora A avr accesso a tutti i metodi di entrambe le classi; 2. Se B una variabile reference compatibile con la variabile reference A, allora B potr accedere solo ai metodi che la classe riferita da A ha ereditato.

8.14 Cast dei tipi


Java, fornisce un modo per girare intorno alle limitazioni imposte dalle differenze tra tipo al run-time e tipo al compile-time, permettendo di risolvere il problema verificatosi con la prima versione della applicazione MacchinaVSVeicolo.
public class MacchinaVSVeicolo { public static void main(String args[])

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

186

{ Macchina fiat = new Macchina(); Veicolo v = fiat; fiat.segnala(); //Questa una chiamata illegale v.segnala(); } }

Il cast di un tipo, una tecnica che consente di dichiarare alla JVM che una variabile reference temporaneamente rappresenter un tipo differente da quello rappresentato al compile-time. La sintassi necessaria a realizzare unoperazione di cast di tipo la seguente:

(nuovi_tipo) nome
Dove nuovo_tipo il tipo desiderato, e nome lidentificatore della variabile che vogliamo convertire temporaneamente. Riscrivendo lesempio precedente utilizzando il meccanismo di cast, il codice verr compilato ed eseguito correttamente :
public class MacchinaVSVeicolo { public static void main(String args[]) { Macchina fiat = new Macchina(); Veicolo v = fiat; fiat.segnala(); //Questa una chiamata legale ((Macchina)v).segnala(); } }

Loperazione di cast possibile su tutti i tipi purch il tipo della variabile reference ed il nuovo tipo siano almeno compatibili. Il cast del tipo di una variabile reference, ha effetto solo sul tipo rappresentato al compile-time e non sulloggetto in se stesso. Il cast su un tipo provocher la terminazione dellapplicazione se, il tipo rappresentato al run-time dalloggetto non rappresenta il tipo desiderato al momento dellesecuzione.

8.15 Loperatore instanceof


Poich, in unapplicazione Java esistono un gran numero di variabili reference, a volte utile determinare al run-time il tipo di oggetto cui una variabile sta facendo riferimento. A tal fine, Java supporta loperatore booleano

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

187

instanceof che controlla il tipo di oggetto referenziato al run-time da una variabile reference. La sintassi formale la seguente:

A instanceof B
Dove A rappresenta una variabile reference, e B un tipo referenziabile. Il tipo rappresentato al run-time dalla variabile reference A sar confrontato con il tipo definito da B. Loperatore torner uno tra i due possibili valori true o false . Nel primo caso (true ) saremo sicuri che il tipo rappresentato da A consente di rappresentare il tipo rappresentato da B. False, indica che A fa riferimento alloggetto null oppure, che non rappresenta il tipo definito da B. In poche parole, se possibile effettuare il cast di A in B, instanceof restituir true. Modifichiamo lapplicazione MacchinaVSVeicolo:
public class MacchinaVSVeicolo { public static void main(String args[]) {

Veicolo v = new Macchina();


v.segnala(); } }

In questo caso, Java produrr un errore di compilazione in quanto, il metodo segnala() definito nella classe Macchina e non nella classe base Veicolo. Utilizzando loperatore instanceof possiamo prevenire lerrore apportando al codice le modifiche seguenti:
public class MacchinaVSVeicolo { public static void main(String args[]) {

} }

Veicolo v = new Macchina(); if(v instanceof Macchina) ((Macchina)v).segnala();

8.16 Loggetto Object


La gerarchia delle classi delle Java Core API, parte dalle classe Object che, ne rappresenta la radice dellalbero, come mostrato nella prossima figura.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

188

Figura 66:

La classe Object

In realt, ogni volta che definiamo una nuova classe Java senza utilizzare il meccanismo dellereditariet ovvero, la classe non estende nessuna classe base, Java ne provocher automaticamente lestensione delloggetto Object. Questa caratteristica degli oggetti Java, nasce principalmente allo scopo di garantire alcune funzionalit base comuni a tutte le classi, includendo la possibilit di esprimere lo stato di un oggetto in forma di stringa, la possibilit di comparare due oggetti o terminarne uno tramite il metodo ereditato finalize(). Questultimo metodo, utilizzato dal garbage collector nel momento in cui elimina loggetto rilasciando la memoria e pu essere modificato per gestire situazioni non controllabili dal garbage collector come i riferimenti circolari.

8.17 Il metodo equals()


Abbiamo gi anticipato che, loperatore di booleano di uguaglianza ==, non sufficiente a confrontare due oggetti poich opera a livello di variabili referenze e di conseguenza, confronta due puntatori a locazioni di memoria producendo il valore true se e solo se le due variabili reference puntano allo stesso oggetto. Abbiamo inoltre detto che, due oggetti sono uguali se uguale il loro stato. Il metodo equals(), ereditato dalla classe Object , confronta due oggetti a livello di stato, restituendo il valore t r u e se e solo se i due oggetti rappresentano due istanze medesime della stessa definizione di classe ovvero, se due oggetti di tipo compatibile si trovano nello stesso stato. Immaginiamo di dover confrontare oggetti di tipo Integer:
package src.esercizi.confronto;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

189

/** * Questa applicazione esegue il confromtro * tra oggetti di tipo Integer utilizzando * il metodo equals ereditato da Object */ public class MetodoEquals { public static void main(String[] argv) { Integer primointero = new Integer(1); Integer secondointero = new Integer(2); Integer terzointero = new Integer(1); if (primointero.equals(secondointero)) System.out.println("primointero else System.out.println("primointero if (primointero.equals(terzointero)) System.out.println("primointero else System.out.println("primointero } }

uguale a secondointero"); diverso da secondointero"); uguale a terzointero"); diverso da terzointero");

Dopo aver compilato ed eseguito lapplicazione,il risultato prodotto prodotto sar il seguente:
primointero diverso da secondointero primointero uguale a terzointero

Proviamo ora a definire una nuova classe che, rappresenta un punto sul piano:
package src.esercizi.confronto; public class Punto { public int x, y; public Punto(int xc, int yc) { x = xc; y = yc; } public static void main(String args[]) { Punto a, b; a = new Punto(1, 2); b = new Punto(1, 2);

//Uguaglianza mediante operatore == if (a == b) System.out.println("Il confronto tra variabili reference vale true"); else System.out.println("Il confronto tra variabili reference vale false"); //Uguaglianza mediante metodo equals if (a.equals(b)) System.out.println("a uguale a b"); else System.out.println("a diverso da b");

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

190

} }

Il confronto tra variabili reference vale false a diverso da b

Lo stato delloggetto Punto, rappresentato dal valore dei due attributi x e y che rappresentano rispettivamente le ascisse e le coordinate del punto su un piano cartesiano. Allinterno del metodo main dellapplicazione, vengono creati due oggetti tra loro compatibili ed aventi lo stesso stato ma, il confronto a.equals(b) restituisce il valore false contrariamente a quanto ci aspettavamo. Dal momento che il metodo equals() esegue un confronto a livello di stato tra due oggetti, a causa dei diversi aspetti che le definizioni di classe possono assumere, limplementazione definita allinterno della definizione della classe Object potrebbe non essere sufficiente. E quindi necessario che il programmatore riscriva il metodo in questione utilizzando il meccanismo di overriding. Modificando la classe Punto inserendo la nuova definizione del metodo object al suo interno, il risultato della applicazione sar finalmente quello atteso.
package src.esercizi.confronto; public class Punto { public int x, y; public Punto(int xc, int yc) { x = xc; y = yc; } public boolean equals(Object o) { if (o instanceof Punto) { Punto p = (Punto)o; if (p.x == x && p.y == y) return true; } return false; } public static void main(String args[]) { Punto a, b; a = new Punto(1, 2); b = new Punto(1, 2);

//Uguaglianza mediante operatore == if (a == b) System.out.println("Il confronto tra variabili reference vale true"); else System.out.println("Il confronto tra variabili reference vale false");
//Uguaglianza mediante metodo equals if (a.equals(b)) System.out.println("a uguale a b"); else System.out.println("a diverso da b");

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

191

} }

Lesecuzione della applicazione produrr il seguente risultato:


Il confronto tra variabili reference vale false a uguale a b

8.18 Rilasciare risorse esterne


Quando un oggetto viene creato mediante loperatore new, il linguaggio Java ci assicura che, verr comunque eseguito un metodo costruttore affinch lo stato iniziale delloggetto sia correttamente impostato. Analogamente, quando un oggetto viene rilasciato, il garbage collector Java ci assicura che, prima di rilasciare loggetto e liberare la memoria allocata sar eseguito il metodo finalize() della classe. Tipicamente, questo metodo utilizzato in quei casi in cui sia necessario gestire situazioni di riferimenti circolari di tra oggetti, oppure circostanze in cui loggetto utilizzi metodi nativi (metodi esterni a Java e nativi rispetto alla macchina locale) che, utilizzano funzioni scritte in altri linguaggi. Dal momento che situazioni di questo tipo coinvolgono risorse al di fuori del controllo del garbage collector10, finalize() pu essere utilizzato per consentire al programmatore di implementare meccanismi di gestione esplicita della memoria. Il metodo ereditato dalla classe Object non fa nulla.

8.19 Oggetti in forma di stringa


Il metodi toString() utilizzato per implementare la conversione in String di una classe. Tutti gli oggetti definiti dal programmatore, dovrebbero contenere questo metodo che ritorna una stringa rappresentante loggetto. Tipicamente questo metodo viene riscritto in modo che ritorni informazioni relative alla versione delloggetto ed al programmatore che lo ha disegnato oppure, viene utilizzato per trasformare in forma di stringa un particolare dato rappresentato dalloggetto. Ad esempio, nel caso delloggetto Integer, il metodo toString(), viene utilizzatio per convertire in forma di stringa il numero intero rappresentato dalloggetto:
String valore_in_forma_di_stringa; Integer intero_in_forma_di_oggetto = new Integer(100); valore_in_forma_di_stringa = intero_in_forma_di_oggetto.toString();

Ad esempio nel caso in cui si utilizzi una funzione C che fa uso della malloc() per allocare memoria

10

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

192

Limportanza del metodo toString() legata al fatto che, il metodo statico System.out.println() utilizza toString() per la stampa delloutput a terminale come mostrato nel prossimo esempio:
package src.esercizi.confronto; public class Punto { public int x, y; public Punto(int xc, int yc) { x = xc; y = yc; } public boolean equals(Object o) { if (o instanceof Punto) { Punto p = (Punto)o; if (p.x == x && p.y == y) return true; } return false; } public String toString() { return "("+x+","+y+")"; } public static void main(String args[]) { Punto a, b; a = new Punto(1, 2); b = new Punto(1, 2); c = new Punto(10, 3);

//Uguaglianza mediante operatore == if (a == b) System.out.println("Il confronto tra variabili reference vale true"); else System.out.println("Il confronto tra variabili reference vale false"); //Uguaglianza mediante metodo equals if (a.equals(b)) System.out.println(a+" uguale a "+b); else System.out.println(a+" diverso da "+b); if (a.equals(c)) System.out.println(a+" uguale a "+c); else System.out.println(a+" diverso da "+c);
} } Il confronto tra variabili reference vale false (1,2) uguale a (1,2) (1,2) diverso da (10,3)

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

193

8.20 Giochi di simulazione


Tutti i linguaggi ad oggetti si prestano facilmente alla creazione di simulazioni anche molto complesse. Nel prossimo esercizio, a partire dalla classe Veicolo, definiremo nuovi tipi di veicolo attraverso il meccanismo della ereditariet e trasformeremo la applicazione Autista in una definizione di classe completa (non pi contenente solo il metodo main). La nuova definizione conterr un costruttore che richiede un Veicolo come attributo e sar in grado di riconoscerne i limiti di velocit. Aggiungeremo alla classe Autista il metodo sorpassa(Veicolo). Infine, utilizzeremo una nuova applicazione che, creati alcuni oggetti di tipo Autista e Veicolo, completa il gioco di simulazione. Il class-diagram relativo allesercizio mostrato nella prossima figura:

Figura 67:

Class-diagram della applicazione Simulazione

package src.esercizi.ereditarieta; public class Veicolo { /** Nome del veicolo */ String nome;

/** Rappresenta la velocit del veicolo espressa in Km/h */ int velocita;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

194

/** Direzione di marcia del veicolo */ int direzione; /** Costante che indica la direzione "dritta" di marcia del veicolo */ final static int DRITTO = 0; /** Costante che indica la svola a sinistra del veicolo */ final static int SINISTRA = -1; /** Costante che indica la svola a destra del veicolo */ final static int DESTRA = 1; public int velocita_massima;
public Veicolo() { velocita = 0; direzione = DRITTO; nome = "Veicolo generico"; }

/** Simula la messa in marcia del veicolo alla velocit di 1 km/h */ public void muovi() { velocita = 1; System.out.println(nome + " si sta movendo a: " + velocita + " Kmh"); } /** Simula la messa in marcia del veicolo specificando la velocit in km/h * @return void * @param int velocita : velocita del veicolo */ public void muovi(int velocita) { this.velocita = velocita; System.out.println(nome + " si sta movendo a: " + velocita + " Kmh"); } /** Simula la frenata del veicolo *@return void */ public void ferma() { velocita = 0; System.out.println(nome + " si fermato"); } /** Simula la svola a sinistra del veicolo *@return void */ public void sinistra() { direzione = SINISTRA; System.out.println(nome + " ha sterzato a sinistra"); } /** Simula la svola a destra del veicolo *@return void */ public void destra() { direzione = DESTRA; System.out.println(nome + " ha sterzato a destra"); } /** Simula la conclusione di una svola a sinistra o a destra del veicolo

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

195

*@return void */ public void diritto() { direzione = DRITTO; System.out.println(nome + " sta procedendo in linea retta"); } /** Restituisce la velocit massima del veicolo *@return int velocit massima del veicolo in Km/h */ public int velocitaMassima() { return velocita_massima; } /** Restituisce la velocit di marcia del veicolo *@return int velocit massima del veicolo in Km/h */ public int velocitaCorrente() { return velocita; }
}

La classe base Veicolo stata modificata rispetto alla versione precedente. Ora, possibile impostare una velocit massima per il veicolo e grazie a due appositi metodi possibile conoscere la velocit di marcia del mezzo di trasporto nonch la sua velocit massima.
package src.esercizi.ereditarieta; public class Cavallo extends Veicolo { private int stanchezza; // range da 0 a 10 public Cavallo(String nome) { velocita = 0; velocita_massima = 20; direzione = DRITTO; this.nome = nome; stanchezza = 0; }

/** * La marcia di un cavallo soggetta non solo a restrizioni * rispetto alla velocit massima dell'animale, * ma dipende dalla stanchezza del cavallo. * @return void * @param int velocita : velocita del cavallo */ public void muovi(int velocita) { if (velocita > velocitaMassima()) velocita = velocitaMassima(); super.muovi(velocita); if (velocita > 10 && stanchezza < 10) stanchezza++; } /** * Quando il cavallo si ferma, la stanchezza dimezza * poich il cavallo pu riposarsi.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

196

* @return void */ public void ferma() { stanchezza = stanchezza /2; super.ferma(); }


public int velocitaMassima() { return velocita_massima - stanchezza; } }

La classe Cavallo, rappresenta un tipo particolare di mezzo di trasporto in quanto, la velocit massima dipende da fattori differenti quali la stanchezza dellanimale. Per simulare questa caratteristica, la classe contiene un dato membro di tipo intero che rappresenta la stanchezza del cavallo, pu contenere solo valori da 0 a 10. Il valore dellattributo stanchezza viene incrementato nel metodo muovi e dimezzato nel metodo ferma.
package src.esercizi.ereditarieta; public class Macchina extends Veicolo { public Macchina(String marca) { velocita_massima = 120; velocita=0; direzione = DRITTO; nome = marca; }

/** * Simula l'attivazione del segnalatore acustico della macchina. * @return void */ public void segnala() { System.out.println(nome + "ha attivato il segnalatore acustivo "); } /** Simula la messa in marcia del veicolo consentendo di specificare * la velocit in km/h. * Questo metodo ridefinisce il metodo analogo * definito nella classse base Veicolo. * Per adattare il metodo al nuovo oggetto, il metodo * verifica che la velocit passata come argomento * sia al massimo pari alla velocit massima della macchina. * @return void * @param int velocita : velocita del veicolo */ public void muovi(int velocita) { if(velocita< velocita_massima) this.velocita= velocita; else this.velocita = velocita_massima; System.out.println(nome + "si sta movendo a: "+ velocita+" Kmh");

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

197

} } package src.esercizi.ereditarieta; public class Motocicletta extends Veicolo { public Motocicletta(String marca) { velocita_massima = 230; velocita=0; direzione = DRITTO; nome = marca; }

/** * Simula l'attivazione del segnalatore acustico della macchina. * @return void */ public void segnala() { System.out.println(nome + "ha attivato il segnalatore acustivo "); } /** Simula la messa in marcia del veicolo consentendo di specificare * la velocit in km/h. * Questo metodo ridefinisce il metodo analogo * definito nella classse base Veicolo. * Per adattare il metodo al nuovo oggetto, il metodo * verifica che la velocit passata come argomento * sia al massimo pari alla velocit massima della macchina. * @return void * @param int velocita : velocita del veicolo */ public void muovi(int velocita) { if(velocita< velocita_massima) this.velocita= velocita; else this.velocita = velocita_massima; System.out.println(nome + "si sta movendo a: "+ velocita+" Kmh"); }
}

La prossima definizione di classe rappresenta lautista generico che dovr guidare uno dei mezzi sopra definiti. La definizione di classe, contiene una serie di metodi necessari a determinare lo stato del mezzo guidato dallautista rappresentato nonch, ad impostare o modificare lo stato iniziale delloggetto ma, il metodo centrale quello che simula il sorpasso tra due automobilisti. Il metodo sorpassa(Autista), le cui attivit sono schematizzate nel prossimo activity-diagram, primo richiede la velocit del veicolo da sorpassare e calcola la eventuale nuova velocit enecessaria ad effettuare il sorpasso. Secondo, tente di accelerare fino alla nuova velocit calcolata. Solo nel caso in cui la velocit finale sar maggiore del veicolo da superare, allora il sorpasso potr ritenersi concluso.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

198

Figura 68:
package src.esercizi.ereditarieta; public class Autista { /** * Nome dell'autista del veicolo */ private String nome;

Sorpasso tra due autisti

/** * Veicolo utilizzato dall'autista */ private Veicolo trasporto;


public Autista(String nome, Veicolo v) { this.nome = nome; trasporto = v; }

/** * Simula il tentativo di sorpasso di un altro autista * @return void * @param Autista : autista da superare */ public void sorpassa(Autista altro_autista) {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

199

int velocita_sorpasso = altro_autista.mezzoDiTrasporto().velocitaCorrente()+1; System.out.println(nome +"sta sorpassando "+ altro_autista.trasporto.nome +"con"+ trasporto.nome); trasporto.muovi(velocita_sorpasso); if (trasporto.velocitaCorrente() < velocita_sorpasso) System.out.println("Questo trasporto troppo lento per superare"); else System.out.println("L'autista "+ altro_autista.nome +" ha mangiato la mia polvere"); }

/** * inizia la marcia del veicolo * @return void * @param int : velocit di marcia del veicolo */ public void viaggia(int velocita_di_marcia) { trasporto.diritto(); trasporto.muovi(velocita_di_marcia); } /** * Restituisce la velocit di marcia del veicolo * @return int : velocit di marcia del veicolo in uso */ public int velocitaDiMarcia() { return trasporto.velocitaCorrente(); } /** * Restituisce la velocit massima del veicolo * @return int : velocit massima del veicolo in uso */ public int velocitaMassima() { return trasporto.velocitaMassima(); } /** * Restituisce il nome dell'autista * @return String : nome dell'autista */ public String nome() { return nome; } /** * Restituisce il veicolo usato dall'autista * @return Veicolo : veicolo in uso */ public Veicolo mezzoDiTrasporto() { return trasporto; }
}

La prossima definizione di classe contiene il metodo main per lesecuzione della simulazione. La sequenza delle operazioni compiute allinterno del metodo main della applicazione, sono rappresentate nel prossimo seuquencediagram:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

200

Figura 69:

Sequenze-Diagram per Simulazione

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

201

package src.esercizi.ereditarieta; public class Simulazione { public static void main(String[] argv) { Autista max = new Autista("Max", new Cavallo("Furia")); Autista catia = new Autista("Catia", new Macchina("Fiat 600")); Autista franco = new Autista("Franco", new Motocicletta("Suzuky XF 650")); Autista giuseppe = new Autista("Giuseppe", new Motocicletta("Honda CBR 600")); max.viaggia(15); franco.viaggia(30); catia.viaggia(40); giuseppe.viaggia(40); max.sorpassa(franco); franco.sorpassa(catia); catia.sorpassa(max); giuseppe.sorpassa(franco); franco.viaggia(franco.mezzoDiTrasporto().velocitaMassima()); giuseppe.sorpassa(franco); } }

Il risultato della esecuzione della applicazione Simulazione il seguente:


java src.esercizi.ereditarieta.Simulazione Furia sta procedendo in linea retta Furia si sta movendo a: 15 Kmh Suzuky XF 650 sta procedendo in linea retta Suzuky XF 650 si sta movendo a: 30 Kmh Fiat 600 sta procedendo in linea retta Fiat 600si sta movendo a: 40 Kmh Honda CBR 600 sta procedendo in linea retta Honda CBR 600 si sta movendo a: 40 Kmh Maxsta sorpassando Suzuky XF 650conFuria Furia si sta movendo a: 19 Kmh Questo trasporto troppo lento per superare Francosta sorpassando Fiat 600conSuzuky XF 650 Suzuky XF 650 si sta movendo a: 41 Kmh L'autista Catia ha mangiato la mia polvere Catiasta sorpassando FuriaconFiat 600 Fiat 600si sta movendo a: 20 Kmh L'autista Max ha mangiato la mia polvere Giuseppesta sorpassando Suzuky XF 650conHonda CBR 600 Honda CBR 600 si sta movendo a: 42 Kmh L'autista Franco ha mangiato la mia polvere Suzuky XF 650 sta procedendo in linea retta Suzuky XF 650 si sta movendo a: 230 Kmh Giuseppesta sorpassando Suzuky XF 650conHonda CBR 600

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

202

Honda CBR 600 si sta movendo a: 231 Kmh Questo trasporto troppo lento per superare

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

203

9 ECCEZIONI
9.1 Introduzione

Le eccezioni Java sono utilizzate in quelle situazioni in cui sia necessario gestire condizioni anomale, ed i normali meccanismi si dimostrano insufficienti ad indicare completamente una condizione di errore o, uneventuale anomalia. Formalmente, uneccezione un evento che si scatena durante lesecuzione di un programma, causando linterruzione del normale flusso delle operazioni. Queste condizioni di errore possono svilupparsi in seguito ad una gran variet di situazioni anomale: il malfunzionamento fisico di un dispositivo di sistema, la mancata inizializzazione di oggetti particolari quali ad esempio connessioni verso basi dati, o semplicemente errori di programmazione come la divisione per zero di un intero. Tutti questi eventi hanno la caratteristica comune di causare linterruzione dellesecuzione del metodo corrente. Il linguaggio Java, cerca di risolvere alcuni di questi problemi al momento della compilazione del codice sorgente, tentando di prevenire ambiguit che potrebbero essere possibili cause di errori, ma non in grado di gestire situazioni complesse o indipendenti da eventuali errori di scrittura del codice. Queste situazioni sono molto frequenti e spesso sono legate ai costruttori di classe. I costruttori sono chiamati dalloperatore new, dopo aver allocato lo spazio di memoria appropriato alloggetto da allocare, non hanno valori di ritorno (dal momento che non c nessuno che possa catturarli) e quindi risulta molto difficile controllare casi di inizializzazione non corretta dei dati membro della classe (ricordiamo che non esistono variabili globali). Oltre a quanto menzionato, esistono casi particolari in cui le eccezioni facilitano la vita al programmatore fornendo un meccanismo flessibile per descrivere eventi che, in mancanza delle quali, risulterebbero difficilmente gestibili. Torniamo ancora una volta a prendere in considerazione la classe Pila:
/** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO * @version 0.1 */
class Pila { int[] dati; int cima;

/** * Il metodo push ritorna un tipo void e prende come * parametro un numero intero da inserire sulla cima della pila * @return void * @param int dato : elemento da inserire sulla cima della pila */ public void push(int dato) {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

204

if(cima < dati.length) { dati[cima] = dato; cima ++; } }

/** * il metodo pop non accetta parametri e restituisce * lelemento sulla cima della Pila * @return int : dato sulla cima della pila */ int pop() { if(cima > 0) { cima--; return dati[cima]; } return 0; // Bisogna tornare qualcosa }
}

Dal momento che il metodo push() non prevede parametri di ritorno, necessario un meccanismo alternativo per gestire un eventuale errore causato da un trabocco dellarray, a seguito dellinserimento di un ventunesimo elemento (ricordiamo che larray pu contenere solo 20 numeri interi). Il metodo pop() a sua volta, costretto ad utilizzare il valore zero come parametro di ritorno nel caso in cui lo stack non possa contenere pi elementi. Questo ovviamente costringere ad escludere il numero intero zero, dai valori che potr contenere la pila e dovr essere riservato alla gestione delleccezione. Un altro aspetto da considerare quando si parla di gestione degli errori, quello legato alla difficolt nel descrivere e controllare situazioni arbitrariamente complesse. Immaginiamo una semplice funzione di apertura, lettura e chiusura di un file. Chi ha gi programmato con linguaggi come il C, ricorda perfettamente i mal di testa causati dalle quantit codice necessario a gestire tutti i possibili casi di errore. Grazie alla loro caratteristica di oggetti particolari, le eccezioni si prestano facilmente alla descrizione di situazioni complicate, fornendo al programmatore la capacit di rappresentare e trasportare, informazioni relativamente a qualsiasi tipologia di errore. Luso di eccezioni consente inoltre di separare il codice contenente le logiche dellalgoritmo della applicazione, dal codice per la gestione degli errori.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

205

Figura 70:

Schema di gestione degli errori in una funzione di lettura di un file

9.2

Propagazione di oggetti

Il punto di forza delle eccezioni, consiste nel permettere la propagazione di un oggetto a ritroso, attraverso la sequenza corrente di chiamate tra metodi. Opzionalmente, ogni metodo pu:

1. Catturare loggetto per gestire la condizione di errore utilizzando le informazioni trasportate, terminandone la propagazione; 2. Prolungare la propagazione ai metodi subito adiacenti nella sequenza di chiamate.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

206

Ogni metodo, che non sia in grado di gestire leccezione, interrotto nel punto in cui aveva chiamato il metodo che sta propagando lerrore. Se la propagazione di uneccezione raggiunge il metodo main dellapplicazione senza essere arrestata, lapplicazione termina in maniera incontrollata. Consideriamo lesempio seguente:
class Eccezioni { double metodo1() { double d; d=4.0 / metodo2(); System.out.println(d) ; } float metodo2() { float f ; f = metodo3(); //Le prossime righe di codice non vengono eseguite //se il metodo 3 fallisce f = f*f; return f; } int metodo3() { if(condizione) return espressione ; else // genera una eccezione e propaga loggetto a ritroso // al metodo2() } }

Questo pseudo codice Java, rappresenta una classe formata da tre metodi: metodo1() che restituisce un tipo double il cui valore determinato sulla base di quello restituito da metodo2() di tipo float. A sua volta, metodo2() restituisce un valore float calcolato in base a quello di ritorno del metodo3() che, sotto determinate condizioni, genera uneccezione. L'esecuzione del metodo1() genera quindi la sequenza di chiamate schematizzata nella Figura 72. Se si verificano le condizioni a seguito delle quali metodo3() genera leccezione, lesecuzione del metodo corrente si blocca e leccezione viene propagata a ritroso verso metodo2() e metodo1() (Figura 72). Una volta propagata, leccezione deve essere intercettata e gestita. In caso contrario si propagher sino al metodo main() dellapplicazione causando la terminazione dellapplicazione. Propagare un oggetto detto exception throwing e fermarne la propagazione exception catching.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

207

Figura 71:

Propagazione di una eccezione Java

In generale, gli oggetti da propagare come eccezioni devono derivare dalla classe base java.lang.Exception appartenente alle Java Core API. A partire da questa, possibile creare nuovi tipi di eccezioni per mezzo del meccanismo dellereditariet, specializzando il codice secondo il caso da gestire.

9.3

Oggetti throwable

Come abbiamo detto, Java consente di propagare solo alcuni tipi di oggetti. Di fatto, Java richiede che tutti gli oggetti da propagare siano derivati dalla classe base java.lang.Throwable. Nonostante questo sembri smentire quanto affermato nel paragrafo precedente, in cui affermavamo che devono derivare dalla classe base java.lang.Exception, in realt entrambe le affermazioni sono vere: vediamo perch. Tecnicamente, tutte le eccezioni generate dal linguaggio Java derivano dalla classe java.lang.Throwable, per convenzione invece, ogni eccezione definita dal programmatore deve derivare da java.lang.Exception che a sua volta deriva da Throwable (Figura 72).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

208

Figura 72:

Albero di derivazione delle eccezioni

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

209

Figura 73:

Albero di derivazione delle eccezioni incontrollate

La classe Throwable contiene i metodi necessari a gestire lo stack tracing11, ovvero la sequenza delle chiamate tra metodi, per la propagazione delloggetto a ritroso lungo la sequenza corrente delle chiamate. Questa classe ha due costruttori:

Throwable(); Throwable(String);
Entrambi i costruttori di Throwable, hanno la responsabilit di mettere in moto il meccanismo di propagazione, il secondo, in pi, imposta un dato membro di tipo String racchiudente un messaggio contenente lo stato dettagliato dellerrore al momento della generazione. Il messaggio di errore trasportato da un oggetto Throwable, accessibile grazie al metodo toString() ereditato da Object. Per poter gestire la propagazione delloggetto lungo la sequenza delle chiamate, i due costruttori effettuano una chiamata al metodo public Throwable fillInStackTrace(), il quale registra lo stato dello stack di sistema.

Al fine garantire la propagazione a ritroso, java utilizza uno stack (LIFO) per determinare la catena dei metodi chiamanti e risalire nella gerarchia.

11

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

210

Il metodo public void printStackTrace() consente invece di stampare sullo standard error la sequenza restituita dal metodo precedente. Consideriamo lesempio seguente:
public class ClasseEsempio { public static void main(String[] argv) { metodo1(null); } static void metodo1 (int[] a) { metodo2 (a); } static void metodo2(int[] b) { System.out.println(b[0]); } }

Il metodo main() della applicazione esegue una chiamata a metodo1() passandogli come argomento un array nullo, array che viene passato a sua volta al metodo2() che tenta di visualizzarne sul terminale il valore del primo elemento. Essendo nullo larray, nel momento in cuimetodo2() tenta di leggere lelemento sulla cima dellarray, lapplicazione produce una eccezione di tipo java.lang.NullPointerException. Dopo essere stata compilata ed eseguita, lapplicazione visualizzer il seguente messaggio di errore:
Exception in thread "main" java.lang.NullPointerException at ClasseEsempio.metodo2(ClasseEsempio.java:14) at ClasseEsempio.metodo1(ClasseEsempio.java:9) at ClasseEsempio.main(ClasseEsempio.java:5)

Le righe 2,3,4 del messaggio identificano la sequenza delle chiamate attive, mentre la prima riga restituisce un messaggio come definito nella stringa passata al costruttore della classe.

9.4

Eccezioni controllate ed eccezioni incontrollate

Le eccezioni Java sono suddivise in due categorie distinte: le eccezioni controllate o checked exceptions e quelle incontrollate o unchecked exceptions. Le prime, rappresentano la maggior parte delle eccezioni a livello applicativo (comprese quelle definite dallutente) e hanno bisogno di essere gestite esplicitamente (da qui la dicitura controllate). Le eccezioni di questo tipo:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

211

1. 2. 3. 4.

Possono essere definite dal programmatore; Devono essere allocate mediante operatore new; Hanno la necessit di essere esplicitamente propagate; Richiedono di essere esplicitamente gestite dal programmatore.

Ricordando la sequenza di apertura e lettura da un file, schematizzata nella Figura 70, una eccezione controllata potrebbe ad esempio rappresentare un errore durante il tentativo di apertura del file, a causa di un inserimento errato del nome. Le eccezioni derivate dalla classe java.lang.RuntimeException (Figura 73), appartengono invece alla seconda categoria di eccezioni. Le eccezioni incontrollate, sono generate automaticamente dalla Java Virtual Machine e sono relative a tutti quegli errori di programmazione che, tipicamente, non sono controllati dal programmatore a livello applicativo: memoria insufficiente, riferimento ad oggetti nulli, accesso errato al contenuto di un array ecc. ecc. Uneccezione incontrollata di tipo java.lang.ArrayIndexOutOfBoundException sar automaticamente generata se, tentassimo di inserire un elemento allinterno di un array, utilizzando un indice maggiore di quelli consentiti dalle dimensioni delloggetto.
package src.esercizi.eccezioni; public class ArrayOutOfBounds { public static void main(String[] argv) { byte[] elenco = new byte[20]; //Indici consentiti 0..19 elenco[20]=5; } } java src.esercizi.eccezioni.ArrayOutOfBounds java.lang.ArrayIndexOutOfBoundsException at src.esercizi.eccezioni.ArrayOutOfBounds.main(ArrayOutOfBounds.java:8) Exception in thread "main"

Le eccezioni appartenenti alla seconda categoria, per loro natura sono difficilmente gestibili, non sempre possono essere catturate e gestite, causano spesso la terminazione anomala dellapplicazione. Java dispone di un gran numero di eccezioni predefinite di tipo incontrollato, in grado di descrivere le principali condizioni di errore a livello di codice sorgente. Esaminiamo le pi comuni. Leccezione java.lang.NullPointerException sicuramente la pi comune tra queste, ed generata tutte le volte che lapplicazione tenti di fare uso di un oggetto nullo. In particolare sono cinque le condizioni che possono causare la propagazione di questoggetto:

1. Effettuare una chiamata ad un metodo di un oggetto nullo; 2. Accedere o modificare un dato membro pubblico di un oggetto nullo;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

212

3. Richiedere la lunghezza di un array nullo; 4. Accedere o modificare i campi di un array nullo; 5. Propagare uneccezione nulla (ovvero non allocata mediante operatore new).
Di fatto, questo tipo di eccezione viene generata automaticamente ogni volta si tenti di effettuare un accesso illegale ad un oggetto null. Leccezione java.lang.ArrayIndexOutOfBoundException , gi introdotta in questo paragrafo, rappresenta invece un specializzazione della classe base java.lang.IndexOutOfBoundException (Figura 74), utilizzata per controllare tutte le situazioni in cui si tenti di utilizzare indici errati per accedere a dati contenuti in strutture dati ordinate.

Figura 74:

Sottoclassi di IndexOutOfBoundException

La seconda le sottoclasse di java.lang.IndexOutOfBoundException la classe java.lang.StringIndexOutOfBoundException , generata da alcuni metodi delloggetto String , per indicare che un indice minore di zero oppure maggiore o uguale alla dimensione della stringa rappresentata (una stringa in Java rappresentata mediante un array di byte).

package src.esercizi.eccezioni; public class StringOutOfBounds { public static void main(String[] argv) { String nome = "massimiliano"; System.out.println("La lunghezza di nome e': "+nome.length()); //La prossima operazione genera una eccezione System.out.println("Il carattere "+nome.length()+" e': +nome.charAt(nome.length())); } }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

213

java src.esercizi.eccezioni.StringOutOfBounds La lunghezza di nome e': 12 java.lang.StringIndexOutOfBoundsException: String index out of range: 12 at java.lang.String.charAt(String.java:516) at src.esercizi.eccezioni.StringOutOfBounds.main(StringOutOfBounds.java:10) Exception in thread "main"

Infine, la classe java.lang.ArithmeticException, generata tutte le volte che si tenti di effetuare una operazione aritmetica non consentita, come ad esempio la divisione di un numero per zer.
package src.esercizi.eccezioni; public class ArithmeticErroCondition { public static void main(String[] argv) { int i = 100; int j = 0; int risultato = i/j; } } java src.esercizi.eccezioni.ArithmeticErroCondition java.lang.ArithmeticException: / by zero at src.esercizi.eccezioni.ArithmeticErroCondition.main(ArithmeticErroCondition.java:9) Exception in thread "main"

9.5

Errori Java

Oltre alle eccezioni di primo e secondo tipo, il package java.lang appartenente alle Java Core API contiene la definizione di un altro tipo particolare di classi chiamate errori. Queste classi, definite anche loro per ereditariet dalla superclasse java.lang.Throwable, rappresentano errori gravi della Java Virtual Machine che causano linterruzione anomala dellapplicazione. A causa della loro natura, questo tipo di eccezioni non sono mai catturate per essere gestite: se un errore accade, la JVM stampa un messaggio di errore su terminale ed esce. La gerarchia di queste classi schematizzata in parte nella prossima immagine:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

214

Figura 75:

Java Errors

Un errore molto comune, soprattutto per chi sviluppa applicazioni Java da poco tempo, quello definito dalla class java.lang.NoClassDefFoundError . Questa eccezione, generata dal classloader della Java Virtual Machine quando lapplicazione richiede di caricare un oggetto, la cui definizione non indicata allinterno della variabile dambiente CLASSPATH.

9.6

Definire eccezioni personalizzate

Quando definiamo nuovi oggetti, spesso desiderabile disegnare nuovi tipi di eccezioni che li accompagnino. Come specificato nei paragrafi precedenti, un nuovo tipo di eccezione deve essere derivata da java.lang.Exception, ed appartiene alla prima categoria di eccezioni: quelle controllate. Il funzionamento interno della nuova eccezione non ristretto da nessuna limitazione. Ad esempio, potremmo creare una eccezione di tipo StackOutOfBoundException, per seganalare che la Pila definita in precedenza, ha raggiunto la capienza massima.
package src.esercizi.eccezioni;

/**

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

215

* Questa eccezione generata dalla classe Pila * quando l'array raggiunge la massima capienza e * si tenta di inserire nuovi elementi all'interno. */ public class StackOutOfBoundException extends Exception { /** * Questo costruttore permette di inserire * un messaggio di errore personalizzato. * @param String s : messaggio di errore */ public StackOutOfBoundException(String s) { super(s); } /** * Questo costruttore deve essere utilizzato se, * si vuole utilizzare un messaggio di errore * comune a tutte le eccezioni di questo tipo. */ public StackOutOfBoundException() { super("StackOutOfDataException: la pila ha raggiunto la capienza massima"); }
}

La nuova classe ha due costruttori :


public StackOutOfBoundException () public StackOutOfBoundException (String s)

Il primo, non accetta argomenti, ed inizializza il messaggio di errore trasportato dalla eccezione con un valore standard; il secondo, accetta come argomento una stringa che rappresenta il messaggio di errore da trasportare. Poich una eccezione un oggetto come altri, potremmo includere allinterno qualsiasi altro tipo di informazione che ritenessimo necessario trasportare. Ad esempio, se volessimo trasportare la dimensione massima della Pila allinterno della eccezione, potremmo modificare la classe nel modo seguente:
package src.esercizi.eccezioni;

/** * Questa eccezione generata dalla classe Pila * quando l'array raggiunge la massima capienza e * si tenta di inserire nuovi elementi all'interno. */ public class StackOutOfBoundException extends Exception {
int capienza = 0;

/** * Questo costruttore permette di inserire * un messaggio di errore personalizzato. * @param String s : messaggio di errore */ public StackOutOfBoundException(String s) {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

216

super(s); }

/** * Questo costruttore permette di inserire * un messaggio di errore personalizzato e * di impostare il valore la capienza massima della pila * @param String s : messaggio di errore * @param int capienza : dimensione massima della pila */ public StackOutOfBoundException(String s, int capienza) { super(s); this.capienza=capienza; } /** * Questo costruttore deve essere utilizzato se, * si vuole utilizzare un messaggio di errore * comune a tutte le eccezioni di questo tipo. */ public StackOutOfBoundException() { super("StackOutOfDataException: la pila ha raggiunto la capienza massima"); } /** * Questo costruttore permette di assegnare * un valore alla capienza massima della pila * @param int capienza : dimensione massima della pila */ public StackOutOfBoundException(int capienza) { super("StackOutOfDataException: la pila ha raggiunto la capienza massima"); this.capienza = capienza; } /** * Restituisce il valore impostato per la capienza * massima della Pila. SE non esplicitamente * impostato, il valore restituito zero. * @return int : capienza massima della pila. */ public int capienza() { return capienza; }
}

9.7

Listruzione throw

La definizione di un oggetto di tipo Throwable, non sufficiente a completare il meccanismo di propagazione delloggetto. Nei paragrafi precedenti abbiamo affermato che, la propagazione di uneccezione controllata, deve essere gestita esplicitamente dallorigine della propagazione delloggetto, fino alla cattura e successiva gestione del medesimo. Le eccezioni, vengono propagate a ritroso attraverso la sequenza dei metodi chiamanti tramite listruzione throw, che ha sintassi:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

217

throw Oggetto ;
dove Oggetto una istanza valida delloggetto Throwable. E importante tener presente che Oggetto rappresenta un oggetto valido creato mediante loperatore new e non semplicemente un tipo di dato. Listruzione throw, causa la terminazione del metodo corrente (come se fosse stata utilizzata listruzione return), ed invia loggetto specificato al metodo chiamante. Non c modo da parte del chiamante di riprendere il metodo terminato senza effettuare una nuova chiamata. Anche in questo caso, il metodo non riprender dal ounto in cui stato interrotto.

9.8

La clausola throws

Le eccezioni possono essere propagate solo dai metodi che ne dichiarano la possibilit. Tentare di generare una eccezione allinterno di un metodo che, non ha precedentemente dichiarato di avere la capacit di propagare tali oggetti, causer un errore in fase di compilazione. Per dichiarare che un metodo ha la capacit di causare eccezioni, necessario utilizzare la clausola throws che, indica al metodo chiamante che un oggetto eccezione potrebbe essere generato o propagato dal metodo chiamato. La clausola throws ha sintassi:

tipo nome (argomenti) throws tipo_Throwable{[,tipo_Throwable]} { Corpo del metodo }


Un metodo con una clausola throws, pu generare una eccezione del tipo dichiarato da tipo_Throwable oppure, ogni tipo derivato da esso. Se un metodo contenente una clausola throws viene ridefinito (overrided) attraverso lereditariet, il nuovo metodo pu scegliere se contenere o no la clausola throws. Nel caso in cui scelga di contenerla, sar costretto a dichiarare lo stesso tipo del metodo originale o, al massimo, un tipo derivato. Analizziamo nuovamente nei dettagli il metodo membro push(int) della classe Pila:
/** * Il metodo push ritorna un tipo void e prende come * parametro un numero intero da inserire sulla cima della pila * @return void * @param int dato : elemento da inserire sulla cima della pila */ void push(int dato) { if(cima < dati.length) {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

218

dati[cima] = dato; cima ++; } }

Quando viene chiamato il metodo push, lapplicazione procede correttamente fino a che, non si tenti di inserire un elemento allinterno della pila piena. In questo caso, non possibile venire a conoscenza del fatto che lelemento andato perduto. Utilizzando il meccanismo delle eccezioni possibile risolvere il problema, modificando leggermente il metodo affinch generi uneccezione quando si tenti di inserire un elemento nella pila piena. Per far questo, utilizziamo leccezione definita nei paragrafi precedenti:
/** * Inserisce un elemento sulla cima della pila * @param int dato : dato da inserire sulla cima della struttura dati * @return void * @exception StackOutOfBoundException */ void push(int dato) throws StackOutOfBoundException { if (cima < dati.length) { dati[cima] = dato; cima++; } else { throw new StackOutOfBoundException("La pila ha raggiunto la dimensione massima. Il valore inserito andato perduto +[" + dato + "]", dati.length); } }

La nuova versione della classe Pila, generer una condizione di errore, segnalando alla applicazione lanomalia, ed evitando che dati importanti vadano perduti.

9.9

Istruzioni try / catch

A questo punto siamo in grado di generare e propagare uneccezione. Consideriamo per la prossima applicazione:
package src.esercizi.eccezioni; public class StackOutOfBound { public static void main(String[] argv) { int capienza = 10; Pila stack = new Pila(capienza); for(int i=0; i<=capienza; i++) { System.out.println("Inserisco "+i+" nella pila"); stack.push(i); System.out.println("Il dato stato inserito"); }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

219

} }

Se provassimo a compilare la nuova classe, il risultato sarebbe il seguente:


D:\PROGETTI\JavaMattone\src\src\esercizi\eccezioni\StackOutOfBound.java:12: unreported exception src.esercizi.eccezioni.StackOutOfBoundException; must be caught or declared to be thrown stack.push(i); ^ 1 error *** Compiler reported errors

Il compilatore allerta il programmatore avvertendo che, leccezione eventualmente propagata dal metodo push della classe Pila, deve essere obbligatoriamente catturata e gestita. Una volta generata uneccezione, lapplicazione destinata alla terminazione salvo che loggetto propagato sia catturato prima di raggiungere il metodo main del programma o, direttamente al suo interno. Di fatto, Java obbliga il programmatore a catturare uneccezione al pi allinterno del metodo main di unapplicazione. Questo compito spetta allistruzione catch. Questa istruzione fa parte di un insieme di istruzioni dette "guardiane", deputate alla gestione delle eccezioni ed utilizzate per racchiudere e gestire le chiamate a metodi che le generano. Listruzione catch non pu gestire da sola uneccezione, ma deve essere sempre accompagnata da un blocco try. Il blocco try utilizzato come guardiano per il controllo di un blocco di istruzioni, potenziali sorgenti di eccezioni. Try ha la sintassi seguente:

istruzione; } catch (Exception nome) { istruzioni } catch (Exception nome) { istruzioni } ..


Listruzione catch, cattura solamente le eccezioni di tipo compatibile con il suo argomento e solamente quelle generate dalle chiamate a metodi racchiuse allinterno del blocco try. Se unistruzione nel blocco try genera uneccezione, le rimanenti istruzioni nel blocco non sono eseguite. Lesecuzione di un blocco catch esclude automaticamente tutti gli altri.

try {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

220

Immaginiamo di avere definito una classe con tre metodi: f1(), f2() e f3(), e supponiamo che i primi due metodi generano rispettivamente uneccezione di tipo IOException ed uneccezione di tipo NullPointerException. Lo pseudo codice seguente, rappresenta un esempio di blocco di guardia, responsabile di intercettare e poi gestire le eccezioni propagate dai due metodi.
try {

f1(); //Una eccezione in questo punto fa saltare f2() e f3() f2(); //Una eccezione in questo punto fa saltare f3() f3();

} catch (IOException _e) { System.out.println(_e.toString()) } catch (NullPointerException _npe) { System.out.println(_npe.toString()) }

Nel caso in cui sia il metodo f1() a generare e propagare leccezione, lesecuzione del blocco di guardia (try) passerebbe il controllo della esecuzione blocco catch che dichiara di gestire leccezione generata da f1():
catch (IOException _e) { System.out.println(_e.toString()) }

Se invece il metodo f2() a propagare leccezione, f 1 ( ) termina correttamente, f3() non viene eseguito ed il controllo passa la blocco catch
catch (NullPointerException _npe) { System.out.println(_npe.toString()) }

Infine, se nessuna eccezione viene generata, i tre metodi vengono eseguiti correttamente ed il controllo passa alla prima istruzione immediatamente successiva al blocco try/catch. Per concludere, una versione corretta della applicazione StackOutOfBound potrebbe essere la seguente:
package src.esercizi.eccezioni; public class StackOutOfBound { public static void main(String[] argv) { int capienza = 10; Pila stack = new Pila(capienza); for(int i=0; i<=capienza; i++) { System.out.println("Inserisco "+i+" nella pila"); try {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

221

stack.push(i); System.out.println("Il dato stato inserito"); } catch (StackOutOfBoundException _error) { System.out.println(_error); } } } }

Il risultato della esecuzione della applicazione sarebbe il seguente:


Il dato stato inserito Inserisco 5 nella pila Il dato stato inserito Inserisco 6 nella pila Il dato stato inserito Inserisco 7 nella pila Il dato stato inserito Inserisco 8 nella pila Il dato stato inserito Inserisco 9 nella pila Il dato stato inserito Inserisco 10 nella pila src.esercizi.eccezioni.StackOutOfBoundException: La pila ha raggiunto la dimensione massima. Il valore inserito andato perduto +[10]

9.10 Singoli catch per eccezioni multiple


Differenziare i blocchi catch, affinch gestiscano ognuno particolari condizioni di errore identificate dal tipo delleccezione propagata, consente di poter specializzare il codice affinch possa prendere decisioni adeguate per la soluzione del problema verificatosi. Esistono dei casi in cui possibile trattare pi eccezioni, utilizzando lo stesso codice. In questi casi necessario un meccanismo affinch il programmatore non debba replicare inutilmente linee di codice. La soluzione a portata di mano: abbiamo detto nel paragrafo precedente che, ogni istruzione catch, cattura solo le eccezioni compatibili con il tipo definito dal suo argomento. Ricordando quanto detto parlando di oggetti compatibili, questo significa che unistruzione catch cattura ogni eccezione dello stesso tipo definito dal suo argomento o derivata dal tipo dichiarato. Daltra parte sappiamo che tutte le eccezioni sono definite per ereditariet a partire dalla classe base Exception. Nellesempio a seguire, la classe base Exception catturer ogni tipo di eccezione rendendo inutile ogni altro blocco catch a seguire.
try {

f1(); //Una eccezione in questo punto fa saltare f2() e f3() f2(); //Una eccezione in questo punto fa saltare f3() f3();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

222

} catch (java.lang.Exception _e) { System.out.println(_e.toString()) } catch (NullPointerException _npe) { //Questo codice non verr mai eseguito }

Un tipo base con un istruzione catch, pu essere utilizzato per implementare un meccanismo di gestione dellerrore che, specializza le istruzioni quando necessario o, utilizza blocchi di codice comuni a tutte le eccezioni che non richiedano una gestione particolare. Consideriamo lesempio seguente:
try { f1(); //Una eccezione in questo punto fa saltare f2() e f3() f2(); //Una eccezione in questo punto fa saltare f3() f3(); } catch (NullPointerException _npe) { //Questo blocco cattura NullPointerException } catch (java.lang.Exception _e) { //Questo blocco cattura tutte le altre eccezioni generate da f2() ed f3() System.out.println(_e.toString()) }

Ricordando la definizione dei metodi f1(), f2() ed f3(), ipotizziamo ora che f3() possa propagare un nuovo tipo di eccezione differente da quella propagata dagli altri due metodi. Cos come abbiamo configurato il blocco di guardia, il primo blocco catch cattura tutte le eccezioni generate dal metodo f1() , tutte le altre sono catturate e gestite dal secondo blocco:
catch (java.lang.Exception _e) { //Questo blocco cattura tutte le altre eccezioni generate da f2() ed f3() System.out.println(_e.toString()) }

Questo meccanismo, ricorda molto listruzione per il controllo di flusso switch, ed in particolarmodo il funzionamento del blocco identificato dalla default.

9.11 Le altre istruzioni guardiane. Finally


Di seguito ad ogni blocco catch, pu essere utilizzato opzionalmente un blocco finally che, sar sempre eseguito prima di uscire dal blocco try/catch. Questo blocco di istruzioni, offre la possibilit di eseguire sempre un certo insieme di istruzioni a prescindere da come i blocchi guardiani catturano e gestiscono le condizioni di errore.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

223

I blocchi finally non possono essere evitati dal controllo di flusso della applicazione. Le istruzioni break, continue o return allinterno del blocco try o allinterno di un qualunque blocco catch verranno eseguito solo dopo lesecuzione delle istruzioni contenute nel blocco finally.
try {

f1(); //Una eccezione in questo punto fa saltare f2() e f3() f2(); //Una eccezione in questo punto fa saltare f3() f3();

} catch (java.lang.Exception _e) { //Questo blocco cattura tutte le eccezioni System.out.println(_e.toString()) return; } finally { System.out.println(Questo blocco viene comunque eseguito) }

Solo una chiamata del tipo System.exit() ha la capacit di evitare lesecuzione del blocco di istruzioni in questione. Per concludere, la sintassi completa per il blocco guardiano diventa quindi:

try {

} catch (Exception nome) { istruzioni } catch (Exception nome) { istruzioni } finally { istruzioni }

istruzione;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

224

10

POLIMORFISMO ED EREDITARIET AVANZATA

10.1 Introduzione
Lereditariet rappresenta uno strumento di programmazione molto potente; daltra parte il semplice modello di ereditariet presentato nell'ottavo capitolo non risolve alcuni problemi di ereditariet molto comuni e se non bastasse, crea alcuni problemi potenziali che possono essere risolti solo scrivendo codice aggiuntivo. Uno dei limiti pi comuni di un modello di ereditariet singola che, non prevede lutilizzo di una classe base come modello puramente concettuale, ossia priva dellimplementazione delle funzioni base. Se facciamo un passo indietro, ricordiamo che abbiamo definito una Pila (pila) come un contenitore allinterno del quale inserire dati da recuperare secondo il criterio primo ad entrare, ultimo ad uscire. Potrebbe esserci per unapplicazione che richiede vari tipi differenti di Stack: uno utilizzato per contenere valori interi ed un altro utilizzato per contenere valori reali a virgola mobile. In questo caso, le regole da utilizzare per manipolare lo Stack sono le stesse, quello che cambia sono i tipi di dato contenuti. Anche se utilizzassimo la classe Pila, riportata di seguito, come classe base, sarebbe impossibile per mezzo della semplice ereditariet creare specializzazioni dellentit rappresentata a meno di riscrivere una parte sostanziale del codice del nostro modello in grado di contenere solo valori interi.
/** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO * @version 0.1 */
class Pila { int[] dati; int cima;

/** * Il metodo push ritorna un tipo void e prende come * parametro un numero intero da inserire sulla cima della pila * @return void * @param int dato : elemento da inserire sulla cima della pila */ public void push(int dato) { if(cima < dati.length) { dati[cima] = dato; cima ++; }
}

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

225

/** * il metodo pop non accetta parametri e restituisce * lelemento sulla cima della Pila * @return int : dato sulla cima della pila */ int pop() { if(cima > 0) { cima--; return dati[cima]; } return 0; // Bisogna tornare qualcosa }
}

Un altro problema che non risolto dal modello ad ereditariet, quello di non consentire ereditariet multipla, ossia la possibilit di derivare una classe da due o pi classi base; la parola chiave extends prevede solamente un singolo argomento. Java risolve tutti questi problemi con due variazioni al modello di ereditariet definito: interfacce e classi astratte. Le interfacce, sono entit simili a classi, ma non contengono implementazioni delle funzionalit descritte. Le classi astratte, anchesse simili a classi normali, consentono di implementare solo parte delle caratteristiche delloggetto rappresentato. Interfacce e classi astratte assieme, permettono di definire un concetto senza dover conoscere i dettagli di una classe, posponendone limplementazione attraverso il meccanismo dellereditariet.

10.2 Polimorfismo : uninterfaccia, molti metodi


Polimorfismo la terza parola chiave del paradigma ad oggetti. Derivato dal greco, significa pluralit di forme ed la caratteristica che ci consente di utilizzare ununica interfaccia per una moltitudine di azioni. Quale sia la particolare azione eseguita, dipende solamente dalla situazione in cui ci si trova. Per questo motivo, parlando di programmazione, il polimorfismo pu essere riassunto nellespressione uninterfaccia, molti metodi. Ci significa che, possiamo definire uninterfaccia unica da utilizzare in molti casi collegati logicamente tra loro. Oltre a risolvere i limiti del modello di ereditariet, Java realizza il polimorfismo per mezzo delle interfacce.

10.3 Interfacce
Formalmente, uninterfaccia Java rappresenta un prototipo e consente al programmatore di definire lo scheletro di una classe: nomi dei metodi, tipi

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

226

ritornati, lista degli argomenti. Al suo interno, il programmatore pu definire dati membro purch di tipo primitivo con ununica restrizione: Java considerer implicitamente questi dati come static e final (costanti). Le interfacce, sono molto utili per definire gruppi di classi aventi un insieme minimo di metodi in comune, senza fornirne per una implementazione comune. Ad esempio, se volessimo generalizzare la definizione di Pila, potremmo definire uninterfaccia contenente i prototipi dei metodi push e pop comuni a tutti gli oggetti di questo tipo. Poich, il tipo di dato gestito da questi oggetti dipende dallimplementazione della pila, ogni classe costruita da questinterfaccia, avr la responsabilit di gestire in modo appropriato i propri dati. Quello che uninterfaccia non consente, , infatti, limplementazione del corpo dei metodi. Di fatto, uninterfaccia stabilisce il protocollo di una classe, senza preoccuparsi dei dettagli di implementazione.

10.4 Definizione di uninterfaccia


Per consentire al programmatore la dichiarazione di interfacce, Java dispone della parola chiave interface. La sintassi necessaria a definire una interfaccia la seguente:

[public | abstract]interface identificatore [extends tipo]{ corpo_dell_interfaccia }


Gli elementi necessari a dichiarare questo tipo particolare di classi, sono quindi elencati di seguito:

1. I modificatori opzionali public o private per definire gli attributi della classe; 2. La parola chiave interface; 3. Un nome che identifica la classe; 4. Opzionalmente, la clausola extends se linterfaccia definita a partire da una classe base; 5 . Le dichiarazioni dei dati membro della classe, e dei prototipi dei metodi.
Proviamo, ed esempio, a definire uninterfaccia che ci consenta di generalizzare la definizione della classe Pila. Per poterlo fare, necessario ricordare che loggetto Object, in quanto padre di tutte le classi Java, compatibile con qualsiasi tipo definito o, definibile. Questa caratteristica alla base di ogni generalizzazione in Java, in quanto, ogni oggetto pu essere ricondotto ad Object e viceversa, da Object possono essere derivati tutti gli

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

227

altri oggetti. Linterfaccia generalizzata di un oggetto generico Pila quindi la seguente:


package src.esercizi.eccezioni; public interface Stack { /** * Inserisce un elemento sulla cima della pila * @param int dato : dato da inserire sulla cima della struttura dati * @return void * @exception <{StackOutOfBoundException}> */ void push(Object dato) throws StackOutOfBoundException;

/** * Ritorna il primo elemento della pila * @return int : primo elemento sulla pila */ Object pop();
}

Consentitemi ora una breve digressione. Dallanalisi della definizione dellinterfaccia Stack, potrebbe a questo punto sorgere il seguente dubbio: avendo utilizzato un oggetto per definire i prototipi dei metodi della classe, come facciamo a rappresentare pile i cui elementi sono rappresentati mediante tipi primitivi (int, long, double ecc. ecc.)? La risposta immediata. Di fatto, nonostante Java disponga di dati di tipo primitivo, essendo un linguaggio orientato ad oggetti, deve poter rappresentare anche i tipi primitivi in forma di oggetto. Nella prossima figura, stato riportato parte del contenuto del package java.lang appartenente alle Java Core API.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

228

Figura 76:

Tipi primitivi come oggetti

La Pila di interi, potrebbe ad esempio essere sostituita da una Pila di oggetti di tipo java.lang.Integer.

10.5 Implementare una interfaccia


Poich uninterfaccia rappresenta solo il prototipo di una classe, affinch possa essere utilizzata necessario che ne esista unimplementazione che rappresenti una classe allocabile. Per implementare uninterfaccia, Java mette a disposizione la parola chiave implements con sintassi:

class nome implements interfaccia{ corpo_della_classe }


La nostra classe Stack potr quindi essere definita da un modello nel modo seguente:
package src.esercizi.eccezioni.stack;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

229

import src.esercizi.eccezioni.StackOutOfBoundException;

/** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO * @version 0.5 */ public class PilaDiInteri implements Stack { /** Contenitore per i dati della Pila */ Integer[] dati; /** Punta al primo elmento corrente nella struttura dati */ int cima; /** Costruisce una pila contenente al massimo 10 elementi */ public PilaDiInteri() { dati = new Integer[20]; cima = 0; } /** * Costruisce una Pila, consentendo di impostarne la dimensione massima * @param int dimensione : dimensione massima della pila */ public PilaDiInteri(int dimensione) { dati = new Integer[dimensione]; cima = 0; } /** * Inserisce un elemento sulla cima della pila * @param int dato : dato da inserire sulla cima della struttura dati * @return void * @exception <{StackOutOfBoundException}> */ public void push(Object dato) throws StackOutOfBoundException { if (cima < dati.length) { dati[cima] = (Integer)dato; cima++; } else { throw new StackOutOfBoundException("La pila ha raggiunto la dimensione massima. Il valore inserito andato perduto +[" + dato + "]", dati.length); } } /** * Ritorna il primo elemento della pila * @return int : primo elemento sulla pila */ public Object pop() { if (cima > 0) { cima--; return dati[cima]; } return null; // Bisogna tornare qualcosa }
}

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

230

In un class-diagram, questo rapporto tra classi e interfacce, rappresentato in modo analogo alla relazione di ereditariet tra classi Java. La relazione tra linterfaccia Stack e la classe PilaDiInteri in un class-diagram mostrata nella figura 77.

Figura 77:

Relazione di ereditariet Classe-Interfaccia

Quando una classe implementa uninterfaccia, obbligata a fornirne unimplementazione di tutti i prototipi dei metodi. In caso contrario, il compilatore generer un messaggio di errore. Di fatto, possiamo pensare ad uninterfaccia come ad una specie di contratto che lambiente di Java stipula con una classe. Implementando uninterfaccia, la classe, non si limita a definire un concetto da un modello logico (molto utile al momento del disegno dellapplicazione), ma assicurer limplementazione di almeno i metodi definiti nellinterfaccia. La relazione che intercorre tra uninterfaccia ed una classe Java, anchessa una forma di ereditariet (da qui la similitudine tra le due simbologie UML). Se linterfaccia dovesse contenere definizioni di dati membro, le stesse saranno ereditate dalla classe costruita da essa mediante la clausola implements. Conseguenza diretta di questaffermazione, la possibilit di utilizzare le interfacce come tipi per definire variabili reference in grado di far riferimento ad oggetti definiti mediante implementazione di uninterfaccia.
Stack s = new PilaDiInteri();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

231

Varranno in questo caso tutte le regole gi discusse nel capitolo settimo parlando di variabili reference ed ereditariet.

10.6 Ereditariet multipla in Java


Se loperatore extends limitava la derivazione di una classe da una sola classe base, loperatore implements ci consente di implementare una classe da quante interfacce desideriamo, semplicemente elencando le interfacce da implementare, separate tra loro con una virgola.

class nome implements interfaccia{[, interfaccia]} { corpo_della_classe }


Questa caratteristica permette al programmatore di creare gerarchie di classi molto complesse in cui, una classe eredita la natura concettuale di molte entit. Se una classe implementa interfacce multiple, dovr fornire tutte le funzionalit per i metodi definiti in tutte le interfacce.

10.7 Classi astratte


Capitano casi in cui lastrazione offerta dalle interfacce, eccede rispetto alle necessit del programmatore (le interfacce non si possono implementare funzionalit alcune). Per risolvere questo problema, Java fornisce un metodo per creare classi base astratte ossia, classi solo parzialmente implementate. Le classi astratte, possono essere utilizzate come normali classi base e rispettano le definizioni fornite per la compatibilit tra classi; tuttavia, queste classi non sono complete e come le interfacce, non possono essere allocate direttamente. Per estendere le classi astratte, si utilizza la clausola extends e di conseguenza, solo una classe astratta pu essere utilizzata per creare nuove definizioni di classe. Questo meccanismo fornisce la scappatoia alla costrizione imposta dalle interfacce, di doverne implementare tutte le funzionalit allinterno di uneventuale nuova definizione di classe, aumentando la flessibilit del linguaggio, nella creazione delle gerarchie di derivazione. Per definire una classe astratta Java mette a disposizione la parola chiave abstract. Questa clausola informa il compilatore che, alcuni metodi della classe potrebbero essere semplicemente prototipi o astratti.

abstract class nome { attributi metodi_astratti

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

232

metodi_non_astratti

Ogni metodo che rappresenta semplicemente un prototipo, deve essere dichiarato abstract, utilizzando la sintassi seguente:

[private|public] abstract tipo_di_ritorno nome(argomento [,argomento] ) { istruzione [istruzione] }


Quando una classe deriva da una classe base astratta, il compilatore richiede che tutti i metodi astratti siano definiti. Se la necessit del momento costringe a non definire questi metodi, la nuova classe dovr a sua volta essere definita abstract. Un esempio duso del modificatore abstract potrebbe essere rappresentato da una classe che rappresenta un terminale generico. Un terminale ha alcune funzionalit comuni quali lo spostamento di un cursore, la stampa di una stringa, consente di spostare il cursore da un punto ad un altro oppure di pulire larea visibile da ogni carattere. Le propriet di un terminale sono sicuramente: il numero di righe e colonne da cui composto e la posizione corrente del cursore. Ovviamente, tra le funzionalit di un terminale, quelle indipendenti dalle specifiche di basso livello del sistema potranno essere implementate gi a livello di oggetto generico, le altre si potranno implementare solo allinterno di classi specializzate. Nel prossimo esempio, viene mostrata una possibile definizione generica di un terminale. Utilizzando le classi astratte, sar possibile definire i prototipi di tutti i metodi che dovranno essere implementati allinterno di classi specializzate, fornendo per la definizione completa di quelle funzionalit di pi alto livello, comuni a tutti i terminali.

package src.esercizi.polimorfismo;

/** * Definizione generica di un terminale a video. */ public abstract class Terminale {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

233

private int rigacorrente, colonnacorrente, numrighe, numcolonne;

/** * Crea un terminale le cui dimensioni sono specificate * dal numero massimo di righe e di colonne visibili. * @return void * @param int rows : numero massimo di righe * @param int col : numero massimo di colonne */ public Terminale(int rows, int cols) { numrighe = rows; numcolonne = cols; rigacorrente = 0; colonnacorrente = 0; } /** * Sposta il cursone alle coordinate precisate * @return void * @param int rows : indice della riga * @param int col : indice della colonna */ public abstract void spostaIlCursore(int rows, int col); /** * Inserisce una stringa di caratteri alle coordinate spcificate * @return void * @param int rows : indice della riga * @param int col : indice della colonna * @param String buffer : sequenza di caratteri da stampare a terminale */ public abstract void inserisci(int rows, int col, String buffer); /** * IPulisce il terminale * @return void */ public void pulisci() { int r, c; for (r = 0; r < numrighe; r++) for (c = 0; c < numcolonne; c++) { spostaIlCursore(r, c); inserisci(r, c, " "); } }
}

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

234

11
11.1 Introduzione

JAVA THREADS

Java un linguaggio multithread, cosa che sta a significare che:

un programma pu essere eseguito logicamente in molti luoghi nello stesso momento.


Il multithreading, consente di creare applicazioni in grado di utilizzare la concorrenza logica tra i processi, con il vantaggio di poter consentire ai thread di continuare a condividere lo spazio di memoria riservato ai dati.

Figura 78:

Thread Macchina e Thread Logici

Nel diagramma nella Figura 79, viene schematizzato lipotetico funzionamento della concorrenza logica. Dal punto di vista dellutente, i thread logici appaiono come una serie di processi che eseguono parallelamente le loro funzioni. Dal punto di vista della applicazione, rappresentano processi logici che, da una parte condividono la stessa memoria della applicazione che li ha creati, dallaltra concorrono con il processo principale al meccanismo di assegnazione del processore su cui lapplicazione in esecuzione.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

235

11.2 Thread e processi


Obiettivo di questo capitolo descrivere come programmare applicazioni multithread utilizzando il linguaggio Java. Prima di procedere, importante capire a fondo il significato di programmazione concorrente, come scrivere applicazioni utilizzando questa tecnica, ed infine la differenza tra thread e processi.

Cara, vai comprare le aspirine in farmacia, mentre misuro la febbre al bambino


Nonostante possa sembrare bizzarro, quello che per noi pu sembrare un comportamento banale, dal punto di vista di un calcolatore, rappresenta un problema gravoso. Eseguire contemporaneamente, due azioni logicamente correlate, comporta uno sforzo non indifferente dovendo gestire problematiche complesse quali:

1. La condivisione dei dati tra le entit che dovranno concorrere a risolvere il problema; 2 . Laccesso concorrente alle risorse condivise del sistema (stampanti, supporti per i dati, periferiche, porte di comunicazione);
Le tecniche di programmazione pi comuni, fanno uso di processi multipli o thread. Un processo, unapplicazione completa con un proprio spazio di memoria. Molti sistemi operativi, consentono lesecuzione contemporanea di tanti processi, supportando la condivisione o la comunicazione di dati mediante rete, memoria condivisa, pipe, ecc... Il sistema operativo UNIX ad esempio, dispone della funzione fork() che, consente ad un processo di sdoppiarsi in un processo figlio (Figura 79).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

236

Figura 79:

Fork UNIX

Il risultato finale saranno due processi distinti, ognuno con una propria area dati. I thread, conosciuti anche come ligth-weight processes o processi leggeri (LWP), vivono allinterno di uno stesso processo con il quale condividono istruzioni ed area dati (Figura 80).

Figura 80:

Thread

I thread, in definitiva, consentono di eseguire pi parti di codice di una stessa applicazione, nello stesso istante.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

237

11.3 Vantaggi e svantaggi nelluso di thread


La scelta tra la prima e la seconda soluzione proposte nel paragrafo precedente, non cos scontata come pu sembrare. Di fatto, i thread non rappresentano sempre la tattica migliore nellapproccio ad un problema di programmazione in concorrenza. In questo paragrafo, cercheremo di comprendere quali sono i vantaggi e gli svantaggi che luso di thread comporta rispetto a quello di processi multipli.

1. I thread sono molto efficienti nellutilizzo della memoria.


Luso di processi multipli, necessita di rispetto a quella richiesta dai thread. eseguito ha bisogno di una propria differentemente dai processi, utilizzano processo che li ha generati. una quantit di memoria maggiore Di fatto, ogni processo per essere area dati ed istruzioni. I thread, la stessa area dati ed istruzioni del

2. I thread comunicano mediante memoria condivisa.


Poich i thread condividono la stessa area di memoria, la comunicazione tra loro pu avvenire utilizzando semplicemente puntatori a messaggi. La comunicazione tra processi implica invece la trasmissione di un messaggio da unapplicazione ad un'altra. La comunicazione tra processi di conseguenza, pu essere causa della duplicazione di grandi quantit di dati.

3. La concorrenza tra thread molto efficiente.


Processi attivi e thread concorrono tra loro allutilizzo del processore. Poich il contesto di un thread sicuramente minore di quello di un processo, minore anche la quantit di dati da salvare sullo stack di sistema. Lalternanza tra thread risulta quindi pi efficiente di quella tra processi.

4. I thread sono parte del codice della applicazione che li genera.


Per aggiungere nuovi thread ad un processo, necessario modificare e compilare tutto il codice della applicazione con tutti i rischi che comporta questa operazione. Viceversa, i processi sono entit dinamiche indipendenti tra loro. Aggiungere un processo significa semplicemente eseguire una nuova applicazione.

5. I thread possono accedere liberamente ai dati utilizzati da altri thread sotto il controllo del medesimo processo.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

238

Condividere la stessa area di memoria con il processo padre e con gli altri thread figli, significa avere la possibilit di modificare dati vitali per gli altri thread o, nel caso peggiore per il processo stesso. Viceversa, un processo non ha accesso alla memoria riservata da un altro. Concludendo, i thread sono pi efficienti dei processi e consentono di sviluppare facilmente applicazioni complesse e molto efficienti. Il costo di questefficienza per alto poich, spesso necessario implementare complesse procedure per la gestione dellaccesso concorrente ai dati condivisi con gli altri thread.

11.4 La classe java.lang.Thread


Il primo metodo per creare un thread estendere la classe base java.lang.Thread mediante la direttiva extends ( figura 81). La classe java.lang.Thread, non una classe astratta e incapsula il codice necessario al funzionamento del thread : avvio, esecuzione, interruzione. Tra i metodi definiti, il metodo

public void run()


deve essere utilizzato per implementare le operazioni eseguite dal thread riscrivendone la definizione nella nuova classe mediante il meccanismo di overriding. In genere, run() lunico metodo su cui effettuare overriding. Nel caso in cui il metodo non sia riscritto, al momento dellavvio del thread, eseguir la funzione nulla.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

239

Figura 81:

La classe java.lang.Thread

La classe java.lang.Thread fornita dei metodi necessari alla esecuzione, gestione e interruzione di un thread. I tre principali: Un esempio di thread definito per ereditariet dalla classe Thread il seguente:
package src.esercizi.thread; public class MioPrimoThread extends Thread { public void run() { try { int i=0; while(true) { sleep(1000); i++; System.out.println("Il valore di i: "+i); } } catch(InterruptedException _ie) { System.out.println(_ie); } } }

In questesempio abbiamo definito una classe che, estende Thread e ne ridefinisce il metodo run(). Il metodo run(), contiene un ciclo infinito che, ad

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

240

ogni iterata si addormenta per un secondo e successivamente aggiorna il valore di una variabile intera visualizzandolo sul terminale. Di fatto, tutti i thread contengono cicli infiniti poich, un thread cessa la propria esecuzione nel momento in cui metodo run() termina ed esce. Nel prossimo esempio, cessiamo lesecuzione del thread provocando linterruzione del ciclo mediante listruzione break, quando la variabile intera assume il valore dieci.
package src.esercizi.thread; public class MioPrimoThread extends Thread { public void run() { try { int i=0; while(true) { sleep(1000); i++; System.out.println("Il valore di i: "+i); //Se i vale 10, il thread viene terminato //provocando linterruzione del ciclo infinito. if(i==10) break; } } catch(InterruptedException _ie) { System.out.println(_ie); } } }

E importante notare che ogni thread, una volta entrato allinterno del ciclo infinito, ogni tanto deve necessariamente addormentarsi per qualche istante. Utilizzando il metodo sleep(long) ereditato dalla superclasse, possibile ottenere leffetto desiderato congelando il thread per il tempo specificato dallargomento del metodo espresso in millisecondi. In caso contrario, il thread consumer tutto il tempo di cpu impedendo ad altri thread o applicazioni di proseguire nellesecuzione.

11.5 Linterfaccia Runnable


Lereditariet singola di Java impedisce ad una classe di avere pi di una classe padre, sebbene possa implementare un numero arbitrario di interfacce. In tutte le sitazioni in cui non sia possibile derivare una classe da java.lang.Thread, pu essere utilizzata linterfaccia java.lang.Runnable, come schematizzato in figura 82. Linterfaccia Runnable contiene il prototipo di un solo metodo:
public interface Runnable

{
public void run();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

241

necessario ad indicare il metodo che dovr contenere il codice del nuovo thread (esattamente come definito nel paragrafo precedente). La classe MioPrimoThread potrebbe essere riscritta come mostrato nel prossimo esempio.
package src.esercizi.thread; public class MioPrimoThread implements Runnable { public void run() { try { int i=0; while(true) { Thread.sleep(1000); i++; System.out.println("Il valore di i: "+i); } } catch(InterruptedException _ie) { System.out.println(_ie); } } }

Figura 82:

Linterfaccia Runnable

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

242

In generale, tutti i thread sono creati utilizzando linterfaccia Runnable (anche la classe java.lang.Thread implementa questinterfaccia come schematizzato nella figura 82), e questo dovuto alla flessibilit offerta dalle interfacce rispetto alle classi, nel disegno delle gerarchie di ereditariet. Daltra parte, uninterfaccia rappresenta solo un modello. Ricordiamo, infatti, che uninterfaccia pu contenere solo metodi astratti e variabili di tipo static final, di conseguenza un interfaccia non pu essere utilizzata per creare un oggetto, in altre parole, non potremmo scrivere: Runnable mioThread = new Runnable() In definitive quindi, implementare la classe Runnable non sufficiente a creare un oggetto thread, semplicemente, abbiamo definito una classe che assomiglia ad un thread. Nel paragrafo precedente, abbiamo detto che, molto del lavoro di un thread svolto dalla classe java.lang.Thread che contiene la definizione dei metodi necessari a gestirne il ciclo di vita completo. Per creare un thread da un oggetto di tipo Runnable possiamo utilizzare la classe java.lang.Thread dotata di un costruttore che accetta come argomento un tipo Runnable. Nel prossimo esempio utilizzando le due versioni della classe MioPrimoThread creeremo due applicazioni uguali che, creano un Thread partendo rispettivamente da un tipo Thread e da un tipo Runnable.
package src.esercizi.thread; public class MioPrimoThread extends Thread { public void run() { try { int i=0; while(true) { Thread.sleep(1000); i++; System.out.println("Il valore di i: "+i); } } catch(InterruptedException _ie) { System.out.println(_ie); } } public static void main(String[] args) { //Crea un thread semplicemente creando un oggetto di tipo MioPrimoThread MioPrimoThread miothread = new MioPrimoThread(); } }

Il metodo main di questa prima versione della applicazione, crea un oggetto thread semplicemente creando un oggetto di tipo MioPrimoThread.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

243

package src.esercizi.thread; public class MioPrimoThread implements Runnable { public void run() { try { int i=0; while(true) { sleep(1000); i++; System.out.println("Il valore di i: "+i); } } catch(InterruptedException _ie) { System.out.println(_ie); } } public static void main(String[] args) { //Crea un thread creando un oggetto di tipo Thread //utilizzando il costruttore che accetta un oggetto Runnable MioPrimoThread t= new MioPrimoThread(); Thread miothread = new Thread(t); } }

Il metodo main della seconda versione della applicazione, crea un oggetto thread creando: prima un oggetto di tipo MioPrimoThread, poi un oggetto di tipo Thread passando il primo oggetto come argomento del metodo costruttore del secondo.

11.6 Ciclo di vita di un Thread


Creare un thread non sufficiente poich, necessario che sia in esecuzione il metodo run() affinch il thread possa ritenersi attivo. La responsabilit di avviare lesecuzione del thread affidata al metodo pubblico start() della classe Thread, il cui funzionamento schematizzato nel sequence-diagram in figura 83.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

244

Figura 83:

Ciclo di vita di un thread

Una volta in esecuzione, un thread pu essere fermato utilizzando in metodo interrupt() . Quando un thread fermato mediante chiamata al metodo interrupt() , non pu in nessun modo essere riavviato mediante il metodo start(). Nel caso in cui sia necessario sospendere e poi riprendere lesecuzione del thread, necessario utilizzare i due metodi suspend() e resume() che oppure, come gi detto in precedenza, possiamo utilizzare il metodo sleep(long). Il metodo sleep(long) consente di interrompere lesecuzione di un thread per un periodo di tempo determinato. I due metodi suspend() e resume(), il cui funzionamento descritto nel sequence-diagram in figura 84 , rispettivamente: interrompe lesecuzione del thread a tempo indeterminato, riprende lesecuzione di un thread, interrotta dal metodo suspend().

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

245

Figura 84:

Sospendere e riprendere lesecuzione

11.7 Lapplicazione Orologio


Per fissare i concetti introdotti nei paragrafi precedenti, sviluppiamo una breve applicazione di esempio che implementa un orologio di sistema un po particolare, il cui pannello principale mostrato nella prossima figura.

Figura 85:

Pannello principale di orologio

Allavvio della applicazione, viene mostrato un pannello contenente i tre pulsanti sulla destra del pannello e larea contenente lora di sistema vuota. Premendo il pulsante start (Figura 86), lapplicazione inizia a visualizzare lora di sistema aggiornandone il valore ogni secondo.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

246

Figura 86:

Avvio della applicazione orologio

Figura 87:

Interruzione dellesecuzione

Figura 88:

Ripresa della esecuzione

Premendo il pulsante sospendi, possibile sospendere, e successivamente riprendere, laggiornamento dellora nel pannello della applicazione (figura 8 7 , 8 8 ). Il pulsante stop interrompe definitivamente lesecuzione dellapplicazione. In nessun modo sar possibile riavviarne lesecuzione. Lapplicazione formata dalle tre classi schematizzate nel class-diagram in figura 89.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

247

Figura 89:

Class-Diagram di orologio

La classe orologio, il cui codice sorgente riportato di seguito, contiene semplicemente il metodo main dellapplicazione il quale, crea il pannello principale e ne imposta le dimensioni iniziali e la posizione sullo schermo.
package src.esercizi.thread; import javax.swing.UIManager; import java.awt.Dimension; import java.awt.Toolkit; public class Orologio { public Orologio() { Pannello frame = new Pannello(); //Centra il frame sullo schermo Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); frameSize.height = ((frameSize.height > screenSize.height) ? screenSize.height : frameSize.height); frameSize.width = ((frameSize.width > screenSize.width) ? screenSize.width : frameSize.width); frame.setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height frameSize.height) / 2); frame.setVisible(true); } public static void main(String[] argv) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

248

} new Orologio(); } }

Il pannello principale rappresentato dalla classe Pannello . Notiamo che, questa classe stata sviluppata utilizzando le librerie swing di Java. Con swing identificata la collezione di classi appartenenti alle Java Core API, necessarie allo sviluppo di interfacce grafiche. Non essendo obiettivo di questo libro introdurre alle librerie swing , rimandiamo la trattazione dellargomento alla documentazione ufficiale della SUN Microsystem che, pu essere scaricata da internet dal sito ufficiale di Java: www.javasoft.com.
package src.esercizi.thread; import import import import import import import import import import javax.swing.JFrame; java.awt.event.WindowEvent; java.awt.Dimension; java.awt.BorderLayout; javax.swing.JPanel; java.awt.Rectangle; javax.swing.JLabel; javax.swing.JButton; java.awt.event.ActionEvent; java.awt.event.ActionListener;

public class Pannello extends JFrame { /** Crea un nuovo JFrame */ public Pannello() { initGUI(); pack(); td = new ThreadClock(display); t = new Thread(td, "Orologio"); }

/** Questo metodo chiamato dal costruttore per inizializzare la fionestra. */ private void initGUI() { start.setText("START"); start.setBounds(new java.awt.Rectangle(229, 70, 117, 30)); start.setActionCommand("START"); start.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { startActionPerformed(e); }}); stop.setText("STOP"); stop.setBounds(new java.awt.Rectangle(229, 37, 117, 30)); stop.setActionCommand("SOSPENDI"); stop.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { stopActionPerformed(e); }}); getContentPane().setLayout(layout); JPanel content = new JPanel(); content.setPreferredSize(new java.awt.Dimension(350, 127)); content.setLayout(null); content.setSize(new java.awt.Dimension(350, 150)); content.add(display); content.add(resume);

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

249

content.add(stop); content.add(start); getContentPane().add(content, BorderLayout.CENTER); setTitle("Orologio di sistema"); setSize(new java.awt.Dimension(354, 150)); addWindowListener( new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { exitForm(evt); } }); display.setText(""); display.setBounds(new java.awt.Rectangle(5, 21, 216, 65)); display.setFont(new java.awt.Font("Verdana", java.awt.Font.PLAIN, 36)); display.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0), 1)); resume.setText("SOSPENDI"); resume.setBounds(new java.awt.Rectangle(229, 4, 117, 30)); resume.setActionCommand("SOSPENDI"); resume.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { resumeActionPerformed(e); } }); }

/** Chiude l'applicazione */ private void exitForm(WindowEvent evt) { System.exit(0); }


public void resumeActionPerformed(ActionEvent e) { if (resume.getText().equals("SOSPENDI")) { t.suspend(); resume.setText("RIPRENDI"); } else { t.resume(); resume.setText("SOSPENDI"); } } public void stopActionPerformed(ActionEvent e) { t.interrupt(); } public void startActionPerformed(ActionEvent e) { t.start(); } private private private private private private private } BorderLayout layout = new BorderLayout(); JLabel display = new JLabel(); JButton resume = new JButton(); JButton stop = new JButton(); JButton start = new JButton(); Thread t; ThreadClock td;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

250

Infine, la classe ThreadClock, implementa linterfaccia Runnable e allinterno del metodo run() contiene le istruzioni necessarie a calcolare lora corrente di sistema per poi trasformarla in forma di stringa. Il metodo costruttore della classe accetta come argomento un oggetto di tipo Jlabel, che rappresenta la componente dellinterfaccia che visualizza la stringa contenente lora di sistema.

import javax.swing.JLabel; import java.util.Date; public class ThreadClock implements Runnable { public ThreadClock(JLabel pannello) { setDisplay(pannello); } public void run() { try { int i = 0; while (true) { Date data = new Date(); String ora = data.getHours()+":"+ data.getMinutes()+":"+data.getSeconds(); getDisplay().setText(ora); Thread.sleep(1000); } } catch (InterruptedException _ie) { System.out.println(_ie); } } private JLabel getDisplay(){ return display; } private void setDisplay(JLabel display){ this.display = display; } private JLabel display; }

Il metodo costruttore della classe pannello, crea un oggetto di tipo ThreadClock e lo utilizza come argomento del metodo costruttore di un oggetto di tipo Thread. Tramite i pulsanti forniti dallinterfaccia, possibile interagire con il thread creato. In questo modo sar possibile provare i vari stati in cui un thread pu trovarsi durante il suo ciclo di vita. In particolare:

1. Il pulsante start avvia il thread. Viene eseguito il metodo run() di ThreadClock e lapplicazione inizia ad aggiornare lora di sistema. Premere questo pulsante, equivale ad eseguire il metodo start() della classe java.lang.Thread;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

251

2. Il pulsante stop ferma il thread e lorologio non viene pi aggiornato. Premere questo pulsante, equivale ad eseguire il metodo interrupt() della classe java.lang.Thread. Da questo momento in poi, non sar pi possibile riavviare il thread; 3. Il pulsante sospendi/riavvia alternamente sospende e riavvia il thread utilizzando i due metodi suspend() e resume() di java.lang.Thread.

11.8 Priorit di un thread


Nel terzo capitolo, parlando di supporto nativo per i thread, abbiamo introdotto il concetto di code Round Robin per la gestione della concorrenza tra i processi che si candidano per utilizzo del processore. Daltra parte abbiamo anche affermato che i thread sono anchessi processi e come tale concorrono tra loro allassegnamento delle risorse della macchina. Nel modello introdotto, tutti i processi gareggiano su uno stesso piano. Non esistono processi privilegiati e soprattutto, ogni processo utilizza le risorse della macchina per uno spazio di tempo (tecnicamente slice) uguale per tutti. I moderni calcolatori utilizzano meccanismi pi complessi in grado di stabilire la priorit di un processo rispetto agli altri. Un processo con priorit pi alta avr pi possibilit di altri di ottenere le risorse della macchina e soprattutto, per una slice maggiore. La classe java.lang.Thread dispone dei metodi

public final void setPriority(int newPriority); public final int getPriority();


che, rispettivamente, imposta la priorit di un thread e ritorna la priorit del thread. I valori ammessi per la priorit di un thread sono compresi tra java.lanag.Thread.MAX_PRIORITY e java.lanag.Thread.MIN_PRIORITY che valgono rispettivamente 1 e 10. La priorit di base di un thread java.lanag.Thread.NORM_PRIORITY che vale 5. Nel prossimo esempio, lapplicazione Priorita esegue due thread identici aventi per diverse priorit. Il metodo run() dei thread contiene un ciclo infinito che stampa sul terminale una stringa che lo identifichi.
package src.esercizi.thread; public class Priorita extends Thread { public Priorita(String nome) { setNome(nome); } public String getNome(){ return nome; } public void setNome(String nome){ this.nome = nome; } public void run() { while(true)

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

252

System.out.println(nome); } public static void main(String[] argv) { Priorita T1 = new Priorita("Thread con priorit alta"); Priorita T2 = new Priorita("Thread con priorit bassa"); T1.setPriority(Thread.MAX_PRIORITY); T2.setPriority(Thread.MIN_PRIORITY); T1.start(); T2.start(); } private String nome; }

Eseguendo lapplicazione, il risultato evidenzia chiaramente come, il thread T1 con priorit pi alta sia privilegiato rispetto a quello con priorit pi bassa.
Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread Thread con con con con con con con con con con con con con con con con con con priorit priorit priorit priorit priorit priorit priorit priorit priorit priorit priorit priorit priorit priorit priorit priorit priorit priorit alta alta alta alta alta alta alta alta bassa alta alta alta alta alta alta alta alta bassa

.. Capita spesso che thread con priorit molto alta facciano uso di risorse di sistema, necessarie al funzionamento di altri thread. In queste situazioni, i thread con minore priorit potrebbero trovarsi a dover lavorare in mancanza di risorse. In questi casi pu essere utilizzato il metodo yeld() affinch, il thread con priorit pi alta, diminuisca temporaneamente la propria priorit consentendo ad altri thread di farne uso. Infine, esistono dei thread particolari, con priorit molto bassa detti thread daemons o servizi. Questi thread, provvedono ai servizi di base di unapplicazione attivandosi solo quando la macchina al minimo delle attivit. Unesempio di thread di questo tipo il garbage collector della JVM. La classe java.lanag.Thread dotata del metodo

public final void setDaemon(boolean on)

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

253

che imposta il thread corrente affinch si comporti come un servizio. Il metodo

public final boolean isDaemon()


pu essere utilizzato per determinare lo stato del thread.

11.9 Sincronizzare thread


Finora abbiamo analizzato thread molto semplici, esistono per situazioni in cui due o pi thread possono trovarsi a dover accedere ad un oggetto per modificarlo in regime di concorrenza. Il rischio cui si va incontro in questi casi, quello dellalterazione dello stato dell'oggetto condiviso. Proviamo ad immaginare una classe che rappresenti il conto in banca della famiglia Tizioecaio e che il conto sia intestato ad entrambi i signori Tizioecaio. Supponiamo ora che sul conto siano depositati 600 euro. Se i due intestatari del conto (thread) accedessero contemporaneamente alloggetto per ritirare dei soldi, si rischierebbe una situazione simile alla seguente:

1. 2. 3. 4. 5. 6. 7. 8.

Il signor Tizioecaio accede al conto chiedendo di ritirare 400 euro. Un metodo delloggetto conto controlla il saldo trovando 600 euro. La signora Tizioecaio accede al conto chiedendo di ritirare 400 euro. Un metodo delloggetto conto controlla il saldo trovando 600 euro. Sono addebitati sul conto i 400 euro del signor Tizioecaio. Sono addebitati sul conto i 400 euro del signor Tizioecaio. Il conto va in scoperto di 200 euro ed i due non lo sanno. Quando i signori Tizioecaio scoprono lo scoperto chiudono il conto presso quella banca.

La classe ContoCorrente potrebbe essere cos definita:


package src.esercizi.thread;

/** * Rappresenta un generico conto corrente bancario. * Fornisce i metodi per depositare o ritirare soldi dal conto. */ public class ContoCorrente { public float saldo(){ return saldo; } /** * Ritira la cifra specificata dal conto * @param float ammontare : ammontare della cifra da depositare * @return void */ public void ritira(float ammontare) {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

254

if(saldo()>=ammontare) saldo = saldo-ammontare; }

/** * Deposita la cifra specificata sul conto * @param float ammontare : ammontare della cifra da depositare * @return void */ public void deposita(float ammontare) { saldo = saldo+ammontare; }
private float saldo=600; }

In questi casi necessario che i due thread siano sincronizzati ovvero, mentre uno esegue loperazione, laltro deve rimanere in attesa. Java fornisce il metodo per gestire la sincronizzazione tra thread mediante la parola chiave synchronized. Questo modificatore deve essere aggiunto alla dichiarazione del metodo da sincronizzare ed assicura che, solo un thread alla volta in grado di eseguire il metodo. Di fatto, indipendentemente dal numero di thread che tenteranno di accedere al metodo sincronizzato, solo uno potr eseguirlo. Gli altri, saranno accodati in attesa di riceverne il controllo. Metodi di questo tipo sono detti Thread Safe. La versione thread safe della classe controcorrente di conseguenza:
package src.esercizi.thread;

/** * Rappresenta un generico conto corrente bancario. * Fornisce i metodi per depositare o ritirare soldi dal conto. */ public class ContoCorrente { public float saldo(){ return saldo; } /** * Ritira la cifra specificata dal conto * @param float ammontare : ammontare della cifra da depositare * @return void */ public synchronized void ritira(float ammontare) { if(saldo()>=ammontare) saldo = saldo-ammontare; } /** * Deposita la cifra specificata sul conto * @param float ammontare : ammontare della cifra da depositare * @return void */

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

255

public synchronized void deposita(float ammontare) { saldo = saldo+ammontare; } private float saldo=600; }

11.10 Lock su oggetto


Il meccanismo descritto nel paragrafo precedente non ha come unico effetto, quello di impedire che due thread accedano ad uno stesso metodo contemporaneamente, ma impedisce il verificarsi di situazioni anomale tipiche della programmazione concorrente. Consideriamo lesempio seguente:
class A { public synchronized int a() { return b() } public synchronized int b(int n) { return a(); } }

Supponiamo ora che un thread T1 esegua il metodo b() della classe A e contemporaneamente, un secondo thread T2 effettua una chiamata al metodo a() dello stesso oggetto. Ovviamente, essendo i due metodi sincronizzati, il primo thread avrebbe il controllo sul metodo b() ed il secondo su a(). Lo scenario che si verrebbe a delineare disastroso: T1 rimarrebbe in attesa sulla chiamata al metodo a() sotto il controllo di T2 e viceversa, T2 rimarrebbe bloccato sulla chiamata al metodo b() sotto il controllo di T1. Il risultato finale sarebbe lo stallo di entrambi i thread. Questa situazione, detta deadlock, difficilmente rilevabile e necessita di algoritmi molto complessi e poco efficienti per essere gestita o prevenuta. Java garantisce al programmatore la certezza che casi di questo tipo non avvengono mai. Di fatto, quando un thread entra allinterno di un metodo sincronizzato ottiene il lock sulloggetto (non solo sul metodo). Ottenuto il lock, il thread potr richiamare altri metodi sincronizzati delloggetto senza entrare in deadlock. Ogni altro thread che prover ad utilizzare loggetto in lock, si metter in coda in attesa di essere risvegliato al momento del rilascio della risorsa da parte del thread proprietario. Quando il primo thread termina le sue operazioni, il secondo otterr il lock e di conseguenza luso privato delloggetto. Formalmente, lock di questo tipo sono chiamati lock su oggetto.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

256

11.11 Lock di classe


Differenti dai lock su oggetto sono invece i lock su classe. Vediamo di cosa si tratta. Sappiamo che metodi statici accedono solo a dati membro statici e che un metodo o un attributo statico non richiedono un oggetto attivo per essere eseguiti. Di conseguenza, un thread che effettui una chiamata ad un metodo statico sincronizzato non pu ottenere il lock sullistanza di un oggetto inesistente. Daltra parte, necessaria una forma di prevenzione dal deadlock anche in questo caso. Java prevede una seconda forma di lock: il lock su classe. Quando un thread accede ad un metodo statico sincronizzato, ottiene un lock su classe, vale a dire, su tutti gli oggetti attivi del tipo utilizzato. In questo scenario, nessun thread potr accedere a metodi statici sincronizzati di ogni oggetto di una stessa classe fino a che un thread detiene il lock sulla classe. Questa seconda forma di lock nonostante riguardi tutte le istanze di un oggetto, comunque meno restrittiva della precedente perch metodi sincronizzati non statici della classe in lock possono essere eseguiti durante lesecuzione del metodo statico sincronizzato.

11.12 Produttore e Consumatore


Quando un thread deve spettare che accada una determinata condizione esterna per proseguire nellesecuzione, il modificatore synchronized non pi sufficiente. Sincronizzare un metodo significa garantire che due thread non accedano contemporaneamente ad un oggetto, tuttavia, synchronized non prevede la possibilit di coordinare pi thread mediante scambio di messaggi. Il problema del Produttore del Consumatore rappresenta un caso tipico in cui il modificatore synchronized non sufficiente a rendere consistente la concorrenza. Il Produttore produce un numero intero compreso tra 0 e 9 o lo rende disponibile al Consumatore, il quale potr utilizzare il numero intero una ed una sola volta (figura 90).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

257

Figura 90:

Usecase-duagram per Produttore Consumatore

Nella nostra implementazione, produttore e consumatore sono rappresentati da due thread che condividono i dati mediante la classe Magazzino (Figura 91). Per rendere la simulazione pi reale, faremo in modo che il Produttore si fermi per un periodo compreso tra 0 e 100 millisecondi prima di generare un nuovo elemento. Il codice delle tre definizioni di classe il seguente.

Figura 91:

Class-diagram per Produttore / Consumatore

/**

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

258

* CLASSE M A G A Z Z I N O */ package src.esercizi.thread; public class Magazzino { public synchronized int getProdotto(){ return prodotto; }
public synchronized void setProdotto(int prodotto){ this.prodotto = prodotto; } private int prodotto; }

/** * CLASSE P R O D U T T O R E */ package src.esercizi.thread; public class Produttore implements Runnable { private Magazzino magazzino; public Produttore(Magazzino magazzino) { this.magazzino = magazzino; }
public void run() { for (int i = 0; i < 10; i++) { magazzino.setProdotto(i); System.out.println("Produttore ha inserito: " + i); try { Thread.sleep((int)(Math.random() * 100)); } catch (InterruptedException e) { } } } }

/** * CLASSE C O N S U M A T O R E */ package src.esercizi.thread;


public class Consumatore implements Runnable { private Magazzino magazzino; public Consumatore(Magazzino magazzino) { this.magazzino = magazzino; } public void run() { int prodotto=0; for (int i = 0; i < 10; i++) { prodotto = magazzino.getProdotto(); System.out.println("Consumatore ha estratto: " + prodotto); } }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

259

La classe magazzino, nonostante sia sincronizzata affinch Produttore e Consumatore non le accedano contemporaneamente, non consente di sincronizzare i due thread affinch:

1 . Il consumatore aspetti che il produttore abbia generato un nuovo numero intero; 2. Il produttore aspetti che il consumatore abbia utilizzato lultimo intero generato.
Quanto detto sar ancora pi evidente dopo che avremo eseguito lapplicazione ProduttoreConsumatore il cui codice il seguente:
package src.esercizi.thread; public class ProduttoreConsumatore { public static void main(String[] argv) { Magazzino magazzino = new Magazzino(); Thread produttore = new Thread(new Produttore(magazzino)); Thread consumatore = new Thread(new Consumatore(magazzino)); produttore.start(); consumatore.start(); } } java src.esercizi.thread.ProduttoreConsumatore Produttore ha inserito: 0 Consumatore ha estratto: Consumatore ha estratto: Consumatore ha estratto: Consumatore ha estratto: Produttore ha inserito: 1 Produttore ha inserito: 2 Produttore ha inserito: 3 Produttore ha inserito: 4

0 0 0 0

Condizioni di questo tipo possono essere risolte solo se il Produttore segnala al Consumatore che ha generato un nuovo elemento ed disponibile per essere utilizzato, viceversa, il Consumatore segnala al Produttore che in attesa di un nuovo elemento da utilizzare. La classe Object dispone dei metodi wait, notify e notifyAll che consentono di indicare ad un oggetto che deve aspettare in attesa di una condizione o, di segnalare che una condizione si verificata.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

260

11.13 Utilizzare i metodi wait e notify


Di seguito, una nuova versione della classe Magazzino i cui metodi getProdotto e setProdotto sono stati modificati utilizzando i metodi wait() e notify() ereditati dalla classe Object.

package src.esercizi.thread;

/** CLASSE M A G A Z Z I N O */ public class Magazzino { public synchronized int getProdotto() { while (isDisponibile() == false) { try { // aspetta che il produttore abbia // generato un nuovo elemento wait(); } catch (InterruptedException e) { } } setDisponibile(false); // notifica al produttore di aver // utilizzato l'ultimo elemento generato notifyAll(); return prodotto; }
public synchronized void setProdotto(int prodotto) { while (isDisponibile() == true) { try { // aspetta che il consumatore abbia // utilizzato l'ultimo elemento wait(); } catch (InterruptedException e) { } } this.prodotto = prodotto; setDisponibile(true); // notifica al consumatore di aver // ugenerato un nuovo elemento notifyAll(); } public boolean isDisponibile() { return disponibile; } public void setDisponibile(boolean disponibile) { this.disponibile = disponibile; } private int prodotto; private boolean disponibile; }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

261

Le attivit svolte dai metodi getProdotto() e setProdotto(int) sono schematizzate in figura 92.

Figura 92:

Activity-Diagram di Produttore / Consumatore

Il metodo getProdotto() entra in un ciclo attendendo che il produttore abbia generato un nuovo elemento. Ad ogni ciclo, viene eseguito il metodo wait() che, acquisisce il lock sulloggetto generato dalla classe Consumatore (il metodo getProdotto() sincronizzato) ed attende un segnale dalla classe Produttore. Cos facendo, la classe Produttore pu ottenere il lock sulloggetto ed inserire il nuovo elemento generato. Terminata loperazione, notifica allla

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

262

classe Consumatore che pu riprendere il controllo delloggetto e continuare con lesecuzione del metodo per acquisire lultimo intero generato. Il metodo setProdotto(int) ha un funzionamento analogo e contrario. Eseguendo nuovamente lapplicazione ProduttoreConsumatore, il risultato finale esattamente quello che ci aspettavamo.
Produttore ha inserito: 0 Consumatore ha estratto: Produttore ha inserito: 1 Consumatore ha estratto: Produttore ha inserito: 2 Consumatore ha estratto: Produttore ha inserito: 3 Consumatore ha estratto: Produttore ha inserito: 4 Consumatore ha estratto:

0 1 2 3 4

11.14 Blocchi sincronizzati


Nei paragrafi precedenti abbiamo visto come i metodi sincronizzati acquisiscano un lock su un oggetto o, nel caso di lock su classe, su tutti quelli di un determinato tipo. Se il metodo sincronizzato viene eseguito molte volte e da molti thread, pu rappresentare un collo di bottiglia in grado di deteriorare pesantemente le prestazioni di tutta lapplicazione. In generale, quindi buona regola limitare le funzioni sincronizzate al minimo indispensabile. Riprendiamo nuovamente la classe Pila, nella versione definita nel capitolo dedivato alle eccezioni e supponiamo che esista un numero non definito di thread che accede ai metodi push e pop delloggetto. Possiamo facilmente proteggere loggetto sincronizzando i due metodi in questione.
package src.esercizi.thread; import src.esercizi.eccezioni.StackOutOfBoundException; /** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO * @version 0.5 */ public class PilaDiInteri { /** Contenitore per i dati della Pila */ Integer[] dati;

/** Punta al primo elmento corrente nella struttura dati */ int cima;
/** Costruisce una pila contenente al massimo 10 elementi */ public PilaDiInteri() { dati = new Integer[20]; cima = 0;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

263

/** * Costruisce una Pila, consentendo di impostarne la dimensione massima * @param int dimensione : dimensione massima della pila */ public PilaDiInteri(int dimensione) { dati = new Integer[dimensione]; cima = 0; } /** * Inserisce un elemento sulla cima della pila * @param int dato : dato da inserire sulla cima della struttura dati * @return void * @exception <{StackOutOfBoundException}> */ public synchronized void push(Object dato) throws StackOutOfBoundException { if (cima < dati.length) { dati[cima] = (Integer)dato; cima++; } else { throw new StackOutOfBoundException("La pila ha raggiunto la dimensione massima. Il valore inserito andato perduto +[" + dato + "]", dati.length); } } /** * Ritorna il primo elemento della pila * @return int : primo elemento sulla pila */ public synchronized Object pop() { if (cima > 0) { cima--; return dati[cima]; } return null; }
}

Anche se risolve il problema della concorrenza, la sincronizzazione dei metodi p u s h e pop rende il codice poco efficiente. Analizzando bene la classe, notiamo che per poter salvaguardare i dati sulla pila, sarebbe sufficente proteggere larray che contiene fisicamente i dati. La versione efficiente della classe PilaDiInteri diventa quindi:
package src.esercizi.thread; import src.esercizi.eccezioni.StackOutOfBoundException; /** * Questa classe definisce un tipo pila, una struttura dati di tipo LIFO * @version 0.5 */ public class PilaDiInteri {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

264

/** Contenitore per i dati della Pila */ Integer[] dati; /** Punta al primo elmento corrente nella struttura dati */ int cima;
/** Costruisce una pila contenente al massimo 10 elementi */ public PilaDiInteri() { dati = new Integer[20]; cima = 0; }

/** * Costruisce una Pila, consentendo di impostarne la dimensione massima * @param int dimensione : dimensione massima della pila */ public PilaDiInteri(int dimensione) { dati = new Integer[dimensione]; cima = 0; } /** * Inserisce un elemento sulla cima della pila * @param int dato : dato da inserire sulla cima della struttura dati * @return void * @exception <{StackOutOfBoundException}> */ public void push(Object dato) throws StackOutOfBoundException { if (cima < dati.length) { synchronized(dati){ dati[cima] = (Integer)dato; cima++; } } else { throw new StackOutOfBoundException("La pila ha raggiunto la dimensione massima. Il valore inserito andato perduto +[" + dato + "]", dati.length); } } /** * Ritorna il primo elemento della pila * @return int : primo elemento sulla pila */ public Object pop() { if (cima > 0) { cima--; return dati[cima]; } return null; }
}

Quando un thread accede al metodo push della classe, ottiene il lock solo sullarray che contiene i dati e solo per il periodo limitato allesecuzione del blocco di codice sincronizzato.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

265

In generale, se non si pu fare a meno di sincronizzare oggetti, buona norma cercare di limitare la sincronizzazione a semplici blocchi di codice. In questo caso genereremo solo lock di tipo dinamico, ossia lock che acquisiamo e rilasciamo in continuazione allinterno di un singolo metodo.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

266

12
12.1 Introduzione

JAVA NETWORKING

La programmazione client-server un tema di primaria importanza poich, spesso necessario creare applicazioni che forniscano servizi di rete. Per ogni piattaforma supportata, Java supporta sia il protocollo TCP sia il protocollo UDP. In questo capitolo introdurremo alla programmazione client-server utilizzando Java, non prima per di aver introdotto le nozioni basilari su reti e TCP/IP.

12.2 I protocolli di rete (Internet)


Descrivere i protocolli di rete e pi in generale il funzionamento di una rete probabilmente cosa impossibile se fatto, come in questo caso, in poche pagine. Cercher quindi di limitare i danni, facendo solo una breve introduzione a tutto ci che utilizziamo e non vediamo quando parliamo di strutture di rete, ai protocolli che pi comunemente sono utilizzati, alle modalit di connessione che ognuno di loro utilizza per realizzare la trasmissione dei dati tra client e server o, viceversa. Due applicazioni (client e server) connesse tramite una rete, comunicano tra loro scambiandosi pacchetti di dati costruiti secondo un comune protocollo di comunicazione che, definisce quella serie di regole sintattiche e semantiche utili alla comprensione dei dati contenuti nel pacchetto. I dati trasportati allinterno di questi flussi di informazioni possono essere suddivisi in due categorie principali:

1. I dati necessari alla comunicazione tra le applicazioni ovvero quelli che non sono a carico dellapplicativo che invia il messaggio (esempio: lindirizzo della macchina che invia il messaggio e quello della macchina destinataria); 2. I dati contenenti informazioni strettamente legate alla comunicazione tra le applicazioni ovvero tutti i dati a carico dellapplicazione che trasmette il messaggio;
Risulta chiaro che, parlando di protocolli, possiamo identificare due macro insiemi : Protocolli di rete e Protocolli applicativi. Appartengono ai protocolli di rete i protocolli dipendenti dalla implementazione dello strato fisico e necessari alla trasmissione di dati tra una applicazione ed un'altra. Appartengono invece ai protocolli applicativi tutti i protocolli che contengono dati dipendenti dalla applicazione e utilizzano i protocolli di rete come supporto per la trasmissione. Nelle prossime due tabelle, sono citati i pi comuni protocolli appartenenti ai due insiemi.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

267

Nome TCP UDP IP ICMP

Protocolli di rete Descrizione Trasmission Control Protocol / Internet Protocol User Datagram Protocol Internet Protocol Internet Control Message Protocol Protocolli applicativi Descrizione Hyper Text Trasfer Protocol Protocollo per la gestione remota via terminale Time Protocol Simple message trasfer protocollo File trasfer protocol

Nome HTTP Telnet TP SMTP FTP

Il primo insieme, pu essere a sua volta suddiviso in due sottoinsiemi: Protocolli di trasmissione e Protocolli di instradamento. Per semplicit faremo comunque sempre riferimento a questi come protocolli di rete. Lunione dei due insiemi suddetti viene comunemente chiamata TCP/IP essendo TCP ed IP i due protocolli pi noti ed utilizzati. Nella Figura 93 riportato lo schema architetturale del TCP/IP dal quale risulter pi comprensibile la suddivisione nei due insiemi suddetti.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

268

Figura 93:

Architettura di TCP/IP

In pratica, possiamo ridurre internet ad un gestore di indirizzi il cui compito quello di far comunicare tra di loro due o pi sistemi appartenenti o no alla stessa rete.

12.3 Indirizzi IP
Per inviare pacchetti da unapplicazione allaltra, necessario specificare lindirizzo della macchina mittente e quello della macchina destinataria. Tutti i computer collegati ad internet sono identificati da uno o pi indirizzi numerici detti IP. Tali indirizzi sono rappresentati da numeri di 32 bit e possono essere scritti in formato decimale, esadecimale o in altri formati. Il formato pi comune quello che usa la notazione decimale separata da punti, con il quale lindirizzo numerico a 32 bit suddiviso in quattro sequenze di un byte, ognuna separate da punto, ed ogni byte scritto mediante numero intero senza segno. Ad esempio, consideriamo lindirizzo IP 0xCCD499C1 (in notazione esadecimale). Poich: 0xCC 0xD4 0x99 0xC1 204 212 153 193

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

269

Lindirizzo IP secondo la notazione decimale separata da punti sar 204.212.153.193. Gli indirizzi IP contengono due informazioni utilizzate dal protocollo IP per linstradamento di un pacchetto dal mittente al destinatario. Queste informazioni rappresentano lindirizzo della rete del destinatario e lindirizzo del computer destinatario allinterno di una rete. Gli indirizzi IP sono suddivisi in quattro classi differenti : A, B, C, D.

INDIRIZZO IP = INDIRIZZO RETE | INDIRIZZO HOST


Gli indirizzi di Classe A sono tutti gli indirizzi il cui primo byte compreso tra 0 e 127 (ad esempio, appartiene alla classe A lindirizzo IP 10.10.2.11) ed hanno la seguente struttura: Id. Rete 0-127 Bit: 0 Identificativo di Host 0-255 0-255 7,8 15,16

0-255 23,24

31

Dal momento che gli indirizzi di rete 0 e 127 sono indirizzi riservati, un IP di classe A fornisce 126 possibili indirizzi di rete e 224 = 16.777.219 indirizzi di host per ogni rete. Appartengono alla Classe B gli indirizzi IP in cui il primo numero compreso tra 128 e 191 (esempio : 129.100.1.32) ed utilizzano i primi due byte per identificare lindirizzo di rete. In questo caso, lindirizzo IP assume quindi la seguente struttura : Id. Rete 128-191 0 Identificativo di Host 0-255 0-255 15,16 23,24

Bit:

0-255 7,8

31

Notiamo che lindirizzo IP rappresenta 214 = 16.384 reti ognuna delle quali con 216 = 65.536 possibili host. Gli indirizzi di Classe C hanno il primo numero compreso tra 192 e 223 (esempio: 192.243.233.4) ed hanno la seguente struttura : Id. Rete 192-223 0 0-255 7,8 0-255 15,16 Id. di Host 0-255 23,24 31

Bit:

Questi indirizzi forniscono identificatori per 222 = 4.194.304 reti e 28 = 256 computer che, si riducono a 254 dal momento che gli indirizzi 0 e 255 sono indirizzi riservati. Gli indirizzi di Classe D, sono invece indirizzi riservati per attivit di multicasting.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

270

Bit:

Indirizzo Multicast 1110. 0 7,8

15,16

23,24

31

In una trasmissione Multicast, un indirizzo non fa riferimento ad un singolo host allinterno di una rete bens a pi host in attesa di ricevere i dati trasmessi. Esistono infatti applicazioni in cui necessario inviare uno stesso pacchetto IP a pi host destinatari in simultanea. Ad esempio, applicazioni che forniscono servizi di streaming video privilegiano comunicazioni multicast a unicast, potendo in questo caso inviare pacchetti a gruppi di host contemporaneamente, utilizzando un solo indirizzo IP di destinazione. Quanto descritto in questo paragrafo schematizzato nella prossima figura da cui appare chiaro che possibile effettuare la suddivisione degli indirizzi IP nelle quattro classi suddette applicando al primo byte in formato binario la seguente regola :

1) un indirizzo IP appartiene alla Classe A se il primo bit uguale a 0; 2) un indirizzo IP appartiene alla Classe B se i primi due bit sono uguali a 10; 3) un indirizzo IP appartiene alla Classe C se i primi tre bit sono uguali a 110; 4) un indirizzo IP appartiene alla Classe D se i primi 4 bit sono uguali a 1110.

Figura 94:

Le quattro forme di un indirizzo IP

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

271

12.4 Comunicazione Connection Oriented o Connectionless


I protocolli di trasporto appartenenti al primo insieme descritto nei paragrafi precedenti, si occupano della trasmissione delle informazioni tra server e client (o viceversa). Per far questo utilizzano due modalit di trasmissione dei dati rispettivamente: con connessione (Connection Oriented) o senza (Connectionless). In modalit con connessione, il TCP/IP stabilisce un canale logico tra il computer server ed il computer client, canale che rimane attivo sino alla fine della trasmissione dei dati o sino alla chiusura da parte del server o del client. Questo tipo di comunicazione, utilizzata in tutti i casi in cui la rete debba garantire lavvenuta trasmissione dei dati e la loro integrit. Con una serie di messaggi detti di Acknowledge , server e client verificano lo stato della trasmissione ripetendola se necessario. Un esempio di comunicazione con connessione la posta elettronica in cui, il client di posta stabilisce un canale logico di comunicazione con il server. Per mezzo di questo canale, il client effettua tutte le operazione di scarico od invio di messaggi di posta. Solo alla fine delle operazione il client si occuper di notificare al server la fine della trasmissione e quindi la chiusura della comunicazione. Nel secondo caso (Connectionless) il TCP/IP non si preoccupa dellintegrit dei dati inviati, n dellavvenuta ricezione da parte del client. Per fare un paragone con situazioni reali, un esempio di protocollo connectionless ci fornito dalla trasmissione del normale segnale televisivo via etere. In questo caso, infatti, il trasmettitore (o ripetitore) trasmette il suo messaggio senza preoccuparsi se il destinatario lo abbia ricevuto.

12.5 Domain Name System : risoluzione dei nomi di un host


Ricordarsi a memoria un indirizzo IP non cosa semplicissima, proviamo infatti ad immaginare quanto potrebbe essere complicato navigare in Internet dovendo utilizzare la rappresentazione in notazione decimale separata da punto degli indirizzi che ci interessaano. Oltre ad un indirizzo IP, ad un host associato uno o pi nomi chiamati Host Name che ci consentono di far riferimento ad un computer in rete utilizzando una forma pi semplice e mnemonica della precedente. Ecco perch generalmente utilizziamo nomi piuttosto che indirizzi per collegarci ad un computer sulla rete. Per poter fornire questa forma di indirizzamento, esistono delle applicazioni che traducono i nomi in indirizzi (o viceversa) comunemente chiamate Server DNS o nameserver. Analizziamo in breve come funziona la risoluzione di un nome mediante nameserver, ossia la determinazione dellindirizzo IP a partire dall Host Name.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

272

Figura 95:

Gerarchia dei server DNS

Tipicamente un nome di host ha la seguente forma:

server.java-net.it
Dal punto di vista dellutente il nome va letto da sinistra verso destra ossia dal nome locale (server) a quello globale (it) ed ha diversi significati: server.javanet.it si riferisce ad un singolo host allinterno di una rete ed per questo motivo detto fully qualified; java-net.it si riferisce al dominio degli host collegati alla rete dellorganizzazione java-net. Infine, it si riferisce ai sistemi amministrativi nella rete mondiale Internet ed detto nome di alto livello Un nome quindi definisce una struttura gerarchica. Proprio su questa gerarchia si basa il funzionamento della risoluzione di un nome. Nella Figura 95 illustrata la struttura gerarchica utilizzata dai server DNS per risolvere i nomi. Dal punto di vista di un server DNS il nome non letto da sinistra verso destra, ma al contrario da destra verso sinistra ed il processo di risoluzione pu essere schematizzato nel modo seguente:

1 . Il nostro browser richiede al proprio server DNS lindirizzo IP corrispondente al nome s e r v e r . j a v a - n e t . i t; 2. Il DNS interroga il proprio database dei nomi per verificare se in grado di risolvere da solo il nome richiesto. Nel caso in cui il nome esista allinterno della propria base dati ritorna lindirizzo IP corrispondente, altrimenti deve tentare unaltra strada per ottenere q u a n t o r i c h i e s t o .

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

273

3. Ogni nameserver deve sapere come contattare un almeno un server radice, ossia un server DNS che conosca i nomi di alto livello e sappia quale DNS in grado di risolverli. Il nostro DNS contatter quindi il server radice a lui conosciuto e chieder quale DNS server in grado di risolvere i nomi di tipo it; 4. Il DNS server interrogher quindi il nuovo sistema chiedendogli quale DNS Server a lui conosciuto in grado di risolvere i nomi appartenenti al dominio java-net.it il quale verr a sua volta interrogato per risolvere infine il nome completo server.java-net.it Nella Figura 96 schematizzato il percorso descritto, affinch il nostro DNS ci restituisca la corretta risposta.

Figura 96:

flusso di ricerca allinterno della gerarchia dei server DNS

12.6 URL
Parlando di indirizzamento allinterno di una rete, il nome di un Host non rappresenta ancora il nostro punto di arrivo. Con le nostre conoscenze siamo ora in grado di trovare ed indirizzare un host, ma non siamo ancora in grado di accedere ad una risorsa, di qualunque tipo essa sia. Navigando in Internet sentiamo spesso nominare il termine URL. URL acronimo di Uniform Resource Locator, rappresenta lindirizzo di una risorsa sulla rete e fornisce informazioni relative al protocollo necessario alla gestione della risorsa indirizzata. Un esempio di URL il seguente:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

274

http://www.java-net.it/index.html
in cui:

1. Http indica il protocollo (in questo caso Hyper Text Trasfer Protocol) 2 . //www.java-net.tv/index.html il nome completo della risorsa richiesta.
In definitiva una URL ha la seguente struttura :

URL = Identificatore del Protocollo : Nome della Risorsa


Il Nome della risorsa pu contenere una o pi componenti tra le seguenti :

1) Host Name : Nome del computer host su cui la risorsa risiede; 2) File Name : Il percorso completo della risorsa sul computer indirizzato da Host Name; 3) Port Number : Il numero della porta a cui connettersi (vedremo in seguito il significato di porta); 4) Reference : un puntatore ad una locazione specifica allinterno di una risorsa.

12.7

Trasmission Control Protocol : trasmissione Connection Oriented

Il protocollo TCP, appartenente allo strato di trasporto del TCP/IP (Figura 93) trasmette dati da server a client, e viceversa, suddividendo un messaggio di dimensioni arbitrarie in frammenti o datagrammi da spedire separatamente e non necessariamente in maniera sequenziale, per poi ricomporli nellordine corretto; una eventuale nuova trasmissione pu essere richiesta per il pacchetto non arrivato a destinazione o contenente dati affetti da errore. Tutto questo in maniera del tutto trasparente rispetto alle applicazioni che trasmettono e ricevono il dato. A tal fine, TCP stabilisce un collegamento logico o, connessione, tra il computer mittente ed il computer destinatario, creando una specie di canale attraverso il quale le due applicazioni possono inviarsi dati in forma di pacchetti di lunghezza arbitraria. Per questo motivo, il protocollo TCP fornisce un servizio di trasporto affidabile, garantendo una corretta trasmissione dei dati tra applicazioni. Nella Figura 97 riportato, in maniera schematica, il flusso di attivit che il TCP/IP deve eseguire in caso di trasmissioni di questo tipo. Queste operazioni, soprattutto su reti di grandi dimensioni, risultano essere molto

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

275

complicate ed estremamente gravose in quanto comportano che, per assolvere al suo compito, il TCP debba tener traccia di tutti i possibili percorsi tra mittente e destinatario.

Figura 97:

Time-Line di una trasmissione Connection Oriented

Unaltra facolt del TCP, quella di poter bilanciare la velocit di trasmissione tra mittente e destinatario, cosa molto utile soprattutto nel caso in cui la trasmissione avvenga attraverso reti eterogenee. In generale, quando due sistemi comunicano tra loro, necessario stabilire le dimensioni massime che un datagramma pu raggiungere affinch possa esservi trasferimento di dati. Tali dimensioni sono dipendenti dallinfrastruttura di rete, dal sistema operativo della macchina host e possono quindi variare anche tra computer appartenenti alla stessa rete. Quando viene stabilita una connessione tra due host, il TCP/IP negozia le dimensioni massime per la trasmissione in ogni direzione dei dati. Mediante il meccanismo di accettazione di un datagramma (acknowledge), di volta in volta il prtocollo trasmette un nuovo valore di dimensione detto finestra che, il mittente potr utilizzare nellinvio del datagramma successivo. Questo valore pu variare in maniera crescente o decrescente a seconda dello stato della infrastruttura di rete. Nonostante le sue caratteristiche, dovendo scegliere il protocollo per la trasmissione di dati, il TCP/IP non risulta sempre essere la scelta migliore.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

276

Dovendo fornire tanti servizi, oltre ai dati relativi al messaggio da trasmettere il protocollo deve trasportare una quantit informazioni, a volte non necessarie, che spesso possono diventare causa di sovraccarico sulla rete. Nella prossima figura stata schematizzata la struttura dei primi sedici ottetti (byte) di un datagramma TCP. In questi casi, necessario favorire la velocit a discapito della qualit della trasmissione, adottando trasmissioni di tipo Connectionless.

Figura 98:

I primi 16 ottetti di un segmento TCP

12.8 User Datagram Protocol : trasmissione Connectionless


User Datagram Protocol si limita ad eseguire operazioni di trasmissione di un pacchetto di dati tra mittente e destinatario con il minimo sovraccarico di rete, senza per garanzia di consegna. Di fatto, questo protocollo in assenza di connessione, non invia pacchetti di controllo della trasmissione perdendo di conseguenza la capacit di rilevare perdite o duplicazioni di dati.

Figura 99:

Segmento di un datagramma UDP

Nella Figura 99 viene mostrato il formato del datagramma UDP. Dal confronto con limmagine precedente, appare chiaro quanto siano ridotte le dimensioni di un datagramma UDP rispetto al datagramma TCP. Altro limite di UDP rispetto al precedente, sta nella mancata capacit di calcolare la finestra per la trasmissione dei dati, calcolo che sar completamente a carico del programmatore. Una applicazione che utilizza

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

277

questo protocollo ha generalmente a disposizione un buffer di scrittura di dimensioni prefissate su cui scrivere i dati da inviare. Nel caso di messaggi che superino queste dimensioni, sar necessario spezzare il messaggio gestendo manualmente la trasmissione dei vari segmenti che lo compongono. Sempre a carico del programmatore sar quindi la definizione delle politiche di gestione per la ricostruzione dei vari segmenti del pacchetto originario, e per il recupero di dati eventualmente persi nella trasmissione.

12.9 Identificazione di un processo : Porte e Socket


Sino ad ora abbiamo sottointeso il fatto che, parlando di rete, esistano allinterno di ogni computer host uno o pi processi che devono comunicare tra loro o con applicazioni esistenti su altri computer host tramite il TCP/IP. Viene spontaneo domandarsi come un processo possa usufruire dei servizi messi a disposizione dal TCP/IP. Il primo passo che un processo deve compiere per trasmettere o ricevere dati, quello di identificarsi al TCP/IP, affinch possa essere riconosciuto dallo strato di gestione della rete. Il riconoscimento di un processo nei confronti della rete, viene effettuato tramite un numero detto porta o port address. Questo numero non per in grado di descrivere in maniera univoca una connessione (n processi su n host differenti potrebbero avere la medesima porta), di conseguenza necessario introdurre un nuova definizione.

Una Association, una quintupla formata dalle informazioni necessarie a descrivere univocamente una connessione : Association = (Protocollo,Indirizzo Locale,Processo Locale,Indirizzo Remoto,Processo Remoto)
Ad esempio, una Association la quintupla: (TCP, 195.233.121.14, 1500, 194.243.233.4, 21) in cui TCP il protocollo da utilizzare, 195.233.121.14 lindirizzo locale ovvero lindirizzo IP del computer mittente, 1500 lidentificativo o porta del processo locale, 194.243.233.4 lindirizzo IP del computer destinatario, ed infine 21 lidentificativo o porta del processo remoto. Come fanno due applicazioni che debbano comunicare tra di loro a creare una Association? Proviamo a pensare ad una Association come ad un insieme contenente solamente i cinque elementi della quintupla. Come si vede dalla Figura 9, questo insieme pi essere costruito mediante unione a partire da due sottoinsiemi, che chiameremo rispettivamente Local Half Association e Remote Half Association:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

278

Figura 100:

Association = Local Half Association U Remote Half Association

Due processi (mittente o locale e destinatario o remoto) definiscono una Association , stabilendo in modo univoco una trasmissione mediante la creazione di due Half Association. Le Half Association sono chiamate Socket. Un socket rappresenta quindi il meccanismo attraverso il quale una applicazione invia e riceve dati tramite rete. Consideriamo ad esempio la precedente Half Association (TCP, 195.233.121.14, 1500, 194.243.233.4, 21). La quintupla rappresenta in modo univoco una trasmissione TCP tra due applicazioni che comunicano tramite i due Socket :

1 : (TCP, 195.233.121.14, 1500) 2 : (TCP, 194.243.233.4, 21)

12.10 Il package java.net


E il package Java che contiene le definizioni di classe necessarie per lo sviluppo di applicazioni client/server basate su socket. Nei prossimi paragrafi analizzeremo le principali classi di questo package e come utilizzare gli oggetti per realizzare trasmissione di dati via rete tra applicazioni Java. La prossima tabella, contiene la gerarchia delle classi e delle interfacce appartenenti a questo package. Scorrendo velocemente i loro nomi, notiamo subito le analogie con la nomenclatura utilizzata nei paragrafi precedenti. Analizziamole brevemente.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

279

Gerarchia delle classi

class java.lang.Object o class java.net.Authenticator o class java.lang.ClassLoader o class java.security.SecureClassLoader o class java.net.URLClassLoader o class java.net.ContentHandler o class java.net.DatagramPacket o class java.net.DatagramSocket o class java.net.MulticastSocket o class java.net.DatagramSocketImpl (implements java.net.SocketOptions) o class java.net.InetAddress (implements java.io.Serializable) o class java.net.PasswordAuthentication o class java.security.Permission (implements java.security.Guard, java.io.Serializable) o class java.security.BasicPermission (implements java.io.Serializable) o class java.net.NetPermission o class java.net.SocketPermission (implements java.io.Serializable) o class java.net.ServerSocket o class java.net.Socket o class java.net.SocketImpl (implements java.net.SocketOptions) o class java.lang.Throwable (implements java.io.Serializable) o class java.lang.Exception o class java.io.IOException o class java.net.MalformedURLException o class java.net.ProtocolException o class java.net.SocketException o class java.net.BindException o class java.net.ConnectException o class java.net.NoRouteToHostException o class java.net.UnknownHostException o class java.net.UnknownServiceException o class java.net.URL (implements java.io.Serializable) o class java.net.URLConnection o class java.net.HttpURLConnection o class java.net.JarURLConnection o class java.net.URLDecoder o class java.net.URLEncoder o class java.net.URLStreamHandler Gerarchia delle Interfacce

o o o o o o

interface java.net.ContentHandlerFactory interface java.net.DatagramSocketImplFactory interface java.net.FileNameMap interface java.net.SocketImplFactory interface java.net.SocketOptions interface java.net.URLStreamHandlerFactory

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

280

Un indirizzo IP rappresentato dalla classe InetAddress, che fornisce tutti i metodi necessari a manipolare un indirizzo per instradare dati sulla rete, e consente la trasformazione di un indirizzo nelle sue varie forme: 1. getAllByName(String host) ritorna tutti gli indirizzi internet di un host dato il suo nome in forma estesa (esempio: getAllByName(www.javanet.it)); 2. getByName(String host) ritorna lindirizzo internet di un host dato il suo nome in forma estesa; 3 . getHostAddress() ritorna una stringa rappresentante lindirizzo IP dellhost nella forma decimale separata da punto "%d.%d.%d.%d"; 4. getHostName() ritorna una stringa rappresentante il nome dellhost. Le classi Socket e ServerSocket implementano rispettivamente un socket client ed uno server per la comunicazione orientata alla connessione, la classe DatagramSocket implementa i socket per la trasmissione senza connessione ed infine la classe MulticastSocket fornisce supporto per i socket di tipo multicast. Rimangono da menzionare le classi U R L , URLConnection, HttpURLConnection e URLEencoder che implementano i meccanismi di connessione tra un browser ed un Web Server.

12.11 Un esempio completo di applicazione client/server


Nei prossimi paragrafi analizzeremo in dettaglio unapplicazione client/server basata su modello di trasmissione con connessione. Prima di entrare nei dettagli dellapplicazione, necessario per porci una domanda: cosa accade tra due applicazioni che comunicano con trasmissione orientata a connessione? Il server, eseguito da un computer host, in ascolto tramite socket su una determinata porta, in attesa di richiesta di connessione da parte di un client (Figura 101). Il client invia la sua richiesta al server tentando la connessione tramite la porta su cui il server in ascolto.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

281

Figura 101:

Richiesta di connessione Client/Server

Dobbiamo a questo punto distinguere due casi:

1. il server comunica con un solo client alla volta; 2. il server deve poter comunicare con pi client contemporaneamente.
Nel primo caso, il server accetta una richiesta di connessione solo se nessunaltro client gi connesso (trasmissione unicast); nel secondo caso, il server in grado di comunicare con pi client contemporaneamente (trasmissione multicast). Nel caso di trasmissione unicast, se la richiesta di connessione tra client e server va a buon fine, il server accetta la connessione stabilendo il canale di comunicazione con il client che, a sua volta, ricevuta, la conferma crea fisicamente un socket su una porta locale tramite il quale trasmettere e ricevere pacchetti (Figura 102).

Figura 102:

Connessione Unicast

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

282

Figura 103:

Connessione Multicast

Nel caso di trasmissione multicast, se la richiesta di connessione tra client e server va a buon fine, il server prima di creare il canale di connessione crea un nuovo socket associato ad una nuova porta (tipicamente avvia un nuovo thread per ogni connessione), a cui cede in gestione il canale di trasmissione con il client. Il socket principale torna quindi ad essere libero di rimanere in ascolto per una nuova richiesta di connessione (Figura 103). Un buon esempio di applicazione client/server che implementano trasmissioni multicast sono browser internet ed il Web Server. A fronte di un numero indefinito di client connessi (browser), il server deve essere in grado di fornire servizi a tutti gli utenti continuando ad ascoltare sulla porta 80 (porta standard per il servizio) in attesa di nuove connessioni.

12.12 La classe ServerSocket


La classe ServerSocket rappresenta quella porzione del server che pu accettare richieste di connessioni da parte del client. Questoggetto deve essere creato passando come attributo al metodo costruttore, il numero della porta su cui il server sar in ascolto. Il codice di seguito rappresenta un prototipo semplificato della classe ServerSocket.
public final class ServerSocket { public ServerSocket(int port) throws IOException ..

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

283

public public public public }

ServerSocket(int port, int count) throws IOException .. Socket accept() throws IOException void close()throws IOException String toString()

Lunico metodo realmente necessario alla classe il metodo accept(), che blocca il thread in attesa di una richiesta di connessioni da parte di un client sulla porta specificata nel costruttore. Quando una richiesta di connessione va a buon fine, il metodo crea il canale di collegamento e restituisce un oggetto Socket gi connesso con il client. Nel caso di comunicazione multicast, sar necessario creare un nuovo thread cui passare loggetto restituito, contenente il codice di gestione della trasmissione e gestione dei dati.

12.13 La classe Socket


La classe Socket rappresenta una connessione client/server via TCP su entrambi i lati. La differenza tra server e client sta nella modalit di creazione di un oggetto di questo tipo. A differenza del server, in cui un oggetto Socket viene creato dal metodo accept() della classe ServerSocket, il client dovr provvedere a crearlo manualmente.
public final class Socket { public Socket (String host, int port) public int getPort() public int getLocalPort() public InputStream getInputStream() throws IOException public OutputStream getOutputStream() throws IOException public synchronized void close () throws IOException public String toString(); }

Quando si crea una istanza manuale della classe Socket, il costruttore messo a disposizione dalla classe accetta due argomenti: il primo, rappresenta il nome dell host a cui ci si vuole connettere; il secondo, la porta su cui il server in ascolto. Il metodo costruttore responsabile del completamento della connessione con il server. Nel caso in cui il tentativo non vada a buon fine, il metodo costruttore generer una eccezione per segnalare lerrore. Notiamo che questa classe ha un metodo getInputStream() e un metodo getOutputStream() . Questo perch un Socket accetta la modalit di trasmissione in entrambi i sensi (entrata ed uscita). I metodi getPort() e getLocalPort() possono essere utilizzati per ottenere informazioni relative alla connessione, mentre il metodo close() chiude la connessione rilasciando la porta libera di essere utilizzata per altre connessioni.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

284

12.14 Un semplice thread di servizio


Abbiamo detto nei paragrafi precedenti che, quando implementiamo un server di rete che gestisce pi client simultaneamente, importante creare dei thread separati per comunicare con ognuno. Questi thread sono detti thread di servizio. Nel nostro esempio, utilizzeremo una classe chiamata Xfer per definire i servizi erogati dalla nostra applicazione client/server .
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. import java.net.* ; import java.lang.* ; import java.io.* ; class Xfer implements Runnable { private Socket connection; private PrintStream o; private Thread me; public Xfer(socket s) { connection = s; me = new Thread(this); me.start(); } public void run() { try { //converte loutput del socket in un printstream o = new PrintStream(connection.getOutPutStream()); } catch(Exception e) {} while (true) { o.println(Questo un messaggio dal server); try { ma.sleep(1000); } catch(Exception e) {} } }

34. }

Xfer pu essere creata solo passandogli un oggetto Socket che deve essere gi connesso ad un client. Una volta istanziato, Xfer crea ed avvia un thread associato a se stesso (linee 11-16). Il metodo run() di Xfer converte lOutputStream del Socket in un PrintStream che utilizza per inviare un semplice messaggio al client ogni secondo.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

285

12.15 TCP Server


La classe NetServ rappresenta il thread primario del server. E questo il thread che accetter connessioni e creer tutti gli alti thread del server.
import java.io.* ; import java.net.* ; import java.lang.* ; public class NetServ implements Runnable { private ServerSocket server; public NetServ () throws Exception { server = new ServerSocket(2000); } public void run() { Socket s = null; Xfer x; while (true) { try {

//aspetta la richiesta da parte del client s = server.accept(); } catch (IOException e) { System.out.println(e.toString()); System.exit(1); } //crea un nuovo thread per servire la richiesta x = new xfer(s);

} } }

public class ServerProgram { public static void main(String args[]) { NetServ n = new NetServ(); (new Thread(n)).start(); } }

Il costruttore alla linea 8 crea una istanza di un oggetto ServerSocket associandolo alla porta 2000. Ogni eccezione viene propagata al metodo chiamante. Loggetto NetServ implementa Runnable, ma non crea il proprio thread. Sar responsabilit dellutente di questa classe creare e lanciare il thread associato a questo oggetto. Il metodo run() estremamente semplice. Alla linea 21 viene accettata la connessione da parte del client. La chiamata server.accept() ritorna un

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

286

oggetto socket connesso con il client. A questo punto, NetServ crea una istanza di Xfer utilizzando il Socket (linea 27) generando un nuovo thread che gestisca la comunicazione con il client. La seconda classe semplicemente rappresenta il programma con il suo metodo main(). Nelle righe 36 e 37 viene creato un oggetto NetServ, viene generato il thread associato e quindi avviato.

12.16 Il client
Loggetto NetClient effettua una connessione ad un host specifico su una porta specifica,e legge i dati in arrivo dal server linea per linea. Per convertire i dati di input viene utilizzato un oggetto DataInputStream, ed il client utilizza un thread per leggere i dati in arrivo.
import java.net.* ; import java.lang.* ; import java.io.* ; public class NetClient implements Runnable { private Socket s; private DataInputStream input; public NetClient(String host, int port) throws Exception { s = new Socket(host, port); } public String read() throws Exception { if (input == null) input = new DataInputStream(s.getInputStream()); return input.readLine(); } public void run() { while (true) { try { System.out.println(nc.read()); } catch (Exception e) { System.out.println(e.toString()); } } } }

class ClientProgram { public static void main(String args[]) { if(args.length<1) System.exit(1); NetClient nc = new NetClient(args[0], 2000); (new Thread(nc)).start();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

287

} }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

288

Parte Seconda Java 2 Enterprise Edition

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

289

13
13.1 Introduzione

JAVA ENTERPRISE COMPUTING

Il termine Enterprise Computing (che per semplicit in futuro indicheremo con EC) sinonimo di Distributed Computing ovvero calcolo eseguito da un gruppo di programmi interagenti attraverso una rete. La figura 104 mostra schematicamente un ipotetico scenario di Architettura Enterprise evidenziando alcune possibili integrazioni tra componenti server-side. Appare chiara la disomogeneit tra le componenti e di conseguenza la complessit strutturale di questi sistemi.

Figura 104:

Architetture enterprise

Dalla figura appaiono chiari alcuni aspetti. Primo, l EC generalmente legato ad architetture di rete eterogenee in cui la potenza di calcolo distribuita tra Main Frame, Super Computer e semplici PC. Lunico denominatore comune tra le componenti il protocollo di rete utilizzato, in genere il TCP/IP. Secondo, applicazioni server di vario tipo girano allinterno delle varie tipologie di hardware. Ed infine lEC comporta spesso luso di molti protocolli di rete e altrettanti standard differenti, alcuni dei quali vengono implementati dallindustria del software con componenti specifiche della piattaforma. E quindi evidente la complessit di tali sistemi e di conseguenza le problematiche che un analista od un programmatore sono costretti ad

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

290

affrontare. Java tende a semplificare tali aspetti fornendo un ambiente di sviluppo completo di API e metodologie di approccio al problem-solving. La soluzione composta da Sun di compone di quattro elementi principali: specifiche, Reference Implementation, test di compatibilit e Application Programming Model. Le specifiche elencano gli elementi necessari alla piattaforma e le procedure da seguire per una corretta implementazione con J2EE. La reference implementation contiene prototipi che rappresentano istanze semanticamente corrette di J2EE al fine di fornire allindustria del software modelli completi per test. Include tool per il deployment e lamministrazione di sistema, EJBs, JSPs, un container per il supporto a runtime e con supporto verso le transazioni, Java Messaging Service e altri prodotti di terzi. L Application Programming Model un modello per la progettazione e programmazione di applicazioni basato sulla metodologia best-practice per favorire un approccio ottimale alla piattaforma. Guida il programmatore analizzando quanto va fatto e quanto no con J2EE, e fornisce le basi della metodologia legata allo sviluppo di sistemi multi-tier con J2EE.

Figura 105:

Modello distribuito basato su java

Nella figura 105 viene illustrato un modello di EC alternativo al precedente in cui viene illustrato con maggior dettaglio il modello precedente con una particolare attenzione ad alcune delle tecnologie di Sun e le loro modalit di interconnessione.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

291

13.2 Architettura di J2EE


Larchitettura proposta dalla piattaforma J2EE divide le applicazioni enterprise in tre strati applicativi fondamentali (figura 106): componenti, contenitori e connettori. Il modello di programmazione prevede lo sviluppo di soluzioni utilizzando componenti a supporto delle quali fornisce quattro tecnologie fondamentali:

Enterprise Java Beans; Servlet; Java Server Pages; Applet.


La prima delle tre, che per semplicit denoteremo con EJB, fornisce supporto per la creazione di componenti server-side che possono essere generate indipendentemente da uno specifico database, da uno specifico transaction server o dalla piattaforma su cui verranno eseguiti. La seconda, servlet, consente la costruzione di servizi Web altamente performanti ed in grado di funzionare sulla maggior parte dei Web server ad oggi sul mercato. La terza, JavaServer Pages o JSP, permette di costruire pagine Web dai contenuti dinamici utilizzando tutta la potenza del linguaggio Java. Le applet, anche se sono componenti client-side rappresentano comunque tecnologie appartenenti allo strato delle componenti.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

292

Figura 106:

Architettura J2EE

In realt esiste una quinta alternativa alle quattro riportate per la quale per non si pu parlare di tecnologia dedicate, ed rappresentata dalle componenti server-side sviluppate utilizzando Java. Il secondo strato rappresentato dai contenitori ovvero supporti tecnologici alle tecnologie appartenenti al primo strato logico della architettura. La possibilit di costruire contenitori rappresenta la caratteristica fondamentale del sistema in quanto fornisce ambienti scalari con alte performance. Infine i connettori consentono alle soluzioni basate sulla tecnologia J2EE di preservare e proteggere investimenti in tecnologie gi esistenti fornendo uno strato di connessione verso applicazioni-server o middleware di varia natura: dai database relazionali con JDBC fino ai server LDAP con JNDI. Gli application-server compatibili con questa tecnologia riuniscono tutti e tre gli strati in un una unica piattaforma standard e quindi indipendente dal codice proprietario, consentendo lo sviluppo di componenti server-centric in grado di girare in qualunque container compatibile con J2EE, indipendentemente dal fornitore di software, e di interagire con una vasta gamma di servizi pre-esistenti tramite i connettori. Ad appoggio di questa soluzione, la Sun mette a disposizione dello sviluppatore un numero elevato di tecnologie specializzate nella soluzione di singoli problemi. Gli EJBs forniscono un modello a componenti per il server-

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

293

side computing, Servlet offrono un efficiente meccanismo per sviluppare estensioni ai Web Server in grado di girare in qualunque sistema purch implementi il relativo container. Infine Java Server Pages consente di scrivere pagine web dai contenuti dinamici sfruttando a pieno le caratteristiche di java. Un ultima considerazione, non di secondaria importanza, va fatta sul modello di approccio al problem-solving: la suddivisione netta che la soluzione introduce tra logiche di business, logiche di client, e logiche di presentazione consente un approccio per strati al problema garantendo ordine nella progettazione e nello sviluppo di una soluzione.

13.3 J2EE Application Model


Date le caratteristiche della piattaforma, laspetto pi interessante legato a quello che le applicazioni non devono fare; la complessit intrinseca delle applicazioni enterprise quali gestione delle transazioni, ciclo di vita delle componenti, pooling delle risorse viene inglobata allinterno della piattaformache provvede autonomamente alle componenti ed al loro supporto. Programmatori e analisti sono quindi liberi di concentrarsi su aspetti specifici della applicazione, come presentazione o logiche di business, non dovendosi occupare di aspetti la cui complessit non irrilevante e in ambito progettuale ha un impatto notevolissimo su costi e tempi di sviluppo. A supporto di questo, lApplication Model descrive la stratificazione orizzontale tipica di una applicazione J2EE. Tale stratificazione identifica quattro settori principali (figura 107) : Client Tier, Web Tier, Business-Tier, EIS-Tier.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

294

Figura 107:

J2EE Application Programmino Model

13.4 Client Tier


Appartengono allo strato client le applicazioni che forniscono allutente una interfaccia semplificata verso il mondo enterprise e rappresentano quindi la percezione che lutente ha della applicazione J2EE. Tali applicazioni si suddividono in due classi di appartenenza: le applicazioni web-based e le applicazioni non-web-based. Le prime sono quelle applicazioni che utilizzano il browser come strato di supporto alla interfaccia verso lutente ed i cui contenuti vengono generati dinamicamente da Servlet o Java Server Pages o staticamente in HTML. Le seconde (non-web-based) sono invece tutte quelle basate su applicazioni stand-alone che sfruttano lo strato di rete disponibile sul client per interfacciarsi direttamente con la applicazione J2EE senza passare per il WebTier. Nella figura 108 vengono illustrate schematicamente le applicazioni appartenenti a questo strato e le modalit di interconnessione verso gli strati componenti la architettura del sistema.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

295

Figura 108:

Client Tier

13.5 Web Tier


Le componenti web-tier di J2EE sono rappresentate da pagine JSP, serverside applet e Servlet. Le pagine HTML che invocano Servlet o JSP, secondo lo standard J2EE, non sono considerate web-components, ed il motivo il seguente: come illustrato nei paragrafi precedenti si considerano componenti, oggetti caricabili e gestibili allinterno di un contenitore in grado di sfruttarne i servizi messi a disposizione. Nel nostro caso, Servlet e JSP hanno il loro ambiente runtime allinterno del Servlet-Container che provvede al loro ciclo di vita, nonch alla fornitura di servizi quali client-request e client-response ovvero come un client appartenente allo strato client-tier rappresenta la percezione che lutente ha della applicazione J2EE, il contenitore rappresenta la percezione che una componente al suo interno ha della interazione verso lesterno (Figura 109). La piattaforma J2EE prevede quattro tipi di applicazioni web : basic HTML, HTML with base JSP and Servlet, Servlet and JSP with JavaBeans components, High Structured Application con componenti modulari, Servlet ed Enterprise Beans. Di queste, le prime tre sono dette applicazioni webcentric, quelle appartenenti al quarto tipo sono dette applicazioni EJB Centric. La scelta di un tipo di applicazione piuttosto che di un altro dipende ovviamente da vari fattori tra cui:

Complessit del problema; Risorse del Team di sviluppo;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

296

Longevit della applicazione; Dinamismo dei contenuti da gestire e proporre.

Figura 109:

Web Tier

Figura 110:

Application Design

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

297

Nella figura 110 viene presentata in modo schematico tutta la gamma di applicazioni web ed il loro uso in relazione a due fattori principali: complessit e robustezza.

13.6 Business Tier


Nellambito di una applicazione enterprise, questo lo strato che fornisce servizi specifici: gestione delle transazioni, controllo della concorrenza, gestione della sicurezza, ed implementa inoltre logiche specifiche circoscritte allambito applicativo ed alla manipolazione dei dati. Mediante un approccio di tipo Object Oriented possibile decomporre tali logiche o logiche di business in un insieme di componenti ed elementi chiamati business objects. La tecnologia fornita da Sun per implementare i business objects quella che in precedenza abbiamo indicato come EJBs (Enterprise Java Beans). Tali componenti si occupano di:

Ricevere dati da un client, processare tali dati (se necessario), inviare i dati allo strato EIS per la loro memorizzazione su base dati; (Viceversa) Acquisire dati da un database appartenente allo strato EIS, processare tali dati (se necessario), inviare tali dati al programma client che ne abbia fatto richiesta.
Esistono due tipi principali di EJBs : entity beans e session beans. Per session bean si intende un oggetto di business che rappresenta una risorsa privata rispetto al client che lo ha creato. Un entity bean al contrario rappresenta in modo univoco un dato esistente allinterno dello strato EIS ed ha quindi una sua precisa identit rappresentata da una chiave primaria (figura 111).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

298

Figura 111:

Business Tier

Oltre alle componenti, la architettura EJB definisce altre tre entit fondamentali: servers, containers e client. Un Enterprise Bean vive allinterno del container che provvede al ciclo vitale della componente e ad una variet di altri servizi quali gestione della concorrenza e scalabilit. LEJB Container a sua volta parte di un EJB server che fornisce tutti i servizi di naming e di directory (tali problematiche verranno affrontate nei capitoli successivi). Quando un client invoca una operazione su un EJB, la chiamata viene intercettata dal container. Questa intercessione del container tra client ed EJB a livello di chiamata a metodo consente al container la gestione di quei servizi di cui si parlato allinizio del paragrafo oppure della propagazione della chiamata ad altre componenti (Load Balancing) o ad altri container (Scalabilit) su altri server sparsi per la rete su differenti macchine. Nella implementazione di una architettura enterprise, oltre a decidere che tipo di enterprise beans utilizzare, un programmatore deve effettuare altre scelte strategiche nella definizione del modello a componenti:

Che tipo di oggetto debba rappresentare un Enterprise Bean; Che ruolo tale oggetto deve avere allinterno di un gruppo di componenti che collaborino tra di loro.
Dal momento che gli enterprise beans sono oggetti che necessitano di abbondanti risorse di sistema e di banda di rete, non sempre soluzione

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

299

ottima modellare tutti i business object come EJBs. In generale una soluzione consigliabile quella di adottare tale modello solo per quelle componenti che necessitino un accesso diretto da parte di un client.

13.7 EIS-Tier
Le applicazioni enterprise implicano per definizione laccesso ad altre applicazioni, dati o servizi sparsi allinterno delle infrastrutture informatiche del fornitore di servizi. Le informazioni gestite ed i dati contenuti allinterno di tali infrastrutture rappresentano la ricchezza del fornitore, e come tale vanno trattati con estrema cura garantendone la integrit. Al modello bidimensionale delle vecchie forme di business legato ai sistemi informativi (figura 112), stato oggi sostituito da un nuovo modello (ebusiness) che introduce una terza dimensione che rappresenta la necessit, da parte del fornitore, di garantire accesso ai dati contenuti nella infrastruttura enterprise via Web a partner commerciali, consumatori, impiegati e altri sistemi informativi. Gli scenari di riferimento sono quindi svariati e comprendono vari modelli di configurazione che le architetture enterprise vanno ad assumere per soddisfare le necessit della new-economy. Un modello classico quello rappresentato da sistemi di commercio elettronico (figura 113) . Nei negozi virtuali o E-Store lutente web interagisce tramite browser con i cataloghi on-line del fornitore, seleziona i prodotti, inserisce i prodotti selezionati nel carrello virtuale, avvia una transazione di pagamento con protocolli sicuri.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

300

Figura 112:

Modelli di business

Figura 113:

Negozi Virtuali o E-Store

13.8 Le API di J2EE


Le API di J2EE forniscono supporto al programmatore per lavorare con le pi comuni tecnologie utilizzate nellambito della programmazione distribuita e dei servizi di interconnessione via rete. I prossimi paragrafi forniranno una breve introduzione alle API componenti lo strato tecnologico fornito con la piattaforma definendo volta per volta la filosofia alla base di ognuna di esse e tralasciando le API relative a Servlet, EJBs e JavaServer Pages gi introdotte nei paragrafi precedenti.

13.9 JDBC : Java DataBase Connectivity


Rappresentano le API di J2EE per poter lavorare con database relazionali. Esse consentono al programmatore di inviare query ad un database relazionale, di effettuare delete o update dei dati allinterno di tabelle, di lanciare stored-procedure o di ottenere meta-informazioni relativamente al database o le entit che lo compongono. Architetturalmente JDBC sono suddivisi in due strati principali: il primo che fornisce una interfaccia verso il programmatore, il secondo di livello pi basso che fornisce invece una serie di API per i produttori di drivers verso database relazionali e nasconde allutente i dettagli del driver in uso. Questa caratteristica rende la tecnologia indipendente rispetto al motore relazionale che il programmatore deve interfacciare.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

301

Figura 115:

Driver JDBC Tipo 3 e 4

I driver JDBC possono essere suddivisi in quattro tipi fondamentali come da tabella: Driver JDBC Descrizione Driver diretto Java nativo Driver Java per connessione diretta a middleware. JDBC-ODBC Bridge e ODBC driver; Driver nativo parzialmente sviluppato con Java.

Nome Tipo 4 Tipo 3 Tipo 2 Tipo 1

I driver di tipo 4 e 3 appartengono a quella gamma di drivers che convertono le chiamate JDBC nel protocollo di rete utilizzato direttamente da un server

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

302

relazionale o un middleware che fornisca connettivit attraverso la rete verso uno o pi database (figura 115) e sono scritti completamente in Java. I driver di tipo 2 sono driver scritti parzialmente in Java e funzionano da interfaccia verso API native di specifici prodotti. I driver di tipo 1, anchessi scritti parzialmente in java hanno funzioni di bridge verso il protocollo ODBC rilasciato da Microsoft. Questi driver sono anche detti JDBC/ODBC Bridge (Figura 116) e rappresentano una buona alternativa in situazioni in cui non siano disponibili driver di tipo 3 o 4.

Figura 116:

Driver JDBC Tipo 1 e 2

13.10 RMI : Remote Method Invocation


Remote Method Invocation fornisce il supporto per sviluppare applicazioni java in grado di invocare metodi di oggetti distribuiti su virtual-machine differenti sparse per la rete. Grazie a questa tecnologia possibile realizzare architetture distribuite in cui un client invoca metodi di oggetti residenti su un server che a sua volta pu essere client nei confronti di un altro server. Oltre a garantire tutte i vantaggi tipici di una architettura distribuita, essendo fortemente incentrato su Java, RMI consente di trasportare in ambiente distribuito tutte le caratteristiche di portabilit e semplicit legata allo sviluppo

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

303

di componenti Object Oriented apportando nuovi e significativi vantaggi rispetto alle ormai datate tecnologie distribuite (vd. CORBA). Primo, RMI in grado di passare oggetti e ritornare valori durante una chiamata a metodo oltre che a tipi di dati predefiniti. Questo significa che dati strutturati e complessi come le Hashtable possono essere passati come un singolo argomento senza dover decomporre loggetto in tipi di dati primitivi. In poche parole RMI permette di trasportare oggetti attraverso le infrastrutture di una architettura enterprise senza necessitare di codice aggiuntivo. Secondo, RMI consente di delegare limplementazione di una classe dal client al server o viceversa. Questo fornisce una enorme flessibilit al programmatore che scriver solo una volta il codice per implementare un oggetto che sar immediatamente visibile sia a client che al server. Terzo, RMI estende le architetture distribuite consentendo luso di thread da parte di un server RMI per garantire la gestione ottimale della concorrenza tra oggetti distribuiti. Infine RMI abbraccia completamente la filosofia Write Once Run Anywhere di Java. Ogni sistema RMI portabile al 100% su ogni Java Virtual Machine.

13.11 Java IDL


RMI fornisce una soluzione ottima come supporto ad oggetti distribuiti con la limitazione che tali oggetti debbano essere scritti con Java. Tale soluzione non si adatta invece alle architetture in cui gli oggetti distribuiti siano scritti con linguaggi arbitrari. Per far fronte a tali situazioni, la Sun offre anche la soluzione basata su CORBA per la chiamata ad oggetti remoti. CORBA (Common Object Request Broker Architecture) uno standard largamente utilizzato introdotto dallOMG (Object Managment Group) e prevede la definizione delle interfacce verso oggetti remoti mediante un IDL (Interface Definition Language) indipendente dalla piattaforma e dal linguaggio di riferimento con cui loggetto stato implementato. Limplementazione di questa tecnologia rilasciata da Sun comprende un ORB (Object Request Broker) in grado di interagire con altri ORB presenti sul mercato, nonch di un pre-compilatore IDL che traduce una descrizione IDL di una interfaccia remota in una classe Java che ne rappresenti il dato.

13.12 JNDI
Java Naming and Directory Interface quellinsieme di API che forniscono laccesso a servizi generici di Naming o Directory attraverso la rete. Consentono, alla applicazione che ne abbia bisogno, di ottenere oggetti o dati tramite il loro nome o di ricercare oggetti o dati mediante luso di attributi a loro associati.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

304

Adesempio tramite JNDI possibile accedere ad informazioni relative ad utenti di rete, server o workstation, sottoreti o servizi generici (figura 117). Come per JDBC le API JNDI non nascono per fornire accesso in modo specifico ad un particolare servizio, ma costituiscono un set generico di strumenti in grado di interfacciarsi a servizi mediante driver rilasciati dal produttore del servizio e che mappano le API JNDI nel protocollo proprietario di ogni specifico servizio. Tali driver vengono detti Service Providers e forniscono accesso a protocolli come LDAP, NIS, Novell NDS oltre che ad una gran quantit di servizi come DNS, RMI o CORBA Registry.

Figura 117:

Architettura JNDI

13.13 JMS
Java Message Service o Enterprise Messaging rappresentano le API forniscono supporto alle applicazioni enterprise nella gestione asincrona della comunicazione verso servizi di messaging o nella creazione di nuovi MOM (Message Oriented Middleware). Nonostante JMS non sia cos largamente diffuso come le altre tecnologie, svolge un ruolo importantissimo nellambito di sistemi enterprise. Per meglio comprenderne il motivo necessario comprendere il significato della parola messaggio che in ambito JMS rappresenta tutto linsieme di messaggi asincroni utilizzati dalle applicazioni enterprise, non dagli utenti

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

305

umani, contenenti dati relativi a richieste, report o eventi che si verificano allinterno del sistema fornendo informazioni vitali per il coordinamento delle attivit tra i processi. Essi contengono informazioni impacchettate secondo specifici formati relativi a particolari eventi di business. Grazie a JMS possibile scrivere applicazioni di business message-based altamente portabili fornendo una alternativa a RMI che risulta necessaria in determinati ambiti applicativi. Una applicazione di Home Banking ad esempio, deve interfacciarsi con il sistema di messaggistica bancaria ed interbancaria da cui trarre tutte le informazioni relative alla gestione economica dellutente. Nel modello proposto da J2EE laccesso alle informazioni contenute allinterno di questo strato possibile tramite oggetti chiamati connettori che rappresentano una architettura standard per integrare applicazioni e componenti enterprise eterogenee con sistemi informativi enterprise anche loro eterogenei. I connettori sono messi a disposizione della applicazione enterprise grazie al server J2EE che li contiene sotto forma di servizi di varia natura (es. Pool di connessioni) e che tramite questi collabora con i sistemi informativi appartenenti allo strato EIS in modo da rendere ogni meccanismo a livello di sistema completamente trasparente alle applicazioni enterprise. Lo stato attuale dellarte prevede che la piattaforma J2EE consenta pieno supporto per la connettivit verso database relazionali tramite le API JDBC, le prossime versioni della architettura J2EE prevedono invece pieno supporto transazionale verso i pi disparati sistemi enterprise oltre che a database relazionali.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

306

14
14.1 Introduzione

ARCHITETTURA DEL WEB TIER

Chi ha potuto accedere ad internet a partire dagli inizi degli anni 90, quando ancora la diffusione della tecnologia non aveva raggiunto la massa ed i costi erano eccessivi, ha assistito alla incredibile evoluzione che ci ha portato oggi ad una diversa concezione del computer. A partire dai primi siti internet dai contenuti statici, siamo oggi in grado di poter effettuare qualsiasi operazione da remoto tramite Web. Questa enorme crescita, come una vera rivoluzione, ha voluto le sua vittime. Nel corso degli anni sono nate, e poi completamente scomparse, un gran numero di tecnologie a supporto di un sistema in continua evoluzione. Servlet e JavaServer Pages rappresentano oggi lo stato dellarte delle tecnologie Web. Raccogliendo la pesante eredit lasciata dai suoi predecessori, la soluzione proposta da SUN rappresenta quanto di pi potente e flessibile possa essere utilizzato per sviluppare applicazioni web.

14.2 Larchitettura del Web Tier


Prima di affrontare il tema dello sviluppo di applicazioni Web con Servlet e JavaServer Pages, importante fissare alcuni concetti. Il web-server gioca un ruolo fondamentale allinterno di questa architettura, costituendone uno strato autonomo. In ascolto su un server, riceve richieste da parte del browser (client), le processa, quindi restituisce al client una entit o un eventuale codice di errore come prodotto della richiesta (Figura 118).

Figura 118:

Architettura del web tier

Le entit prodotte da un web-server possono essere entit statiche o entit dinamiche. Una entit statica , ad esempio, un file HTML residente sul file-

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

307

system del server. Rispetto alle entit statiche, unico compito del web-server quello di recuperare la risorsa dal file-system ed inviarla al browser che si occuper della visualizzazione dei contenuti. Quello che pi ci interessa sono invece le entit dinamiche, ossia entit prodotte dalla esecuzione di applicazioni eseguite dal web-server su richiesta del client. Il modello proposto nella figura precedente ora si complica in quanto viene introdotto un nuovo grado di complessit (Figura 119 ) nella architettura del sistema, i quale, oltre a fornire accesso a risorse statiche, dovr fornire un modo per accedere a contenuti e dati memorizzati su una Base Dati, dati con i quali un utente internet potr interagire da remoto.

Figura 119:

Architetture dinamiche

14.3 Inviare dati


Mediante form HTML possibile inviare dati ad un web-server. Tipicamente lutente riempie alcuni campi il cui contenuto, una volta premuto il pulsante submit, viene inviato al server con il messaggio di richiesta. La modalit con cui questi dati vengono inviati dipende dal metodo specificato nella richiesta. Utilizzando il metodo GET, i dati vengono appesi alla request-URI nella forma di coppie chiave=valore separati tra di loro dal carattere &. La stringa seguente un esempio di campo request-URI per una richiesta di tipo GET.

http://www.java-net.it/servelt/Hello?nome=Massimo&cognome=Rossi

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

308

http://www.java-net.it rappresenta lindirizzo del web-server a cui inviare la richiesta. I campi /servlet/Hello rappresenta la locazione della applicazione web da eseguire. Il carattere ? separa lindirizzo dai dati e il carattere & separa ogni coppia chiave=valore. Questo metodo, utilizzato per default dai browser a meno di specifiche differenti, viene utilizzato, ad esempio, nei casi in cui si richieda al server il recupero di informazioni mediante query su un database. Il metodo POST, pur producendo gli stessi effetti del precedente, utilizza una modalit di trasmissione dei dati differente dal momento che i contenuti dei campi di un form vengono inglobati allinterno di un message-header. Rispetto al precedente, il metodo POST consente di inviare una quantit maggiore di dati ed quindi utilizzato, ad esempio, quando necessario inviare al server nuovi dati affinch possano essere memorizzati allinterno della Base Dati. Si noti, inoltre, come lutilizzo del metodo POST nasconda allutente la struttura applicativa, dal momento che non vengono mostrati i nomi delle variabili utilizzati per inviare le informazioni.

14.4 Sviluppare applicazioni web


Dal punto di vista del flusso informativo, una applicazione web paragonabile ad una qualsiasi altro tipo di applicazione, con la fondamentale differenza relativa alla presenza del web-server che fornisce lambiente di runtime per lesecuzione. Affinch ci sia possibile necessario che esista un modo standard affinch il web-server possa trasmettere i dati inviati dal client allapplicazione, che esista un modo univoco per laccesso alle funzioni fornite dalla applicazione (entry-point), che lapplicazione sia in grado di restituire al web-server i dati prodotti in formato HTML da inviare al client nel messaggio di risposta. Esistono molte tecnologie per scrivere applicazioni Web, che vanno dalla pi comune CGI a soluzioni proprietarie come ISAPI di Microsoft o NSAPI della Netscape.

14.5 Common Gateway Interface


CGI sicuramente la pi comune e datata tra le tecnologie server-side per lo sviluppo di applicazioni web dinamiche. Scopo principale di un CGI quello di fornire funzioni di gateway tra il web-server e la base dati del sistema. I CGI possono essere scritti praticamente con tutti i linguaggi di programmazione. Questa caratteristica classifica i CGI in due categorie: CGI sviluppati mediante linguaggi script e CGI sviluppati con linguaggi compilati. I primi, per i quali vengono generalmente utilizzati linguaggi quali PERL e PHP, sono semplici da implementare, ma hanno lo svantaggio di essere molto lenti dal momento che la loro esecuzione richiede lintervento di un traduttore che trasformi le chiamate al linguaggio script in chiamate di sistema. I secondi a

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

309

differenza dei primi sono molto veloci, ma risultano difficili da programmare (generalmente sono scritti mediante linguaggio C). Nella Figura 120 schematizzato il ciclo di vita di un CGI. Il pi grande svantaggio nelluso dei CGI sta nella scarsa scalabilit della tecnologia: ogni volta che il web-server riceve una richiesta deve, infatti, creare una nuova istanza del CGI. Il dover istanziare un processo per ogni richiesta ha come conseguenza enormi carichi in fatto di risorse macchina e tempi di esecuzione. Tipicamente, per ogni richiesta, allavvio del CGI devono essere ripetuti i passi seguenti:

1. 2. 3. 4. 5. 6.

Caricamento dellapplicazione e avvio; Connessione alla base dati del server; Estrazione dei dati dalla richiesta http; Elaborazione dei dati; Costruzione della risposta in formato HTML; Trasmissione della risposta.

A meno di non utilizzare strumenti di altro tipo, inoltre impossibile riuscire ad ottimizzare gli accessi al database. Ogni istanza del CGI sar difatti costretta ad aprire e chiudere una propria connessione verso il DBMS. Questi problemi sono stati affrontati ed in parte risolti dai Fast-CGI grazie ai quali possibile condividere una stessa istanza tra pi richieste HTTP.

Figura 120:

Common Gateway Interface

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

310

14.6 ISAPI ed NSAPI


Per far fronte ai limiti tecnologici imposti dai CGI, Microsoft e Netscape hanno sviluppato API proprietarie mediante le quali creare librerie ed applicazioni che possono essere caricate dal Web server al suo avvio ed utilizzate come proprie estensioni. Linsuccesso di queste tecnologie stato, per, segnato proprio dalla loro natura di tecnologie proprietarie e quindi non portabili tra le varie piattaforme sul mercato. Essendo utilizzate come moduli del Web-server, era inoltre caso frequente che un loro accesso errato alla memoria causasse il crash dellintero Web server, provocando enormi danni al sistema che doveva ogni volta essere riavviato.

14.7 ASP Active Server Pages


Active Server Pages stata lultima tecnologia Web rilasciata da Microsoft. Una applicazione ASP tipicamente un misto tra HTML e linguaggi script come VBScript o JavaScript mediante i quali si pu accedere ad oggetti del server. Una pagina ASP, a differenza di ISAPI, non viene eseguita come estensione del Web-server, ma viene compilata alla prima chiamata, ed il codice compilato pu quindi essere utilizzato ad ogni richiesta HTTP. Nonostante sia oggi largamente diffusa, ASP, come ISAPI, rimane una tecnologia proprietaria e quindi in grado di funzionare solamente su piattaforma Microsoft. Il reale svantaggio di ASP , per, legato alla sua natura di mix tra linguaggi script ed HTML. Questa sua caratteristica ha come effetto secondario quello di riunire in un unico contenitore sia le logiche applicative che le logiche di presentazione, rendendo estremamente complicate le normali operazioni di manutenzione delle pagine.

14.8 Java Servlet e JavaServer Pages


Sun cerca di risolvere i problemi legati alle varie tecnologie illustrate mettendo a disposizione del programmatore due tecnologie che, raccogliendo leredit dei predecessori, ne abbattono i limiti, mantenendo e migliorando gli aspetti positivi di ognuna. In aggiunta, Servlet e JavaServer Pages ereditano tutte quelle caratteristiche che rendono le applicazioni Java potenti, flessibili e semplici da mantenere: portabilit del codice e paradigma Object Oriented. Rispetto ai CGI, Servlet forniscono un ambiente ideale per quelle applicazioni per cui sia richiesta una massiccia scalabilit e di conseguenza lottimizzazione degli accessi alle basi dati di sistema. Rispetto ad ISAPI ed NSAPI, pur rappresentando una estensione al web server, mediante il meccanismo delle eccezioni risolvono a priori tutti gli errori che potrebbero causare la terminazione prematura del sistema.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

311

Rispetto ad ASP, Servlet e JavaServer Pages separano completamente le logiche di business dalle logiche di presentazione dotando il programmatore di un ambiente semplice da modificare o mantenere.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

312

15
15.1 Introduzione

JAVA SERVLET API

Java Servlet sono oggetti Java con propriet particolari che vengono caricati ed eseguiti dal Web server che le utilizzer come proprie estensioni. Il Web server, di fatto, mette a disposizione delle Servlet il container che si occuper della gestione del loro ciclo di vita, delle gestione dellambiente allinterno delle quali le servlet girano, dei servizi di sicurezza. Il container ha anche la funzione di passare i dati dal client verso le servlet, e viceversa ritornare al client i dati prodotti dalla loro esecuzione. Dal momento che una servlet un oggetto server-side, pu accedere a tutte le risorse messe a disposizione dal server per generare pagine dai contenuti dinamici come prodotto della esecuzione delle logiche di business. E ovvio che sar cura del programmatore implementare tali oggetti affinch gestiscano le risorse del server in modo appropriato, evitando di causare danni al sistema che le ospita.

15.2 Il package javax.servlet


Questo package il package di base delle Servlet API, e contiene le classi per definire Servlet standard indipendenti dal protocollo. Tecnicamente una Servlet generica una classe definita a partire dallinterfaccia Servlet contenuta allinterno del package javax.servlet. Questa interfaccia contiene i prototipi di tutti i metodi necessari alla esecuzione delle logiche di business, nonch alla gestione del ciclo di vita delloggetto dal momento del suo istanziamento, sino al momento della sua terminazione.
package javax.servlet; import java.io.*; public interface Servlet { public abstract void destroy(); public ServletConfig getServletConfig(); public String getServletInfo(); public void service (ServletRequest req, ServletResponse res) throws IOException, ServletException; }

I metodi definiti in questa interfaccia devono essere supportati da tutte le servlet o possono essere ereditati attraverso la classe astratta GenericServlet che rappresenta una implementazione base di una servlet generica. Nella Figura 121 viene schematizzata la gerarchia di classi di questo package. Il package include inoltre una serie di classi utili alla comunicazione tra client e server, nonch alcune interfacce che definiscono i prototipi di oggetti

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

313

utilizzati per tipizzare le classi che saranno necessarie alla specializzazione della servlet generica in servlet dipendenti da un particolare protocollo.

Figura 121:

Il package javax.servlet

15.3 Il package javax.servlet.http


Questo package supporta lo sviluppo di Servlet che, specializzando la classe base astratta GenericServlet definita nel package javax.servlet, utilizzano il protocollo HTTP.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

314

Figura 122:

Il package javax.servlet.http

Le classi di questo package estendono le funzionalit di base di una servlet supportando tutte le caratteristiche della trasmissione di dati con protocollo HTTP compresi cookies, richieste e risposte HTTP nonch metodi HTTP (get, post head, put ecc. ecc.). Nella Figura 122 schematizzata la gerarchia del package in questione. Formalmente, quindi, una servlet specializzata per generare contenuti specifici per il mondo web sar ottenibile estendendo la classe base astratta javax.servlet.http.HttpServlet.

15.4 Ciclo di vita di una servlet


Il ciclo di vita di una servlet definisce come una servlet sar caricata ed inizializzata, come ricever e risponder a richieste dal client ed infine come sar terminata prima di passare sotto la responsabilit del garbage collector. Il ciclo di vita di una servlet schematizzato nel diagramma seguente (Figura 123).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

315

Figura 123:

Ciclo di vita di una servlet

Il caricamento e listanziamento di una o pi servlet a carico del Web server ed avviene al momento della prima richiesta HTTP da parte di un client, o, se specificato direttamente, al momento dellavvio del servizio. Questa fase viene eseguita dal Web server utilizzando loggetto Class del package java.lang. Dopo che stata caricata, necessario che la servlet venga inizializzata. Durante questa fase, la servlet generalmente carica dati persistenti, apre connessioni verso il database o stabilisce legami con altre entit esterne. Linizializzazione della servlet avviene mediante la chiamata al metodo init(), definito nella interfaccia javax.servlet.Servlet ed ereditato dalla classe base astratta javax.servlet.http.HttpServlet. Nel caso in cui il metodo non venga riscritto, il metodo ereditato non eseguir nessuna operazione. Il metodo init di una servlet prende come parametro di input un oggetto di tipo ServletConfig che consente di accedere a parametri di inizializzazione passati attraverso il Web server nella forma di coppie chiave-valore. Dopo che la nostra servlet stata inizializzata, pronta ad eseguire richieste da parte di un client. Le richieste da parte di un client vengono inoltrate alla servlet dal container nella forma di oggetti di tipo HttpServletRequest e HttpServletResponse mediante passaggio di parametri al momento della chiamata del metodo service(). Come per init(), il metodo service() viene ereditato di default dalla classe base astratta HttpServlet. In questo caso per il metodo ereditato, a meno che non venga ridefinito, eseguir alcune istruzioni di default come vedremo tra qualche paragrafo. E comunque importante ricordare che allinterno di questo metodo vengono definite le logiche di business della nostra applicazione. Mediante loggetto HttpServletRequest saremo in grado di accedere ai parametri passati in input dal client, processarli e rispondere con un oggetto di tipo HttpServletResponse. Infine, quando una servlet deve essere distrutta (tipicamente quando viene terminata lesecuzione della chiamata proveniente dal web server), il container chiama di default il metodo destroy(). LOverriding di questo metodo ci consentir di rilasciare tutte le risorse utilizzate dalla servlet, ad

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

316

esempio le connessioni a basi dati, garantendo che il sistema non rimanga in uno stato inconsistente a causa di una gestione malsana da parte della applicazione web.

15.5 Servlet e multithreading


Tipicamente, quando n richieste da parte del client arrivano al web server, vengono creati n thread differenti in grado di accedere ad una particolare servlet in maniera concorrente (Figura 124).

Figura 124:

Accesso Concorrente alle Servlet

Come tutti gli oggetti Java, servlet non sono oggetti thread-safe, ovvero necessario che il programmatore definisca le politiche di accesso alla istanza della classe da parte del thread. Inoltre, lo standard definito da SUN Microsystem prevede che un server possa utilizzare una sola istanza di questi oggetti (questa limitazione anche se tale aiuta alla ottimizzazione della gestione delle risorse per cui, ad esempio, una connessione ad un database sar condivisa tra tante richieste da parte di client). Mediante lutilizzo delloperatore synchronized, possibile sincronizzare laccesso alla classe da parte dei thread, dichiarando il metodo service() di tipo sincronizzato o limitando lutilizzo del modificatore a singoli blocci di codice che contengono dati sensibili.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

317

15.6 Linterfaccia SingleThreadModel


Quando un thread accede al metodo sincronizzato di una servlet, ottiene il lock (ossia lesclusiva) sulla istanza delloggetto. Abbiamo inoltre detto che un Web server, per definizione, utilizza una sola istanza di servlet condividendola tra le varie richieste da parte dei client. Stiamo creando un collo di bottiglia che, in caso di sistemi con grossi carichi di richieste, potrebbe ridurre significativamente le prestazioni del sistema. Se il sistema dispone di abbondanti risorse, le Servlet API ci mettono a disposizione un metodo per risolvere il problema a scapito delle risorse della macchina rendendo le servlet classi thread-safe. Formalmente, una servlet viene considerata thread-safe se implementa linterfaccia javax.servlet.SingleThreadModel . In questo modo saremo sicuri che solamente un thread avr accesso ad una istanza della classe in un determinato istante. A differenza dellutilizzo del modificatore synchronized, in questo caso il Web server creer pi istanze di una stessa servlet (tipicamente in numero limitato e definito) al momento del caricamento delloggetto, e utilizzer le varie istanze assegnando al thread che ne faccia richiesta, la prima istanza libera (Figura 125).

Figura 125:

SingleThreadModel

15.7 Un primo esempio di classe Servlet


Questa prima servlet di esempio fornisce la versione Web della applicazione Java HelloWorld. La nostra servlet, una volta chiamata, ci restituir una pagina HTML contenente semplicemente la stringa HelloWorld.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

318

import javax.servlet.* ; import javax.servlet.http.* ; public class HelloWorldServlet extends HttpServlet { public void service (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType(text/html); ServletOutputStream out = res.getOutputStream(); out.println(<html>); out.println(<head><title>Hello World</title></head>); out.println(<body>); out.println(<h1>Hello World</h1>); out.println(</body></html>); } }

15.8 Il metodo service()


Se il metodo service() di una servlet non viene modificato, la nostra classe eredita di default il metodo service definito allinterno della classe astratta HttpServlet (Figura 126). Essendo il metodo chiamato in causa al momento dellarrivo di una richiesta da parte di un client, nella sua forma originale questo metodo ha funzioni di dispatcher tra altri metodi basati sul tipo di richiesta HTTP in arrivo dal client.

Figura 126:

Il metodo service

Come abbiamo gi introdotto nel capitolo precedente, il protocollo HTTP ha una variet di tipi differenti di richieste che possono essere avanzate da parte

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

319

di un client. Comunemente quelle di uso pi frequente sono le richieste di tipo GET e POST. Nel caso di richiesta di tipo GET o POST, il metodo service definito allinterno della classe astratta HttpServlet chiamer i metodi rispettivamente doGet() o doPost() che conterranno ognuno il codice per gestire la particolare richiesta. In questo caso quindi, non avendo applicato la tecnica di Overriding sul metodo service(), sar necessario implementare almeno uno di questi metodi allinterno della nostra nuova classe. a seconda del tipo di richieste che dovr esaudire (Figura 127).

Figura 127:

Il metodo Service

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

320

16
16.1 Introduzione

SERVLET HTTP

HTTP servlet rappresentano una specializzazione di servlet generiche e sono specializzate per comunicare mediante protocollo HTTP. Il package javax.servlet.http mette a disposizione una serie di definizioni di classe che rappresentano strumenti utili alla comunicazione tra client e server con questo protocollo, nonch forniscono uno strumento flessibile per accedere alle strutture definite nel protocollo, al tipo di richieste inviate, ai dati trasportati. Le interfacce ServletRequest e ServletResponse rappresentano, rispettivamente, richieste e risposte HTTP. In questo capitolo le analizzeremo in dettaglio con lo scopo di comprendere i meccanismi di ricezione e manipolazione dei dati di input nonch di trasmissione delle entit dinamiche prodotte.

16.2 Il protocollo HTTP 1.1


Rispetto alla classificazione fornita nei paragrafi precedenti riguardo i protocolli di rete, HTTP (HyperText Trasfer Protocol) appartiene allinsieme dei protocolli applicativi e nella sua ultima versione, fornisce un meccanismo di comunicazione tra applicazioni estremamente flessibile dotato di capacit praticamente infinita nel descrivere possibili richieste, nonch i possibili scopi per cui la richiesta stata inviata. Grazie a queste caratteristiche HTTP largamente utilizzato per la comunicazione tra applicazioni in cui sia necessaria la possibilit di negoziare dati e risorse, da qui luso del protocollo come standard per i web server e le applicazioni Web based. Il protocollo fu adottato nel 1990 dalla World Wide Web Global Information Initiative a partire dalla versione 0.9 come schematizzato nella tabella sottostante: Il protocollo HTTP Descrizione Semplice protocollo per la trasmissione dei dati via internet. Introduzione rispetto alla versione precedente del concetto di Mime-Type. Il protocollo ora in grado di trasportare metainformazioni relative ai dati trasferiti. Estende la versione precedente affinch il protocollo contenga informazioni che tengono in considerazione problematiche a livello applicativo come ad esempio quelle legate alla gestione della cache.

Versione HTTP 0.9 HTTP 1.0

HTTP 1.1

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

321

Scendendo nei dettagli, HTTP un protocollo di tipo Request/Response: un client invia una richiesta al server e rimane in attesa di una sua risposta. Entrambe, richiesta e risposta HTTP, hanno una struttura che identifica due sezioni principali: un message-header ed un message-body, come schematizzato nella figura 128. Il message-header contiene tutte le informazioni relative alla richiesta o risposta, mentre il message-body contiene eventuali entit trasportate dal pacchetto (intendendo per entit i contenuti dinamici del protocollo come pagine HTML) e tutte le informazioni relative: ad esempio mime-type, dimensioni ecc. Le entit trasportate allinterno del message-body vengono inserite allinterno dellentity-body. Le informazioni contenute allinterno di un messaggio HTTP sono separate tra di loro dalla sequenza di caratteri CRLF dove CR il carattere Carriage Return (US ASCII 13) e LF il carattere Line Feed (US ASCII 10). Tale regola non applicabile ai contenuti dellentity-body che rispettano il formato come definito dal rispettivo MIME.

Figura 128:

Struttura di http Request e Response

Per dare una definizione formale del protocollo HTTP utilizzeremo la sintassi della forma estesa di Backus Naur che per semplicit indicheremo con lacronimo BNF. Secondo la BNF, un messaggio HTTP generico definito dalla seguente regola:

HTTP-Message = Request | Response

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

322

Request = generic-message Response = generic-message generic-message = start-line *(message-header CRLF) CRLF [messagebody] start-line = Request-line | Response-line

16.3 Richiesta HTTP


Scendendo nei dettagli, una richiesta HTTP da un client ad un server definita dalla seguente regola BNF:

Request = Request-line *((general-header | request-header | entity-header) CRLF) CRLF [message-body]


La Request-line formata da tre token separati dal carattere SP o Spazio (US ASCII 32) che rappresentano rispettivamente: metodo, indirizzo della risorsa o URI, versione del protocollo.

Request-line = Method SP Request-URI SP http-version CRLF SP = <US-ASCII Space (32) > Method = OPTIONS | GET | HEAD | POST | PUT | DELETE | TRACE | CONNECT | extension-method extension-method = token Request-URI = * | absoluteURI | abs-path | authority HTTP-version = HTTP / 1*DIGIT . 1*DIGIT DIGIT = <any US ASCII digit 0....9>
Un esempio di Request-line inviata da client a server potrebbe essere :

GET /lavora.html HTTP/1.1


Il campo General-header ha valore generale per entrambi I messagi di richiesta o risposta e non contiene dati applicabili alla entit trasportata con il messaggio:

General-header = Cache-Control | Connection | Date | Pragma | Trailer | Trasfer-Encoding

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

323

| Upgrade | Via | Warning


Senza scendere nei dettagli di ogni singolo campo contenuto nel Generalheader, importante comprenderne lutilizzo. Questo campo infatti estremamente importante in quanto trasporta le informazioni necessarie alla negoziazione tra le applicazioni. Ad esempio, il campo Connection pu essere utilizzato da un server proxy per determinare lo stato della connessione tra client e server e decidere su eventuali misura da applicare. I campi di request-header consentono al client di inviare informazioni relative alla richiesta in corso ed al client stesso.

Request-header = Accept | Accept-Charset | Accept-Encoding | Accept-Language | Authorization | Expect | From | Host | If-Match | If-Modified-Since | If-None-Match | If-Range | If-Unmodified-Since | Max-Forwards | Proxy-Authorization | Range | Referer | TE | User-Agent Entity-header e Message-Body verranno trattati nei paragrafi seguenti.

16.4 Risposta HTTP


Dopo aver ricevuto un messaggio di richiesta, il server risponde con un messaggio di risposta definito dalla regola BNF:

Response = Status-line *((general-header | response-header | entity-header) CRLF)

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

324

CRLF [message-body]
La Status-line la prima linea di un messaggio di risposta HTTP e consiste di tre token separati da spazio, contenenti rispettivamente: informazioni sulla versione del protocollo, un codice di stato che indica un errore o leventuale buon fine della richiesta, una breve descrizione del codice di stato.

Status-line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF SP = <US-ASCII Space (32) > Status-Code = "100" : Continue | "101 : Switching Protocols | "200" : OK | "201" : Created | "202" : Accepted | "203" : Non-Authoritative Information | "204" : No Content | "205" : Reset Content | "206" : Partial Content | "300" : Multiple Choices | "301" : Moved Permanently | "302" : Found | "303" : See Other | "304" : Not Modified | "305" : Use Proxy | "307" : Temporary Redirect | "400" : Bad Request | "401" : Unauthorized | "402" : Payment Required | "403" : Forbidden | "404" : Not Found | "405" : Method Not Allowed | "406" : Not Acceptable | "407" : Proxy Authentication Required | "408" : Request Time-out | "409" : Conflict | "410" : Gone | "411" : Length Required | "412" : Precondition Failed | "413" : Request Entity Too Large | "414" : Request-URI Too Large | "415" : Unsupported Media Type

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

325

| "416" : Requested range not satisfiable | "417" : Expectation Failed | "500" : Internal Server Error | "501" : Not Implemented | "502" : Bad Gateway | "503" : Service Unavailable | "504" : Gateway Time-out | "505" : HTTP Version not supported | extension-code extension-code = 3DIGIT Reason-Phrase = *<TEXT, excluding CR, LF>
Un codice di stato un intero a 3 cifre ed il risultato della elaborazione della richiesta da parte del server, mentre la Reason-Phrase intende fornire una breve descrizione del codice di stato. La prima cifra di un codice di stato definisce la regola per suddividere i vari codici: 1xx : Informativo Richiesta ricevuta, continua la processazione dei dati; 2xx : Successo Lazione stata ricevuta con successo, compresa ed accettata; 3xx : Ridirezione Ulteriori azioni devono essere compiute al fine di completare la richiesta; 4xx : Client-Error La richiesta sintatticamente errata e non pu essere soddisfatta; 5xx : Server-Error Il server non ha soddisfatto la richiesta apparentemente valida. Come per il Request-Header, il Response-Header contiene campi utili alla trasmissione di informazioni aggiuntive relative alla risposta e quindi anchesse utili alla eventuale negoziazione :

response-header = Accept-Ranges | Age | Etag | Location | Proxy-Authenticate | Retry-After | Server | www-autheticate

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

326

16.5 Entit
Come abbiamo visto, richiesta e risposta HTTP possono trasportare entit, a meno che non sia previsto diversamente dallo status-code della risposta o dal request-method della richiesta (cosa che vedremo nel paragrafo successivo). In alcuni casi un messaggio di risposta pu contenere solamente il campo entity-header (ossia il campo descrittore della risorsa trasportata). Come regola generale, entity-header trasporta meta-informazioni relative alla risorsa identificata dalla richiesta.

entity-header = Allow | Content-Encoding | Content-Language | Content-Length | Content-Location | Content-MD5 | Content-Range | Content-Type | Expires | Last-Modified | extension-header extension-header = message-header
Queste informazioni, a seconda dei casi, possono essere necessarie o opzionali. Il campo extension-header consente laggiunta di una nuove informazioni non previste, ma necessarie, senza apportare modifiche al protocollo.

16.6 I metodi di request


I metodi definiti dal campo request-method sono necessari al server per poter prendere decisioni sulle modalit di elaborazione della richiesta. Le specifiche del protocollo HTTP prevedono nove metodi, tuttavia, tratteremo brevemente i tre pi comuni: HEAD, GET, POST. Il metodo GET significa voler ottenere dal server qualsiasi informazione (nella forma di entit) come definita nel campo request-URI. Se utilizzato per trasferire dati mediante form HTML, una richiesta di tipo GET invier i dati contenuti nel form scrivendoli in chiaro nella request-URI. Simile al metodo GET il metodo HEAD che ha come effetto quello di indicare al server che non necessario includere nella risposta un message-body contenente la entit richiesta in request-URI. Tale metodo ha lo scopo di ridurre i carichi di rete in quei casi in cui non sia necessario linvio di una

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

327

risorsa: ad esempio nel caso in cui il client disponga di un meccanismo di caching. Il metodo POST quello generalmente utilizzato l dove siano necessarie operazioni in cui richiesto il trasferimento di dati al server affinch siano processati. Una richiesta di tipo POST non utilizza il campo Request-URI per trasferire i parametri, bens invia un pacchetto nella forma di message-header con relativo entity-header. Il risultato di una operazione POST non produce necessariamente entit. In questo caso uno status-code 204 inviato dal server indicher al client che la processazione andata a buon fine senza che per abbia prodotto entit.

16.7 Inizializzazione di una Servlet


Linterfaccia ServletConfig rappresenta la configurazione iniziale di una servlet. Oggetti definiti per implementazione di questa interfaccia, contengono i parametri di inizializzazione della servlet (se esistenti) nonch permettono alla servlet di comunicare con il servlet container restituendo un oggetto di tipo ServletContext.
public interface ServletConfig { public ServletContext getServletContext(); public String getInitParameter(String name); public Enumeration getInitParameterNames(); }

I parametri di inizializzazione di una servlet devono essere definiti allinterno dei file di configurazione del web server che si occuper di comunicarli alla servlet in forma di coppie chiave=valore. I metodi messi a disposizione da oggetti di tipo ServletConfig per accedere a questi parametri sono due: il metodo getInitParameterNames(), che restituisce un elenco (enumerazione) dei nomi dei parametri, ed il metodo getInitParameter(String) che, preso in input il nome del parametro in forma di oggetto String, restituisce a sua volta una stringa contenente il valore del parametro di inizializzazione. Nellesempio seguente, utilizziamo il metodo getInitParameter(String) per ottenere il valore del parametro di input con chiave CHIAVE_PARAMETRO: String key = CHIAVE_PARAMETRO; String val = config.getInitParameter(key); dove config rappresenta un oggetto di tipo ServletConfig passato come parametro di input al metodo init(ServletConfig) della servlet. Il metodo getServletContext() , restituisce invece un oggetto di tipo ServletContext tramite il quale possibile richiedere al container lo stato dellambiente allinterno del quale le servlet stanno girando.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

328

Un metodo alternativo per accedere ai parametri di configurazione della servlet messo a disposizione dal metodo getServletConfig() definito allinterno della interfaccia servlet di javax.servlet. Utilizzando questo metodo, sar di fatto possibile accedere ai parametri di configurazione anche allinterno del metodo service(). Nel prossimo esempio viene definita una servlet che legge un parametro di input con chiave INPUTPARAMS, lo memorizza in un dato membro della classe e ne utilizza il valore per stampare una stringa alla esecuzione del metodo service().
import javax.servlet.* ; import javax.servlet.http.* ; import java.io ; public class Test extends HttpServlet { String initparameter=null; public void init(ServletConfig config) { initparameter = config.getInitParameter(INPUTPARAMS); } public void service (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType(text/html); ServletOutputStream out = res.getOutputStream(); out.println(<html>); out.println(<head><title>Test</title></head>); out.println(<body>); out.println(<h1>Il parametro di input vale + initparameter +</h1>); out.println(</body></html>); } }

16.8 Loggetto HttpServletResponse


Questo oggetto definisce il canale di comunicazione tra la servlet ed il client che ha inviato la richiesta (browser). Questo oggetto mette a disposizione della servlet i metodi necessari per inviare al client le entit prodotte dalla manipolazione dei dati di input. Una istanza delloggetto ServletResponse viene definita per implementazione della interfaccia HttpServletResponse definita allinterno del package javax.servlet.http che definisce una specializzazione della interfaccia derivata rispetto al protocollo HTTP.
package javax.servlet; import java.io.*; public interface ServletResponse extends RequestDispatcher

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

329

{ public public public public public }

String getCharacterEncoding(); ServletOutputStream getOutputStream() throws IOException; PrintWriter getWriter() throws IOException; void setContentLength(int length); void setContentType(String type);

package javax.servlet.http; import java.util.*; import java.io.*; public interface HttpServletResponse extends javax.servlet.ServletResponse { public static final int SC_CONTINUE = 100; public static final int SC_SWITCHING_PROTOCOLS = 101; public static final int SC_OK = 200; public static final int SC_CREATED = 201; public static final int SC_ACCEPTED = 202; public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203; public static final int SC_NO_CONTENT = 204; public static final int SC_RESET_CONTENT = 205; public static final int SC_PARTIAL_CONTENT = 206; public static final int SC_MULTIPLE_CHOICES = 300; public static final int SC_MOVED_PERMANENTLY = 301; public static final int SC_MOVED_TEMPORARILY = 302; public static final int SC_SEE_OTHER = 303; public static final int SC_NOT_MODIFIED = 304; public static final int SC_USE_PROXY = 305; public static final int SC_BAD_REQUEST = 400; public static final int SC_UNAUTHORIZED = 401; public static final int SC_PAYMENT_REQUIRED = 402; public static final int SC_FORBIDDEN = 403; public static final int SC_NOT_FOUND = 404; public static final int SC_METHOD_NOT_ALLOWED = 405; public static final int SC_NOT_ACCEPTABLE = 406; public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407; public static final int SC_REQUEST_TIMEOUT = 408; public static final int SC_CONFLICT = 409; public static final int SC_GONE = 410; public static final int SC_LENGTH_REQUIRED = 411; public static final int SC_PRECONDITION_FAILED = 412; public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413; public static final int SC_REQUEST_URI_TOO_LONG = 414; public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415; public static final int SC_INTERNAL_SERVER_ERROR = 500; public static final int SC_NOT_IMPLEMENTED = 501; public static final int SC_BAD_GATEWAY = 502; public static final int SC_SERVICE_UNAVAILABLE = 503; public static final int SC_GATEWAY_TIMEOUT = 504; public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505; void addCookie(Cookie cookie); boolean containsHeader(String name); String encodeRedirectUrl(String url); //deprecated String encodeRedirectURL(String url); String encodeUrl(String url); String encodeURL(String url); void sendError(int statusCode) throws java.io.IOException;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

330

void void void void void void void }

sendError(int statusCode, String message) throws java.io.IOException; sendRedirect(String location) throws java.io.IOException; setDateHeader(String name, long date); setHeader(String name, String value); setIntHeader(String name, int value); setStatus(int statusCode); setStatus(int statusCode, String message);

Questa interfaccia definisce i metodi per impostare dati relativi alla entit inviata nella risposta (lunghezza e tipo mime ), fornisce informazioni relativamente al set di caratteri utilizzato dal browser (in HTTP questo valore viene trasmesso nellheader Accept-Charset del protocollo), infine fornisce alla servlet laccesso al canale di comunicazione per inviare lentit al client. I due metodi deputati alla trasmissione sono quelli definiti nella interfaccia ServletResponse. Il primo, ServletOutputStream getOutputStream(), restituisce un oggetto di tipo ServletOutputStream e consente di inviare dati al client in forma binaria (ad esempio il browser richiede il download di un file). Il secondo PrintWriter getWriter() restituisce un oggetto di tipo PrintWriter e quindi consente di trasmettere entit allo stesso modo di una System.out. Utilizzando questo secondo metodo, il codice della servlet mostrato nel paragrafo precedente diventa quindi:
import javax.servlet.* ; import javax.servlet.http.* ; import java.io ; public class Test extends HttpServlet { String initparameter=null; public void init(ServletConfig config) { initparameter = config.getInitParameter(INPUTPARAMS); } public void service (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType(text/html); PrintWriter out = res. getWriter (); out.println(<html>); out.println(<head><title>Test</title></head>); out.println(<body>); out.println(<h1>Il parametro di input vale + initparameter +</h1>); out.println(</body></html>); } }

Nel caso in cui sia necessario utilizzare il metodo setContentType , sar obbligatorio effettuarne la chiamata prima di utilizzare il canale di output.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

331

16.9 I metodi specializzati di HttpServletResponse


Nel paragrafo precedente abbiamo analizzato i metodi di HttpServletResponse ereditati a partire dalla interfaccia ServletResponse. Come si vede dal codice riportato nel paragrafo precedente, HttpServletResponse specializza un oggetto response affinch possa utilizzare gli strumenti tipici del protocollo HTTP mediante metodi specializzati e legati alla definizione del protocollo. I metodi definiti allinterno di questa interfaccia coprono la possibilit di modificare o aggiungere campi allinterno dellheader del protocollo HTTP, metodi per lavorare utilizzando i cookie, metodi per effettuare URL encoding o per manipolare errori HTTP. E inoltre possibile, mediante il metodo sendRedirect(), provocare il reindirizzamento della connessione verso un altro server sulla rete.

16.10 Notificare errori utilizzando Java Servlet


Allinterno della interfaccia HttpServletResponse, oltre ai prototipi dei metodi, vengono dichiarate tutta una serie di costanti che rappresentano i codici di errore cos come definiti dallo standard HTTP. Il valore di queste costanti pu essere utilizzato, mediante i metodi sendError(..) e setStatus(), per inviare al browser particolari messaggi con relativi codici di errore. Un esempio realizzato nella servlet seguente:
import javax.servlet.* ; import javax.servlet.http.* ; import java.io ; public class TestStatus extends HttpServlet { String initparameter=null; public void init(ServletConfig config) { } public void service (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType(text/html); ServletOutputStream out = res.getOutputStream(); out.println(<html>); out.println(<head><title>Test Status Code</title></head>); out.println(<body>); res.sendError(HttpServletResponse. SC_OK, Il sito e stato temporaneamente sospeso); out.println(</body></html>); } }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

332

16.11 Loggetto HttpServletRequest


Come oggetti di tipo HttpServletResponse rappresentano una risposta HTTP, oggetti di tipo HttpServletRequest rappresentano una richiesta HTTP.
package javax.servlet; import java.net.*; import java.io.*; import java.util.*; public interface ServletRequest { public Object getAttribute(String name); public Enumeration getAttributeNames(); public String getCharacterEncoding(); public int getContentLength(); public String getContentType(); public ServletInputStream getInputStream() throws IOException; public String getParameter(String name); public Enumeration getParameterNames(); public String[] getParameterValues(String name); public String getProtocol(); public BufferedReader getReader() throws IOException; public String getRealPath(String path); public String getRemoteAddr(); public String getRemoteHost(); public String getScheme(); public String getServerName(); public int getServerPort(); public Object setAttribute(String name, Object attribute); }

package javax.servlet.http; import java.util.*; import java.io.*; public interface HttpServletRequest extends javax.servlet.ServletRequest { String getAuthType(); Cookie[] getCookies(); long getDateHeader(String name); String getHeader(String name); Enumeration getHeaderNames(); int getIntHeader(String name); String getMethod(); String getPathInfo(); String getPathTranslated(); String getQueryString(); String getRemoteUser(); String getRequestedSessionId(); String getRequestURI(); String getServletPath(); HttpSession getSession(); HttpSession getSession(boolean create); boolean isRequestedSessionIdFromCookie();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

333

boolean isRequestedSessionIdFromUrl(); boolean isRequestedSessionIdFromURL(); boolean isRequestedSessionIdValid(); }

Mediante i metodi messi a disposizione da questa interfaccia, possibile accedere ai contenuti della richiesta HTTP inviata dal client, compresi eventuali parametri o entit trasportate allinterno del pacchetto HTTP. I metodi ServletInputStream getInputStream() e BufferedReader getReader() ci permettono di accedere ai dati trasportati dal protocollo. Il metodo getParameter(String) ci consente di ricavare i valori dei parametri contenuti allinterno della query string della richiesta referenziandoli tramite il loro nome. Esistono inoltre altri metodi che consentono di ottenere meta-informazioni relative alla richiesta: ad esempio i metodi
.. public String getRealPath(String path); public String getRemoteAddr(); public String getRemoteHost(); public String getScheme(); public String getServerName(); public int getServerPort(); .

Nel prossimo esempio utilizzeremo questi metodi per implementare una servlet che stampa a video tutte le informazioni relative alla richiesta da parte del client:
import javax.servlet.* ; import javax.servlet.http.* ; import java.io ; public class Test extends HttpServlet { String initparameter=null; public void service (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType(text/html); PrintWriter out = res. getWriter (); out.println(<html>); out.println(<head><title>Test</title></head>); out.println(<body>); out.println(<b>Carachter encoding</b>+req.getCharacterEncoding()+<BR>); out.println(<b>Mime tipe dei contenuti</b>+req.getContentType()+<BR>); out.println(<b>Protocollo</b>+req.getProtocol()+<BR>); out.println(<b>Posizione fisica:</b>+req.getRealPath()+<BR>); out.println(<b>Schema:</b>+req.getScheme()+<BR>); out.println(<b>Nome del server:</b>+req.getServerName()+<BR>); out.println(<b>Porta del server:</b> +req.getServerPort()+<BR>);

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

334

out.println(</body></html>); out.close(); } }

Altri metodi di questa interfaccia ci consentono di utilizzare i cookie esistenti sul client oppure ottenere meta-informazioni relative al client che ha inviato la richiesta.

16.12 Inviare dati mediante la query string


Nel capitolo 13 abbiamo introdotto la trasmissione di dati tra client e applicazione Web. Ricordando quanto detto, utilizzando il metodo GET del protocollo HTTP, i dati vengono appesi alla URL che identifica la richiesta nella forma di coppie nome=valore separate tra loro dal carattere &. Questa concatenazione di coppie detta query string ed separata dallindirizzo della risorsa dal carattere ?. Ad esempio la URL

http://www.java-net.it/servelt/Hello?nome=Massimo&cognome=Rossi
contiene la query string ?nome=Massimo&cognome=Rossi. Abbiamo inoltre accennato al fatto che, qualora il metodo utilizzato sia il metodo POST, i dati non vengano trasmessi in forma di query string ma vengono accodati nella sezione dati del protocollo HTTP. Le regole utilizzare in automatico dal browser e necessarie al programmatore per comporre la query string sono le seguenti:

La query string inizia con il carattere ?. Utilizza coppie nome=valore per trasferire i dati. Ogni coppia deve essere separata dal carattere &. Ogni carattere spazio deve essere sostituito dalla sequenza %20. Ogni carattere % deve essere sostituito dalla sequenza %33.
I valori di una query string possono essere recuperati da una servlet utilizzando i metodi messi a disposizione da oggetti di tipo HttpServletRequest . In particolare, nellinterfaccia HttpServletRequest vengono definiti quattro metodi utili alla manipolazione dei parametri inviati dal browser.
public public public public String getQueryString (); Enumeration getParameterNames(); String getParameter(String name); String[] getParameterValues(String name);

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

335

Il primo di questi metodi ritorna una stringa contenente la query string, se inviata dal client (null in caso contrario); il secondo ritorna una Enumerazione dei nomi dei parametri contenuti nella query string; il terzo il valore di un parametro a partire dal suo nome; infine il quarto ritorna un array di valori del parametro il cui nome viene passato in input. Questultimo metodo viene utilizzato quando il client utilizza oggetti di tipo checkbox che possono prendere pi valori contemporaneamente. Nel caso in cui venga utilizzato il metodo POST, il metodo getQueryString risulter inutile dal momento che questo metodo non produce la query string.

16.13 Query String e Form Html


Il linguaggio HTML pu produrre URL contenenti query string definendo, allinterno della pagina HTML, dei form. La sintassi per costruire form HTML la seguente:
<FORM method=[GET/POST] action=URL> <INPUT type=[text|textarea|select|hidden|option] name=nomedelvalore [value= valoreiniziale1]> <INPUT type=[text|textarea|select|hidden|option] name=nomedelvalore2 [value= valoreiniziale2]> . <INPUT type=[text|textarea|select|hidden|option] name=nomedelvaloreN[value= valoreinizialeN]> <INPUT type=SUBMIT value=labeldelpulsante> </FORM>

Nellistante in cui lutente preme il pulsante SUBMIT, il browser crea una query string contenente le coppie nome=valore che identificano i dati nel form e la appende alla URL che punter alla risorsa puntata dal campo action del tag <FORM>.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

336

Figura 129:

Un form HTML

Vediamo un form di esempio:


<html> <body> <FORM method=GET action=http://www.java-net.it/Prova> <INPUT type=hidden name=param value="nascosto"><br> Nome:<br><INPUT type=text name=nome value=""><br> Cognome:<br><INPUT type=text name=cognome value=""><br> Et:<br><INPUT type=text name=eta value=""><br> <INPUT type=SUBMIT value="Invia I dati"> </FORM> </body> </html>

Nella Figura 129 viene riportato il form cos come il browser lo presenta allutente. Dopo aver inserito i valori nei tre campi di testo, la pressione del pulsante provocher linvio da parte del client di una richiesta HTTP identificata dalla URL seguente:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

337

http://www.javanet.it/Prova?param=nascosto&nome=nomeutente&cognome=cognomeutente &eta=etautente

16.14 I limiti del protocollo HTTP: cookies


Il limite maggiore del protocollo HTTP legato alla sua natura di protocollo non transazionale, ovvero non in grado di mantenere dati persistenti tra i vari pacchetti HTTP. Fortunatamente esistono due tecniche per aggirare il problema: i cookies e la gestione di sessioni utente. I cookies sono piccoli file contenenti una informazione, scritta al loro interno secondo un certo formato, che vengono depositati dal server sul client e contengono informazioni specifiche relative alla applicazione che li genera. Se utilizzati correttamente, consentono di memorizzare dati utili alla gestione del flusso di informazioni, creando delle entit persistenti in grado di fornire un punto di appoggio per garantire un minimo di transazionalit alla applicazione Web Formalmente i cookie contengono, al loro interno, una singola informazione nella forma nome=valore, pi una serie di informazioni aggiuntive che rappresentano:

Il dominio applicativo del cookie; Il path della applicazione; La durata della validit del file; Un valore booleano che identifica se il cookie criptato o no.
Il dominio applicativo del cookie consente al browser di determinare se, al momento di inviare una richiesta HTTP, dovr associarle il cookie da inviare al server. Un valore del tipo www.java-net.it indicher al browser che il cookie sar valido solo per la macchina www allinterno del dominio java-net.it. Il path della applicazione rappresenta il percorso virtuale della applicazione per la quale il cookie valido. Un valore del tipo / indica al browser che qualunque richiesta HTTP da inviare al dominio definito nel campo precedente dovr essere associata al cookie. Un valore del tipo /servlet/ServletProva indicher invece al browser che il cookie dovr essere inviato in allegato a richieste HTTP del tipo http://www.java-net.it/servlt/ServletProva. La terza propriet, comunemente detta expiration, indica il tempo di durata della validit del cookie in secondi prima che il browser lo cancelli definitivamente. Mediante questo campo possibile definire cookie persistenti ossia senza data di scadenza.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

338

16.15 Manipolare cookies con le Servlet


Una servlet pu inviare uno o pi cookie ad un browser mediante il metodo addCookie(Cookie) definito nellinterfaccia HttpServletResponse, che consente di appendere lentit ad un messaggio di risposta al browser. Viceversa, i cookie associati ad una richiesta HTTP possono essere recuperati da una servlet utilizzando il metodo getCookie() definito nella interfaccia HttpServletRequest, che ritona un array di oggetti. Il cookie, in Java, viene rappresentato dalla classe javax.servlet.http.Cookie, il cui prototipo riportato nel codice seguente.
package javax.servlet.http; public class Cookie implements Cloneable { public Cookie(String name, String value); public String getComment() ; public String getDomain() ; public int getMaxAge(); public String getName(); public String getPath(); public boolean getSecure(); public String getValue(); public int getVersion(); public void setComment(String purpose); public void setDomain(String pattern); public void setMaxAge(int expiry); public void setPath(String uri); public void setSecure(boolean flag); public void setValue(String newValue); public void setVersion(int v); }

16.16 Un esempio completo


Nellesempio implementeremo una servlet che alla prima chiamata invia al server una serie di cookie, mentre per ogni chiamata successiva ne stampa semplicemente il valore contenuto.
import javax.servlet.* ; import javax.servlet.http.* ; import java.io.* ; public class TestCookie extends HttpServlet { private int numrichiesta=0; public void service (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { Cookie cookies = null; res.setContentType("text/html"); PrintWriter out = res. getWriter ();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

339

out.println("<html>"); out.println("<head><title>Cookie Test</title></head>"); out.println("<body>"); switch(numrichiesta) { case 0: //Appende 10 cookie alla risposta http for (int i=0; i<10; i++) { String nome="cookie"+i; String valore="valore"+i; cookies = new Cookie(nome,valore); cookies.setMaxAge(1000); res.addCookie(cookies); } out.println("<h1>I cookie sono stati appesi a questa risposta<h1>"); numrichiesta++; break; default : //ricavo l'array dei cookie e stampo le //coppie nome=valore Cookie cookies[] = req.getCookies(); for (int j=0; j<cookies.length; j++) { Cookie appo = cookies[j]; out.println("<h1>"+appo.getName()+" = "+appo.getValue()+"<h1>"); } } out.println("</body></html>"); out.close(); } }

16.17 Sessioni utente


I cookie non sono uno strumento completo per memorizzare lo stato di una applicazione web. Di fatto, i cookie sono spesso considerati dallutente poco sicuri e pertanto non accettati dal browser, che li rifiuter al momento dellinvio con un messaggio di risposta. Come se non bastasse, il cookie ha un tempo massimo di durata prima di essere cancellato dalla macchina dellutente. Servlet risolvono questo problema mettendo a disposizione del programmatore la possibilit di racchiudere lattivit di un client per tutta la sua durata allinterno di sessioni utente. Una servlet pu utilizzare una sessione per memorizzare dati persistenti, dati specifici relativi alla applicazione e recuperarli in qualsiasi istante sia necessario. Questi dati possono essere inviati al client allinterno dellentit prodotta.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

340

Ogni volta che un client effettua una richiesta HTTP, se in precedenza stata definita una sessione utente legata al particolare client, il servlet container identifica il client e determina quale sessione stata associata ad esso. Nel cosa in cui la servlet la richieda, il container gli mette disposizione tutti gli strumenti per utilizzala. Ogni sessione creata dal container associata in modo univoco ad un identificativo o ID. Due sessioni non possono essere associate ad uno stesso ID.

16.18 Sessioni dal punto di vista di una servlet


Il servlet container contiene le istanze delle sessioni utente rendendole disponibili ad ogni servlet che ne faccia richiesta (Figura 130).

Figura 130:

Le sessioni utente sono accessibili da tutte le servlet

Ricevendo un identificativo dal client, una servlet pu accedere alla sessione associata allutente. Esistono molti modi per consentire al client di tracciare lidentificativo di una sessione, tipicamente viene utilizzato il meccanismo dei cookie persistenti. Ogni volta che un client esegue una richiesta HTTP, il cookie contenente lidentificativo della richiesta viene trasmesso al server che ne ricava il valore contenuto e lo mette a disposizione delle servlet. Nel caso in cui il browser non consenta lutilizzo di cookies esistono tecniche alternative che risolvono il problema.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

341

Tipicamente una sessione utente deve essere avviata da una servlet dopo aver verificato se gi non ne esista una utilizzando il metodo getSession(boolean) di HttpServletRequest. Se il valore boolean passato al metodo vale true ed esiste gi una sessione associata allutente, il metodo semplicemente ritorner un oggetto che la rappresenta, altrimenti ne creer una ritornandola come oggetto di ritorno del metodo. Al contrario, se il parametro di input vale false, il metodo torner la sessione se gi esistente null altrimenti. Quando una servlet crea una nuova sessione, il container genera automaticamente lidentificativo ed appende un cookie contenente l ID alla risposta HTTP in modo del tutto trasparente alla servlet.

16.19 La classe HttpSession


Un oggetto di tipo httpSession viene restituito alla servlet come parametro di ritorno del metodo getSession(boolean) e viene definito dallinterfaccia javax.servlet.http.HttpSession .
package javax.servlet.http; public interface HttpSession { long getCreationTime(); String getId(); long getLastAccessedTime(); int getMaxInactiveInterval(); HttpSessionContext getSessionContext(); Object getValue(String name); String[] getValueNames(); void invalidate(); boolean isNew(); void putValue(String name, Object value); void removeValue(String name); int setMaxInactiveInterval(int interval); }

Utilizzando i metodi

String getId() long getCreationTime() long getLastAccessedTime() int getMaxInactiveInterval() boolean isNew()
possiamo ottenere le meta-informazioni relative alla sessione che stiamo manipolando. E importante notare che i metodi che ritornano un valore che rappresenta un tempo rappresentano il dato in secondi. Sar quindi necessario operare le necessarie conversioni per determinare informazioni tipo data ed ora. Il significato dei metodi descritti risulta chiaro leggendo il

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

342

nome del metodo. Il primo ritorna lidentificativo della sessione, il secondo il tempo in secondi trascorso dalla creazione della sessione, il terzo il tempo in secondi trascorso dallultimo accesso alla sezione, infine il quarto lintervallo massimo di tempo di inattivit della sessione. Quando creiamo una sessione utente, loggetto generato verr messo in uno stato di NEW che sta ad indicare che la sessione stata creata ma non attiva. Dal un punto di vista puramente formale ovvio che per essere attiva la sessione deve essere accettata dal client, ovvero una sessione viene accettata dal client solo nel momento in cui invia al server per la prima volta lidentificativo della sessione. Il metodo isNew() restituisce un valore booleano che indica lo stato della sessione. I metodi

void putValue(String name, Object value) void removeValue(String name) String[] getValueNames(String name) Object getValue(String name)
Consentono di memorizzare o rimuovere oggetti nella forma di coppie nome=oggetto allinterno della sessione, consentendo ad una servlet di memorizzare dati (in forma di oggetti) allinterno della sessione per poi utilizzarli ad ogni richiesta HTTP da parte dellutente associato alla sessione. Questi oggetti saranno inoltre accessibili ad ogni servlet che utilizzi la stessa sessione utente potendoli recuperare conoscendo il nome associato.

16.20 Un esempio di gestione di una sessione utente


Nel nostro esempio creeremo una servlet che conta il numero di accessi che un determinato utente ha effettuato sul sistema, utilizzando il meccanismo delle sessioni.
import javax.servlet.* ; import javax.servlet.http.* ; import java.io.* ; public class TestSession extends HttpServlet { public void service (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res. getWriter (); out.println("<html>"); out.println("<head><title>Test di una sessione servlet</title></head>"); out.println("<body>"); HttpSession sessione = req.getSession(true); if(session.isNew())

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

343

{ out.println("<strong>Id della sessione: </strong>" +session.getId()+"<br>"); out.println("<strong>Creata al tempo: </strong>" +session.creationTime()+"<br>"); out.println("<strong>Questa la tua prima +connessione al server </strong>"); session.putValue("ACCESSI", new Integer(1)); } else { int accessi = ((Integer)session.getValue("ACCESSI")).intValue(); accessi++; session.putValue("ACCESSI", new Integer(accessi)); out.println("<strong>Questa la tua connessione numero: </strong>" +accessi); } out.println("</body></html>"); out.close(); } }

16.21 Durata di una sessione utente


Una sessione utente rappresenta un oggetto transiente la cui durata deve essere limitata al periodi di attivit dellutente sul server. Utilizzando i metodi

void invalidate(); int setMaxInactiveInterval(int interval)


possibile invalidare una sessione o disporre che. una volta superato lintervallo massimo di inattivit, la servlet venga automaticamente resa inattiva dal container.

16.22 URL rewriting


Il browser ha la facolt di accettare o meno cookies da parte di un server Web. Quando ci accade impossibile, per il server, tracciare lidentificativo della sessione e di conseguenza mettere in grado una servlet di accederle. Un metodo alternativo quello di codificare lidentificativo della sessione allinterno della URL che il browser invia al server allinterno di una richiesta HTTP. Questa metodologia deve necessariamente essere supportata dal server, che dovr prevedere lutilizzo di caratteri speciali allinterno della URL. Server differenti potrebbero utilizzare metodi differenti. Linterfaccia HttpServletResponse ci mette a disposizione il metodo encodeURL(String) che prende come parametro di input una URL, determina se necessario riscriverla ed eventualmente codifica allinterno della URL lidentificativo della sessione.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

344

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

345

17
17.1 Introduzione

JAVASERVER PAGES

La tecnologia JavaServer Pages rappresenta un ottimo strumento per scrivere pagine web dinamiche ed insieme a servlet consente di separare le logiche di business della applicazione Web (servlet) dalle logiche di presentazione. Basato su Java, JavaServer Pages sposano il modello write once run anywhere, consentono facilmente luso di classi Java, di JavaBeans o laccesso ad Enterprise JavaBeans.

17.2 JavaServer Pages


Una pagina JSP un semplice file di testo che fonde codice HTML e codice Java, a formare una pagina dai contenuti dinamici. La possibilit di fondere codice HTML con codice Java, senza che nessuno interferisca con laltro consente di isolare la rappresentazione dei contenuti dinamici dalle logiche di presentazione. Il disegnatore potr concentrarsi solo sulla impaginazione dei contenuti, i quali saranno inseriti dal programmatore che non dovr preoccuparsi dellaspetto puramente grafico. Da sole, JavaServer Pages consentono di realizzare applicazioni Web dinamiche (Figura 131) accedendo a componenti Java contenenti logiche di business o alla base dati del sistema. In questo modello, il browser accede direttamente ad una pagina JSP che riceve i dati di input, li processa utilizzando eventualmente oggetti Java, si connette alla base dati effettuando le operazioni necessarie e ritorna al client la pagina HTML prodotta come risultato della processazione dei dati.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

346

Figura 131:

Primo modello di sistema

Il secondo modello, e forse il pi comune, utilizza JavaServer Pages come strumento per sviluppare template, demandando completamente a servlet la processazione dei dati di input (Figura 132).

Figura 132:

Secondo modello di sistema

In questo nuovo modello, il browser invia la sua richiesta ad una servlet che si preoccuper di processare i dati di input, utilizzando eventualmente JDBC o interrogando speciali classi delegate al collegamento con la base dati del sistema. La servlet ora generer alcuni oggetti (non pi pagine HTML) come prodotto della esecuzione del metodo service(). Utilizzando gli strumenti messi a disposizione dal container, la servlet potr inviare gli oggetti prodotti ad una pagina JavaServer Pages che si preoccuper solamente di ricavare il contenuto di questi oggetti inserendoli allinterno del codice HTML. Sara infine la pagina JSP ad inviare al client la pagina prodotta.

17.3 Compilazione di una pagina JSP


Se dal punto di vista del programmatore una pagina JSP un documento di testo contenente tag HTML e codice Java, dal punto di vista del server una pagina JSP utilizzata allo stesso modo di una servlet. Di fatto, nel momento del primo accesso da parte dellutente, la pagina JSP richiesta viene trasformata in un file Java e compilata dal compilatore interno della virtual machine. Come prodotto della compilazione otterremo una classe Java che

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

347

rappresenta una servlet di tipo HttpServlet che crea una pagina HTML e la invia al client. Tipicamente il Web server memorizza su disco tutte le definizioni di classe ottenute dal processo di compilazione appena descritto per poter riutilizzare il codice gi compilato. Lunica volta che una pagina JSP viene compilata al momento del suo primo accesso da parte di un client o dopo modifiche apportate dal programmatore affinch il client acceda sempre alla ultima versione prodotta.

17.4 Scrivere pagine JSP


Una pagina JSP deve essere memorizzata allinterno di un file di testo con estensione .jsp . E questa lestensione che il Web server riconosce per decidere se compilare o no il documento. Tutte le altre estensioni verranno ignorate. Ecco quindi un primo esempio di pagina JSP:
<html> <body> <h1> Informazioni sulla richiesta http </h1> <br> Metodo richiesto : <%= request.getMethod() %> <br> URI : <%= request.getRequestURI() %> <br> Protocollo : <%= request.getProtocol() %> <br> <body> </html>

Una pagina JSP, come si vede chiaramente dallesempio, per la maggior parte formata da codice HTML con in pi un piccolo insieme di tag addizionali. Nel momento in cui avviene la compilazione della pagina il codice HTML viene racchiuso in istruzioni di tipo out.println(codicehtml) mentre il codice contenuto allinterno dei tag aggiuntivi viene utilizzato come codice eseguibile. Leffetto prodotto sar, comunque, quello di inserire allinterno del codice HTML valori prodotti dalla esecuzione di codice Java. I tag aggiuntivi rispetto a quelli definiti da HTML per scrivere pagine jsp sono tre: espressioni, scriptlet, dichiarazioni. Le espressioni iniziano con la sequenza di caratteri <%= e terminano con la sequenza %>, le istruzioni contenute non devono terminare con il carattere ; e devono ritornare un valore che pu essere promosso a stringa. Ad esempio una espressione JSP la riga di codice:
<%= new Integer(5).toString() %>

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

348

Le scriptlet iniziano con la sequenza <%, terminano con la sequenza %> e devono contenere codice Java valido. Allinterno di questi tag non si possono scrivere definizioni di classi o di metodi, ma consentono di dichiarare variabili visibili allinterno di tutta la pagina JSP. Una caratteristica importante di questi tag che il codice Java scritto allinterno non deve essere completo, ovvero possibile fondere blocchi di codice Java allinterno di questi tag con blocchi di codice HTML. Ad esempio:
<% for(int i=0; i<10; i++) { %> <strong> Il valore di I : <%= new Integer(i).toString() %> </strong> <% } %>

Infine le dichiarazioni iniziano con la sequenza <%! e terminano con la sequenza %> e possono contenere dichiarazioni di classi o di metodi utilizzabili solo allinterno della pagina, e a differenza del caso precedente, devono essere completi. Nellesempio utilizziamo questi tag per definire un metodo StampaData() che ritorna la data attuale in formato di stringa:
<%! String StampaData() { return new Date().toString(); } %>

Nel prossimo esempio di pagina jsp, allinterno di un loop di dieci cicli, effettuiamo un controllo sulla variabile intera che definisce il contatore del ciclo. Se la variabile pari stampiamo il messaggio Pari, altrimenti il messaggio Dispari.
<html> <body> <% for(int i=0; i<10; i++) { if(i%2==0) { %> <h1>Pari</h1> <% } else { %> <h2>Dispari</h2> <% } } %> <body> </html>

Laspetto pi interessante del codice nellesempio proprio quello relativo alla possibilit di spezzare il sorgente Java contenuto delle scriptlets per dar modo al programmatore di non dover fondere tag HTML allinterno del codice sorgente Java.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

349

17.5 Invocare una pagina JSP da una servlet


Nel secondo modello di accesso ad una applicazione Web trattato nel paragrafo 2 di questo capitolo, abbiamo definito una pagina jsp come template ossia meccanismo di presentazione dei contenuti generati mediante una servlet. Affinch sia possibile implementare quanto detto, necessario che esista un meccanismo che consenta ad una servlet di trasmettere dati prodotti alla pagina JSP. Sono necessarie alcune considerazioni:

1. una pagina JSP viene tradotta una in classe Java di tipo HttpServlet e di conseguenza compilata, ed eseguita, allinterno dello stesso container che fornisce lambiente alle servlet; 2. JSP ereditano quindi tutti i meccanismi che il container mette a disposizione delle servlet, compreso quello delle sessioni utente.
Lo strumento messo a disposizione dal container per rigirare una chiamata da una servlet ad una JavaServer Pages, passando eventualmente parametri, loggetto RequestDispatcher restituito dal metodo

public ServletContext getRequestDispatcher(String Servlet_or_JSP_RelativeUrl)


definito nella interfaccia javax.servlet.ServletContext.
package javax.servlet; public interface RequestDispatcher { public void forward(ServletRequest req, ServletResponse res); public void include(ServletRequest req, ServletResponse res); }

tramite il metodo forward(ServletRequest req, ServletResponse res) possibile reindirizzare la chiamata alla Servlet, o JavaServer Page, come indicato dalla URL definita dal parametro di input di getRequestDispatcher. Il metodo public void include(ServletRequest req, ServletResponse res), pur producendo a sua volta un reindirizzamento, inserisce il risultato prodotto dalla entit richiamata allinterno del risultato prodotto dalla servlet che ha richiamato il metodo. Nellesempio viene definita una servlet che trasferisce tramite requestDispatcher la richiesta HTTP alla pagina jsp /appo.jsp memorizzata nella root del Web server.
public class Redirect extends HttpServlet {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

350

public void service (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { ServletContext contesto = getServletContext(); RequestDispatcher rd = contesto.getRequestDispatcher(/appo.jsp); try { rd.forward(req, res); } catch(ServletException e) { System.out.println(e.toString()); } } }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

351

18
18.1 Introduzione

JAVASERVER PAGES: NOZIONI AVANZATE

I tag JSP possono essere rappresentati in due modi differenti: Short-Hand ed XML equivalent. Ogni forma delle due prevede i seguenti tag aggiuntivi rispetto ad HTML: TAG JSP Short Hand <% codice java %>

Tipo Scriptlet

Direttive Dichiarazione

<%@ tipo attributo %> <%! Dichiarazione %>

Espressione

<%= espressione %>

Azione

NA

XML <jsp :scriptlet> codice java </jsp :scriptlet> <jsp:directive.tipo attributo /> <jsp:decl> dichiarazione; </jsp:decl> <jsp:expr> espressione; </jsp:expr > <jsp:useBean .> <jsp:include .> <jsp:getProperty .> ecc.

18.2 Direttive
Le direttive forniscono informazioni aggiuntive sullambiente allinterno della quale la JavaServer Page in esecuzione. Le possibili direttive sono due: Page : informazioni sulla pagina Include File da includere nel documento e possono contenere gli attributi seguenti: Direttive JSP Descrizione Dichiara al server il linguaggio utilizzato allinterno della pagina JSP

Attributo e possibili valori language=Java

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

352

extends=package.class

import=package.*, package.class

session=true|false

buffer=none|8kb|dimensione

autoFlush=true|false

isThreadSafe=true|false

info=info_text

errorPage=error_url

isErrorPage=true|false contentType=ctinfo

Definisce la classe base a partire dalla quale viene definita la servlet al momento della compilazione. Generalmente non viene utilizzato. Simile alla direttiva import di una definizione di classe Java. Deve essere una delle prime direttive e comunque comparire prima di altri tag JSP. Di default, session vale true e significa che i dati appartenenti alla sessione utente sono disponibili dalla pagina JSP Determina se loutput stream della JavaServer Pages utilizza o no un buffer di scrittura dei dati. Di default la dimensione di 8k. Questa direttiva va utilizzata affiancata dalla direttiva autoflush Se impostato a true , svuota il buffer di output quando risulta pieno invece di generare una eccezione Di default lattributo impostato a true e indica allambiente che il programmatore si preoccuper di gestire gli accessi concorrenti mediante blocchi sincronizzati. Se impostato a false, viene utilizzata di default linterfaccia SingleThreadModel in fase di compilazione. Fornisce informazioni sulla pagina che si sta accedendo attraverso il metodo Servlet.getServletInfo(). Fornisce il path alla pagina jsp che verr richiamata in automatico per gestire eccezioni che non vengono controllate allinterno della pagina attuale. Definisce la pagina come una una pagina di errore. Definisce il mime tipe della pagina prodotta dalla esecuzione.

Un esempio di blocco di direttive allinterno di una pagina JSP il seguente:


<%@ page language=Java session=true errorPage=/err.jsp %> <%@ page import= java.lang.*, java.io.*%> <%@ include file=headers/intestazione.html %>

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

353

18.3 Dichiarazioni
Una dichiarazione rappresenta, dal punto di vista del container che compila la JavaServer Page, il blocco di dichiarazione dei dati membro o dei metodi della classe Servlet generata. Per definire un blocco di dichiarazioni si utilizza il tag <%! Dichiarazione %>. Un esempio:
<%! String nome =mionome; int tipointero=1; private String stampaNome() { return nome; } %>

18.4 Scriptlets
Come gi definito nel capitolo precedente, le scriptlets rappresentano blocchi di codice java incompleti e consentono di fondere codice Java con codice HTML. Oltre a poter accedere a dati e metodi dichiarati allinterno di tag di dichiarazione, consente di accedere ad alcuni oggetti impliciti ereditati dallambiente servlet. Gli oggetti in questione sono sette e sono i seguenti:

1. request: rappresenta la richiesta del client 2. response: rappresenta la risposta del client 3. out: rappresenta lo stream di output html inviato come risposta al client 4. session: rappresenta la sessione utente 5. page: rappresenta la pagina JSP attuale 6. config: rappresenta i dettagli della configurazione del server 7 . pageContext: rappresenta un container per i metodi relativi alle servlet

18.5 Oggetti impliciti: request


Questo oggetto messo a disposizione della pagina JSP in maniera implicita e rappresenta la richiesta HTTP inviata dallutente, ovvero implementa linterfaccia javax.servlet.http.HttpServletRequest. Come tale questo oggetto mette a disposizione di una pagina JSP gli stessi metodi utilizzati allinterno di una servlet. Nellesempio seguente i metodi messi a disposizione vengono utilizzati implicitamente per generare una pagina HTTP che ritorna le informazioni relative alla richiesta:
<%@ page language=Java session=true %> <%@ page import= java.lang.*, java.io.*, javax.servlet.* , javax.servlet.http.*%>

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

354

<html> <head><title>Test</title></head> <body> <b>Carachter encoding</b> <%=request.getCharacterEncoding() %> <BR> <b>Mime tipe dei contenuti</b> <%=request.getContentType()%> <BR> <b>Protocollo</b> <%=request.getProtocol()%> <BR> <b>Posizione fisica:</b> <%=request.getRealPath()%> <BR> <b>Schema:</b> <%=request.getScheme()%> <BR> <b>Nome del server:</b> <%=request.getServerName()%> <BR> <b>Porta del server:</b> <%=request.getServerPort()%> <BR> </body></html>

18.6 Oggetti impliciti: response


Rappresenta la risposta HTTP che la pagina JSP invia al client ed implementa javax.servlet.http.HttpServletResponse. Compito dei metodi messi a disposizione da questo oggetto quello di inviare dati di risposta, aggiungere cookies, impostare headers HTTP e ridirezionare chiamate.

18.7 Oggetti impliciti : session


Rappresenta la sessione utente come definito per servlet. Anche questo metodo mette a disposizione gli stessi metodi di servlet e consente di accedere ai dati della sessione utente allinterno della pagina JSP.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

355

19
19.1 Introduzione

JDBC

JDBC rappresentano le API di J2EE per poter lavorare con database relazionali. Esse consentono al programmatore di inviare query ad un database relazionale, di effettuare delete o update dei dati allinterno di tabelle, di lanciare stored-procedure o di ottenere meta-informazioni relativamente al database o le entit che lo compongono. JDBC sono modellati a partire dallo standard ODBC di Microsoft basato, a sua volta, sulle specifiche X/Open CLI. La differenza tra le due tecnologie sta nel fatto che, mentre ODBC rappresenta una serie di C-Level API, JDBC fornisce uno strato di accesso verso database completo e soprattutto completamente ad oggetti.

19.2 Architettura di JDBC


Architetturalmente JDBC sono suddivisi in due strati principali: il primo che fornisce una interfaccia verso il programmatore, il secondo, di livello pi basso, che fornisce invece una serie di API per i produttori di drivers verso database relazionali e nasconde allutente i dettagli del driver in uso. Questa caratteristica rende la tecnologia indipendente rispetto al motore relazionale con cui il programmatore deve comunicare. I driver utilizzabili con JDBC sono di quattro tipi:

1. JDBC-ODBC bidge: bridge jdbc/odbc che utilizzano linterfaccia ODBC del client per connettersi alla base dati; 2 . JDBC Native Bridge:ha sempre funzioni di bridge (ponte), ma traduce le chiamate JDBC in chiamate di driver nativi gi esistenti sulla macchina; 3. JDBC Net Bridge: ha una architettura multi-tier ossia si connette ad un RDBMS tramite un middleware connesso fisicamente al database e con funzioni di proxy; 4. Driver JDBC Java: driver nativo verso il database, scritto in Java e compatibile con il modello definito da JDBC.
I driver JDBC, di qualsiasi tipo siano, vengono caricati dalla applicazione in modo dinamico mediante una istruzione del tipo Class.forName(package.class) . Una volta che il driver (classe java) viene caricato possibile connettersi al database tramite il driver manager utilizzando il metodo getConnection() che richiede in input la URL della base dati ed una serie di informazioni necessarie ad effettuare la connessione. Il formato con cui devono essere passate queste informazioni dipende dal produttore dei driver.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

356

Figura 133:

Architettura JDBC

19.3 Driver di tipo 1


Rappresentati dal bridge jdbc/odbc di SUN, utilizzano linterfaccia ODBC del client per connettersi alla base dati (Figura 134). Questo tipo di driver sono difficili da amministrare e dipendono dallimplemetazione ODBC e dalle piattaforme Microsoft. Il fatto di utilizzare meccanismi intermedi per connettersi alla base dati (lo strato ODBC) fa si che non rappresentino la soluzione ottima per tutte le piattaforme in cui le prestazioni del sistema rappresentano laspetto critico (il mapping JDBC ODBC complesso ed oneroso in termini di prestazioni).

19.4 Driver di tipo 2


I driver di tipo 2 richiedono che sulla macchina client siano installati i driver nativi del database con cui lutente intende dialogare (Figura 135). Il driver JDBC convertir, quindi, le chiamate JDBC in chiamate compatibili con le API native del server. Luso di API scritte in codice nativo rende poco portabili le soluzioni basate su questo tipo di driver. I driver JDBC/ODBC possono essere visti come un caso specifico dei driver di tipo 2.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

357

Figura 134:

Driver JDBC Tipo 1

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

358

Figura 135:

Driver JDBC tipo 2

19.5 Driver di tipo 3


I driver di tipo 3 consentono di non utilizzare codice nativo sulla macchina client e quindi consentono di costruire applicazioni Java portabili. Formalmente i driver di tipo 3 sono rappresentati da oggetti che convertono le chiamate JDBC in un protocollo di rete e comunicano con una applicazione middleware chiamata server di rete. Compito del server di rete quello di rigirare le chiamate da JDBC instradandole verso il server database (Figura 136). Utilizzare architetture di questo tipo dette multi-tier comporta molti vantaggi soprattutto in quelle situazioni in cui un numero imprecisato di client deve connettersi ad una moltitudine di server database. In questi casi, infatti, tutti i client potranno parlare un unico protocollo di rete, quello conosciuto dal middleware, e sar compito del server di rete tradurre i pacchetti nel protocollo nativo del database con cui il client sta cercando di comunicare. Lutilizzo di server di rete consente inoltre di utilizzare politiche di tipo caching e pooling oppure di load balancing del carico.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

359

Figura 136:

Driver JDBC Tipo 3

19.6 Driver di tipo 4


Un driver di tipo quattro fornisce accesso diretto ad un database ed un oggetto Java completamente serializzabile (Figura 137). Questo tipo di driver possono funzionare su tutte le piattaforme e dal momento che sono serializzabili possono essere scaricati dal server.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

360

Figura 137:

Driver JDBC Tipo 4

19.7 Una prima applicazione di esempio


Prima di scendere nei dettagli della tecnologia JDBC, ci soffermeremo su una prima applicazione di esempio. Lapplicazione usa driver JDBC di tipo 1 per connettersi ad un database access contenente una sola tabella chiamata impiegati, come mostrata nella Figura 138.

Figura 138:

Tabella Access

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

361

import java.sql.* ; public class EsempioJDBC { public static void main(String args[]) { try { Class.forname(sun.jdbc.odbc.JdbcOdbcDriver); } catch(ClassNotFoundException e) { System.out.println(e.toString()); System.out.println(Il driver non pu essere caricato); System.exit(1); } try { Connection conn = DriverManager.getConnection(jdbc:odbc:impiegati,,); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(SELECT NOME FROM IMPIEGATI); while(rs.next()) { System.out.println(rs.getString(NOME)); } rs.close(); stmt.close(); conn.close(); } catch(SQLException se){ System.out.println(se.getMessage()); se.printStackTrace(System.out); System.exit(1); } } }

Dopo aver caricato il driver JDBC di tipo 1 utilizzando il metodo statico forName(String) della classe java.lang.Class

Class.forName(sun.jdbc.odbc.JdbcOdbcDriver);
lapplicazione tenta la connessione al database utilizzando il metodo statico getConnection(String,String,String) delloggetto DriverManager definito nel package, passando come parametro una di tipo Stringa che rappresenta la URL della base dati con una serie di informazioni aggiuntive.

Connection conn = DriverManager.getConnection(jdbc:odbc:impiegati,,)

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

362

Nel caso in cui la connessione vada a buon fine, la chiamata al metodo di DriverManager ritorna un oggetto di tipo Connection definito nellinterfaccia java.sql.Connection. Mediante loggetto Connection creiamo quindi un oggetto di tipo java.sql.Statement che rappresenta la definizione uno statement SQL tramite il quale effettueremo la nostra query sul database.

Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(SELECT NOME FROM IMPIEGATI);


La nostra applicazione visualizzer infine il risultato della query mediante un ciclo while che scorre gli elementi di un oggetto ResultSet definito in java.sql.ResultSet e ritornato come parametro dalla esecuzione del metodo executeQuery(String) di java.sql.Statement.

19.8 Richiedere una connessione ad un database


Il primo passo da compiere quando utilizziamo un driver JDBC quello di tentare la connessione al database, sar quindi necessario che il driver sia caricato allinterno della virtual machine utilizzando il metodo statico forName(String) delloggetto Class definito nel package java.lang. Una volta caricato il driver, si registrer sul sistema come driver JDBC disponibile chiamando implicitamente il metodo statico registerDriver(Driver driver) della classe DriverManager. Questo meccanismo consente di caricare allinterno del gestore dei driver JDBC pi di un driver, per poi utilizzare quello necessario al momento della connessione che rappresenta il passo successivo da compiere. Per connetterci al database la classe DriverManager mette a disposizione il metodo statico getConnection() che prende in input tre stringhe e ritorna un oggetto di tipo java.sql.Connection , che da questo momento in poi rappresenter la connessione ad uno specifico database a seconda del driver JDBC richiamato:

Connection conn = DriverManager.getConnection(String URL, String user, String password);


Una URL JDBC identifica un database dal punto di vista del driver JDBC. Di fatto URL per driver differenti possono contenere informazioni differenti, tipicamente iniziano con la string jdbc, contengono indicazioni sul protocollo e informazioni aggiuntive per connettersi al database.

JDBC URL = jdbc:protocollo:other_info


Per driver JDBC di tipo 1 (bridge JDBC/ODBC) la JDBC URL diventa

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

363

JDBC/ODBC URL = jdbc:odbc:id_odbc


Dove id_odbc il nome ODBC definito dallutente ed associato ad una particolare connessione ODBC.

19.9 Eseguire query sul database


Dopo aver ottenuto la nostra sezione vorremmo poter eseguire delle query sulla base dati. E quindi necessario avere un meccanismo che ci consenta di effettuare operazioni di select, delete, update sulle tabelle della base dati. La via pi breve quella che utilizza loggetto java.sql.Statement che rappresenta una istruzione SQL da eseguire ed ritornato dal metodo createStatement() delloggetto java.sql.Connection.

Statement stmt = conn.createStatement();


I due metodi principali delloggetto Statement sono i metodi:

javax.sql.ResultSet executeQuery(String sql); int executeUpdate(String sql);


Il primo viene utilizzato per tutte quelle query che ritornano valori multipli (select) il secondo per tutte quelle query che non ritornano valori (update o delete). In particolar modo lesecuzione del primo metodo produce, come parametro di ritorno, un oggetto di tipo java.sql.ResultSet che rappresenta il risultato della query riga dopo riga. Questo oggetto legato allo statement che lo ha generato; se loggetto statement viene rilasciato mediante il metodo close(), anche il ResultSet generato non sar pi utilizzabile e viceversa fino a che un oggetto ResultSet non viene rilasciato mediante il suo metodo close() non sar possibile utilizzare Statement per inviare al database nuove query.

19.10 Loggetto ResultSet


Loggetto ResultSet rappresenta il risultato di una query come sequenza di record aventi colonne rappresentati dai nomi definiti allinterno della query. Ad esempio la query SELECT NOME, COGNOME FROM IMPIEGATI produrr un ResultSet contenente due colonne identificate, rispettivamente, dalla stringa NOME e dalla stringa COGNOME (Figura 139). Questi identificativi possono essere utilizzati con i metodi delloggetto per recuperare i valori trasportati come risultato della select. Nel caso in cui la query compaia nella forma SELECT * FROM IMPIEGATI le colonne del ResultSet saranno identificato de un numero intero a partire da 1 da sinistra verso destra (Figura 140).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

364

Figura 139:
import java.sql.* ;

Esempio di SELECT con attributi definiti

public class Esempio2JDBC { public static void main(String args[]) { try { Class.forName(sun.jdbc.odbc.JdbcOdbcDriver); } catch(ClassNotFoundException e) { System.out.println(e.toString()); System.out.println(Il driver non pu essere caricato); System.exit(1); } try { Connection conn = DriverManager.getConnection(jdbc:odbc:impiegati,,); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(SELECT NOME, COGNOME FROM IMPIEGATI); while(rs.next()) { System.out.println(rs.getString(NOME)); System.out.println(rs.getString(COGNOME)); } rs.close(); stmt.close(); conn.close();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

365

} catch(SQLException _sql) { System.out.println(se.getMessage()); Se.printStackTrace(System.out); System.exit(1); } } }

Figura 140:
import java.sql.* ;

Esempio di SELECT *.

public class Esempio3JDBC { public static void main(String args[]) { try { Class.forName(sun.jdbc.odbc.JdbcOdbcDriver); } catch(ClassNotFoundException e) { System.out.println(e.toString()); System.out.println(Il driver non pu essere caricato); System.exit(1); } try { Connection conn = DriverManager.getConnection(jdbc:odbc:impiegati,,);

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

366

Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(SELECT * FROM IMPIEGATI); while(rs.next()) { System.out.println(rs.getString(1)); System.out.println(rs.getString(2)); System.out.println(rs.getInt(3)); } rs.close(); stmt.close(); conn.close(); } catch(SQLException _sql) { System.out.println(se.getMessage()); Se.printStackTrace(System.out); System.exit(1); } } }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

367

20
20.1 Introduzione

REMOTE METHOD INVOCATION

La Remote Method Invocation (RMI) il primo strumento nativo Java per la creazione di software distribuito. La sua introduzione, risalente alla versione 1.1 del linguaggio, stata effettuata per risolvere il problema di far comunicare tra loro oggetti non residenti sulla stessa macchina. Prima dellintroduzione di RMI, il linguaggio Java metteva a disposizione, come unico strumento, la classica comunicazione basata sui socket. Questo meccanismo, derivante dalle architetture clientserver e pertanto funzionale e notevolmente conosciuto, mal si sposa con la filosofia Object Oriented, dal momento che, nel caso si voglia invocare un metodo di un oggetto remoto, lunico sistema quello di istaurare tra i due sistemi un protocollo applicativo di comunicazione basato su contenuti. Questo fatto accoppia notevolmente le implementazioni del client e del server, oltre al fatto che per gestire correttamente i socket necessaria una certa cautela. Il principale vantaggio di RMI risiede nella caratteristica di essere una implementazione 100% Java, e quindi utilizzabile da qualsiasi oggetto o componente scritto in Java, del pi noto e vasto capitolo delle architetture distribuite. La tecnologie introdotta in RMI, inoltre, rappresenta la base del paradigma Java per oggetti distribuiti, e pertanto utilizzata anche in altri elementi dellarchitettura J2EE, in particolare negli Enterprise JavaBeans. Il rovescio della medaglia rappresentato dal fatto che questo vantaggio si configura come un limite nei casi in cui sia necessario integrare oggetti distribuiti scritti in linguaggi arbitrari. Nella versione 1.3 del linguaggio Java RMI stato modificato al fine di integrare, come strumento di trasporto, il protocollo IIOP (Inter Internet ORB Protocol) nativo dellarchitettura CORBA e maggiormente aderente agli standard internazionali. Per questo motivo dalla versione 1.3 in poi RMI diventa RMI-IIOP. In questo capitolo verr analizzato RMI come strumento per la realizzazione di architetture distribuite e sar effettuata una rapida panoramica illustrativa sui principali aspetti.

20.2 Architetture distribuite


Il concetto di architettura distribuita basato sulla possibilit di invocare metodi di oggetti dislocati su una macchina qualsiasi presente in rete, come se tali oggetti fossero locali al sistema chiamante; ci possibile grazie alla separazione delle interfacce client e server, secondo lo schema di cui in figura seguente:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

368

Figura 141:

Schema base di architettura distribuita

Il paradigma si caratterizza per la distinzione tra oggetti (indicati, in figura, con client e server), contenenti la logica applicativa, e le interfacce che essi espongono verso i sistemi remoti. Lobiettivo quello di implementare la location trasparency, per cui il client non ha necessit di conoscere i dettagli implementativi delloggetto server, n la macchina ove risiede, n la piattaforma e neanche come raggiungerlo. Al client necessario conoscere solo il nome delloggetto da chiamare e la sua interfaccia, mentre i servizi di ricerca delloggetto fisico e di verifica della congruenza del protocollo sono delegati ad un apposito strato software, denominato middleware. In realt, per far s che tutto funzioni, le chiamate remote debbono essere, in qualche modo, strutturate secondo un modello comune. Linterfaccia remota del server inclusa nello stub che rappresenta, per il client, una finta istanza locale delloggetto remoto. Il client, grazie alla presenza dello stub, vede loggetto server come locale, e pertanto ne invoca i metodi pubblici definiti nella sua interfaccia. Lo stub si preoccupa di passare la chiamata sulla rete, dove viene intercettata dallo skeleton che si preoccupa di ricostruire la semantica della chiamata e passarla, per lesecuzione, alloggetto server vero e proprio. Sono, quindi, lo stub e lo skeleton ad effettuare il vero lavoro di disaccoppiamento e distribuzione dei sistemi, ma, per fortuna, tali elementi sono invisibili al programmatore, che non si deve preoccupare del loro

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

369

sviluppo; il middleware a crearli sulla base della definizione di interfaccia remota. Questo schema comune a tutti gli standard di architetture distribuite, compreso, ad esempio, CORBA. Come detto, la specifica J2EE per java 1.3 e successive versioni, utilizza limplementazione RMI-IIOP che utilizza come protocollo di trasporto lo standard CORBA.

Figura 142:

Ruolo del middleware in unarchitettura distribuita

20.3 RMI in dettaglio


Facendo riferimento allo schema di architettura distribuita definito precedentemente, RMI non che una delle possibili implementazioni, e sfrutta una serie di servizi (ed API) appositamente introdotte. Lo schema generale presentato nella prossima figura. Rispetto alle figure precedenti, nella figura 143 evidenziato il ruolo svolto da RMI come middleware per i servizi di naming e come elemento applicativo caratterizzante lo strato di trasporto (a livello di protocollo di rete, quello utilizzato sempre il TCP/IP).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

370

Figura 143:

Architettura di RMI

Per poter riconoscere un oggetto presente in un punto qualsiasi della rete, necessario che vi sia un servizio dedicato, contenente dei meccanismi per lindividuazione univoca delloggetto stesso. Questa esigenza nota in tutti i protocolli, e ciascun protocollo ha sviluppato i propri meccanismi per:

1. individuare univocamente i computer, attraverso formalismi standard riconosciuti dai dispositivi delegati a gestire linstradamento delle comunicazioni e a gestire le liste dei nomi 2. fornire informazioni per distinguere i vari servizi
Ad esempio, per individuare un computer su una rete il meccanismo pi semplice utilizzato nel protocollo TCP/IP quello basato sullindirizzo IP. Questo indirizzo individua i computer su scala gerarchica attraverso un indirizzo composto da quattro byte; per indicare comunemente questo indirizzo ogni byte viene separato dal successivo da un punto (ad esempio, 127.0.0.1). A questo primo livello di indirizzamento si aggiunto il servizio (offerto dai DNS - Domain Name Service) che individua il computer con un nome logico con il seguente modello:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

371

nomemacchina.nomesottodominio. .nomedominio.suffissoIP
Ad esempio, la macchina di nome antares sul dominio miodominio.it sar individuata dal nome antares.miodominio.it A questo nome possibile, secondo le specifiche TCP/IP, associare:

1. un suffisso indicante il servizio, cui corrisponde la porta sulla quale risponde lapplicativo; ad esempio, riferito al caso precedente, se abbiamo un servizio in ascolto sulla porta 5600 lindirizzo per interrogare quel servizio antares.miodominio.it:5600 2. unidentificativo del tipo di protocollo utilizzato a livello applicativo: ad esempio, nel caso del protocollo HTTP lidentificativo maggiormente usato WWW ed un valido indirizzo http://www.miodominio.it:8080; nel caso, invece, di protocollo FTP un indirizzo valido ftp://ftp.miodominio.it (in questo caso, non indicando la porta, si utilizza quella di default).
Questo modo di gestire i nomi consente di individuare in modo univoco qualsiasi host di rete, il suo servizio ed il protocollo applicativo, consentendo di implementare dei servizi di registrazione dei nomi e dei servizi, sia a livello di singolo computer sia di rete locale o internet. RMI definisce, in modo del tutto analogo agli esempi precedenti, il protocollo associato allindirizzo del computer da ricercare con la forma

protocol://nomecomputer:porta
e fornisce una utilit per registrare la lista degli oggetti che richiedono i servizi RMI attraverso il tool rmiregistry presente nel SDK.

20.4 Capire ed usare RMI


Al fine di comprendere meglio lutilizzo di RMI, opportuno avvalerci di un esempio. Per utilizzare in modo corretto RMI, la prima cosa da fare definire qual linterfaccia esportata dalloggetto che deve essere chiamato da remoto. Supponiamo, come esempio, di voler implementare la comunicazione fra due oggetti, di cui quello server supponiamo sia una libreria, che esporta un metodo che ritorna, a partire dal titolo di un libro, la lista degli autori. Il codice dellinterfaccia, che chiameremo Libreria, presentato nel listato seguente
import java.rmi.Remote;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

372

public interface Libreria extends Remote { public String nomiDegliAutori(String libro) throws java.rmi.RemoteException; }

Come si nota, linterfaccia estende una classe specifica di RMI, linterfaccia java.rmi.Remote. Tale interfaccia necessaria per definire le interfacce che possono essere invocate da una Java Virtual Machine non locale allimplementazione delloggetto. Il metodo invocabile da remoto, inoltre, deve necessariamente emettere una eccezione, la java.rmi.RemoteException, che la superclasse delle eccezioni del package java.rmi e comprende tutte le eccezioni che possono intervenire durante la comunicazione remota. La classe di gestione della libreria (che supponiamo contenere le istanze ai libri) implementa la suddetta interfaccia, ed il codice descritto di seguito.
import java.util.*; import java.rmi.server.UnicastRemoteObject; import java.rmi.RemoteException; public class LibreriaServer extends UnicastRemoteObject implements Libreria { Hashtable libri;

// Implementazione del metodo invocable da remoto public String nomiDegliAutori(String titolo) throws RemoteException{ String autori; autori = (String)libri.get(titolo); if (autori== null) return ("Libro non trovato"); else return autori; } // Costruttore della classe public LibreriaServer() throws RemoteException{ libri = new Hashtable(); libri.put("La Divina Commedia","Dante Alighieri"); libri.put("Il Decameron","Giovanni Boccaccio"); libri.put("I Promessi Sposi","Alessandro Manzoni"); }
public static void main(String[] args) { try { LibreriaServer rmiServer = new LibreriaServer();

// Con questo metodo vediamo qual il registro // degli oggetti della macchina locale Registry registroDeGliOggetti = LocateRegistry.getRegistry(); // Registro loggetto sul registro degli oggetti locale registroDeGliOggetti.rebind("LaMiaLibreria", rmiServer); System.out.println("Classe LibreriaServer registrata e pronta a ricevere
chiamate."); } catch (Exception e) { e.printStackTrace(); } }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

373

La classe LibreriaServer, per poter esser un oggetto invocabile da remoto via RMI, deve estendere la superclasse java.rmi.server.UnicastRemoteObject. Questa classe definisce il comportamento delle classi che la estendono in termini di referenza univoca (la referenza alloggetto unica e valida solo quando loggetto attivo) e possibilit di essere invocate secondo il modello point-to-point. Il metodo nomiDegliAutori corretto dal momento che ritorna un oggetto, String , che implementa in modo nativo linterfaccia java.io.Serializable, e questo garantisce la correttezza della trasmissione dei dati sullo stream TCP (di questo parleremo pi avanti). La classe LibreriaServer contiene anche un metodo main() utilizzato per registrare loggetto sul registry dei nomi. Per la comunicazione con il registry, viene utilizzata la classe java.rmi.Registry, ed i metodi per la registrazione delloggetto sono bind (con dichiarazione univoca della classe) e rebind (qualora si voglia dinamicamente modificare il registry). Entrambi i metodi hanno come parametri una stringa, che rappresenta il nome logico delloggetto da invocare (in questo caso, LaMiaLibreria), e la referenza alloggetto stesso. Il nome logico pu essere scelto arbitrariamente dallutente e rappresenta il nome logico del servizio cui cercher di connettersi il client. Qualche ulteriore osservazione sul metodo main:

1. il meccanismo di registrazione in esso dichiarato non deve risiedere necessariamente allinterno nella classe server, anzi, qualora si abbiano pi classi da registrare, possibile dichiararlo in unaltra classe che si occupi solo della registrazione degli oggetti sul server; 2. il secondo obiettivo perseguito dal meccanismo di registrazione) ed in particolare dai metodi bind) quello di mantenere vivo e disponibile il servizio per le chiamate: questo implica che la chiamata al bind mette il programma in uno stato di ascolto sulla porta definita dal registry (generalmente la 1099).
Il registry dei nomi fornito dal SDK la gi citata utilit rmiregistry, presente nella directory bin di installazione dellSDK, che andr lanciata prima di eseguire la classe LibreriaServer. Una volta scritta e compilata la classe, necessario generare i componenti Stub e Skeleton. Per questo compito presente, fra gli eseguibili del SDK, lutility rmic , la cui invocazione, per la classe LibreriaServer, genera i file LibreriaServer_Stub.class e LibreriaServer_Skel.class. A questo punto possibile scrivere il codice di un possibile programma client che, prelevando il nome del libro di cui cercare gli autori dalla riga di

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

374

comando, invoca in modo remoto il metodo nomiDegliAutori e stampa lautore.


import java.rmi.Naming; import Libreria; public class Client { public static void main(String[] args) { try { // Ricerca delloggetto LibreriaServer Libreria miaCopiaVirtualeOggettoServer= (Libreria)Naming.lookup("rmi://nomeDelServer/LaMiaLibreria"); String autori = miaCopiaVirtualeOggettoServer.nomiDegliAutori(args[0]);

// Stampa del risultato. System.out.println("Sulla libreria server gli autori del libro " + args[0] + " sono: " + autori);
} catch (Exception e) { System.out.println("Error while looking up account:"); e.printStackTrace(); } } }

Si noti, innanzitutto, come il client per referenziare ed utilizzare loggetto remoto non abbia bisogno del codice dello stesso, ma solo della sua interfaccia (import Libreria). La parte pi importante del codice quella della riga:
Libreria miaCopiaVirtualeOggettoServer = (Libreria) Naming.lookup("rmi://nomeDelServer/LaMiaLibreria");

La classe Client vede LibreriaServer mediante la sua interfaccia, e ne istanzia una copia virtuale (miaCopiaVirtualeOggettoServer) attraverso i servizi della classe java.rmi.Naming. Il metodo lookup(String) ritorna lo Stub delloggetto remoto, sulla base del nome dichiarato nella forma:

rmi://nomecomputer:porta/nomeOggettoRemoto
Loggetto ritornato dal metodo lookup un generico java.rmi.Remote e quindi, per poterlo utilizzare, necessario effettuarne il cast al tipo richiesto (nel caso in oggetto, Libreria). Dopo il casting, loggetto ottenuto miaCopiaVirtualeOggettoServer un riferimento locale alloggetto server la cui istanza risiede sulla macchina remota; di questo oggetto possono essere invocati i metodi esposti nella sua interfaccia (nel nostro caso il metodo nomiDegliAutori).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

375

20.5 Levoluzione in RMI-IIOP


Lesempio precedentemente esposto riferito alla specifica 1.1 di Java (quindi RMI); per poterlo far evolvere verso la 1.2 e successive (RMI-IIOP) necessario effettuare alcune modifiche. Per quanto riguarda la definizione dellinterfaccia non ci sono modifiche. Di seguito presentato il listato di LibreriaServer.java, ove in grassetto sono evidenziate le modifiche effettuate:

import java.util.*; import javax.rmi.PortableRemoteObject; import javax.naming.*; import java.rmi.RemoteException; import java.rmi.registry.*; public class LibreriaServer extends PortableRemoteObject implements Libreria { Hashtable libri; public String nomiDegliAutori(String titolo) throws RemoteException{ String autori; autori = (String)libri.get(titolo); if (autori== null) return ("Libro non trovato"); else return autori; } public LibreriaServer() throws RemoteException{ libri = new Hashtable(); libri.put("La Divina Commedia","Dante Alighieri"); libri.put("Il Decameron","Giovanni Boccaccio"); libri.put("I Promessi Sposi","Alessandro Manzoni"); } public static void main(String[] args) { try { LibreriaServer rmiServer = new LibreriaServer();

// Otteniamo la referenza ad una istanza IIOP via JNDI Hashtable props = new Hashtable(); //definiamo le propriet per la creazione del Context props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory"); props.put(Context.PROVIDER_URL, "iiop://localhost:900"); Context ctx = new InitialContext(props);

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

376

ctx.rebind("LaMiaLibreria", rmiServer ); System.out.println("Classe LibreriaServer registrata e pronta a ricevere chiamate."); } catch (Exception e) { e.printStackTrace(); } } }

Innanzitutto la superclasse da estendere, per poter generare un oggetto invocabile da remoto, non pi UnicastRemoteObject ma la PortableRemoteObject del package javax.rmi. La modifica maggiore, in questo caso, risiede nella modalit di registrazione del servizio al registry; viene creata una Hashtable, contentente una lista di propriet, da passare al costruttore della classe Context. I servizi JNDI (Java Naming & Directory Interface) associano nomi ad oggetti, e la classe Context definisce il contenitore delle associazioni utilizzato. Fra le propriet da specificare per inizializzare correttamente il Context, necessario definire nella propriet Context.INITIAL_CONTEXT_FACTORY qual limplemetazione (JNDI definisce solo interfacce) della classe che si occupa di offrire i servizi di naming. In questo caso la classe specificata com.sun.jndi.cosnaming.CNCtxFactory, che la classe definita nellSDK J2EE; nel caso si voglia utilizzare un altro strumento di registry collegato ad un ORB proprietario, necessario specificare qui la classe opportuna. Laltra propriet, Context.PROVIDER_URL , definisce qual lindirizzo del computer ove risiede il gestore dei servizi di registrazione. Si noti come, in questo caso, il protocollo di trasporto non sia pi RMI ma IIOP, che implica una modifica anche del gestore dei servizi di registry che deve esser conforme allo standard CORBA. Nel SDK lutility di gestione, che sostituisce la rmiregistry della versione 1.1, tnameserv, la cui porta di default la 900. Lesecuzione della utility tnameserv produce loutput in figura seguente:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

377

Figura 144:

Lesecuzione della utility tnameserv

Lesecuzione della classe LibreriaServer, che registra la classe sul registry server e la mette in attesa di chiamate, produce il seguente output:

Figura 145:

Esecuzione della classe LibreriaServer

Vediamo il codice del client:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

378

import javax.naming.*; import java.util.*; import javax.rmi.PortableRemoteObject; public class Client { public static void main(String[] args) { try { // inizializzazione del contesto Hashtable props = new Hashtable(); props.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.cosnaming. CNCtxFactory"); props.put(Context.PROVIDER_URL, "iiop://localhost:900"); Context ctx = new InitialContext(props);

// Otteniamo un riferimento al contesto in cui presente loggetto server Object riferimento = ctx.lookup("LaMiaLibreria"); // ricerca e allocazione del finto oggetto server Libreria mioLinkOggettoServer = (Libreria)PortableRemoteObject.narrow(riferimento,Libreria.class);
String autori = mioLinkOggettoServer.nomiDegliAutori(args[0]);

// Stampa del risultato. System.out.println("Sulla libreria server lautore del libro " + args[0] + " e:\n\t" +
autori); } catch (Exception e) { e.printStackTrace(); } } }

Come risulta dallanalisi delle righe in grassetto, la creazione del Context analoga a quanto visto per il metodo main della classe LibreriaServer. Il primo elemento di novit rappresentato dalla creazione di un oggetto che fa da riferimento generico alloggetto remoto, attraverso il metodo ctx.lookup(String). In questa chiamata viene effettuata la ricerca delloggetto remoto ma, diversamente da quando visto nel caso Java 1.1, viene restituito un generico oggetto che fa riferimento al contesto in cui contenuta listanza remota. Il vero riferimento alloggetto remoto definito nellistruzione successiva, in cui viene utilizzato (in modo analogo a quanto visto con loggetto Naming nel caso Java 1.1) il metodo narrow delloggetto PortableRemoteObject. Loutput prodotto dallesecuzione di Client.java presentato nella figura seguente:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

379

Figura 146:

esecuzione di Client.java

20.6 Serializzazione
Uno degli aspetti cruciali quando si lavora con oggetti distribuiti la garanzia che le comunicazioni fra i vari oggetti avvengano in modo corretto, ossia senza che la distribuzione su computer differenti ne infici la funzionalit. I metodi delle classi, infatti, hanno spesso come argomenti, o come valori di ritorno, dati che, in linguaggio Java, sono a loro volta classi; ad esempio, per comunicare una stringa di caratteri ad un metodo il modo pi intuitivo quello di mettere tra i parametri di input del metodo un dato di tipo java.lang.String. Cosa succede alloggetto, passato come valore al metodo o ritornato dallo stesso, qualora loggetto chiamante sia una classe remota? Il protocollo di comunicazione (TCP/IP o UDP) non conosce la struttura delle classi Java: il suo compito si esaurisce nel trasportare blocchi di byte sulla rete secondo il proprio standard. Ogni oggetto, pertanto, quando viene trasmesso sulla rete viaggia secondo il formato binario cos come qualsiasi altro tipo di dato primitivo. Per far in modo che la struttura delloggetto rimanga valida (ed utilizzabile) durante una trasmissione, necessario che loggetto venga convertito in modo opportuno in uno stream di byte, ed analogamente possa essere ricostruito dal ricevente. Per gestire semplicemente questo processo ci viene in aiuto linterfaccia java.io.Serializable. Questa interfaccia vuota, quindi le classi che la implementano non debbono implementare alcun metodo in pi, ma ha il compito di comunicare al compilatore java la direttiva su come costruirne una

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

380

rappresentazione binaria adatta al trasporto su uno stream (in pratica, che loggetto in questione deve essere trattato in modo da poter viaggiare su stream in modo sicuro). E utile aggiungere alcune importanti considerazioni:

1 . qualsiasi oggetto da far passare su uno stream deve essere serializzabile. Se una classe non implementa linterfaccia Serializable (o non estende una classe a sua volta serializzabile) evitate di effettuarne la trasmissione di tale classe: non riuscirete a ricostruire loggetto; 2 . non esiste una eccezione specifica per segnalare un errore sulla serializzazione: la trasmissione di un oggetto non serializzato comporta errori a runtime nel momento in cui si tenta di utilizzare loggetto stesso; 3. la trasmissione di cui si parla relativa alla scrittura su qualsiasi tipo di stream (non solo quella su socket): anche se volete salvare una classe su un file, loggetto, per poter essere ricostruito, deve essere serializzabile.

20.7 Marshalling di oggetti


Il termine marshalling viene normalmente utilizzato per definire il processo di conversione di dati tra piattaforme differenti. Quando, infatti, lo scambio di dati avviene tra computer aventi differenti piattaforme software (sistemi operativi, ma anche bus CORBA, ad esempio), pu accadere che i sistemi abbiano diversi modi per trattare determinate categorie di dati. Questo problema viene parzialmente risolto da Java considerando che i tipi di dati primitivi hanno lunghezza standard per tutte le piattaforme. Questo, per, non risolve tutti i problemi dal momento che:

1. anche se gli oggetti sono identici, la rappresentazione binaria per la trasmissione, su piattaforme differenti, pu essere diversa (es. bigendian e little-endian) 2. la rappresentazione in binario di oggetti generici pu essere fatta in modo differente da piattaforme diverse; 3. a volte vengono effettuate delle operazioni di casting forzato, che su un programma stand-alone possono risultare indolori, ma su programmi distribuiti generano errori. Se, ad esempio, su una interfaccia remota ho un certo tipo come definito per il passaggio di parametri, se tale tipo viene forzato il programma potrebbe non funzionare bene.
I processi di marshalling e unmarshalling vertono sulla conversione degli oggetti nella loro rappresentazione binaria (insieme alla serializzazione), e

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

381

vengono effettuati dallo Stub e dallo Skeleton, sulla base della definizione della interfaccia remota. In pratica, stub e skeleton si occupano di preparare gli oggetti al loro invio su rete (ovvero alla loro ricostruzione dalla rappresentazione binaria) basandosi sulla definizione dei tipi presente sullinterfaccia. Se il processo di marshalling (o quello di unmarshalling) fallisce, la JVM emette a run-time una speciale eccezione, di tipo MarshalException.

20.8 Attivazione dinamica di oggetti


Nellesempio precedente abbiamo visto come loggetto remoto si registri al registry server e rimanga disponibile per rispondere alle chiamate dei client remoti. Questa procedura, tuttavia, pu risultare onerosa dal punto di vista delluso di risorse qualora diventino numerose le istanze di oggetti da mantenere disponibili in memoria. Per risolvere questo problema, con la versione 1.2 di RMI stata introdotta anche la possibilit di istanziare dinamicamente gli oggetti in base alle richieste dei client, delegando le logiche di esecuzione dei processi ad un agente esterno. Questo processo detto attivazione e lagente un daemon che ha il compito di intercettare le chiamate dei client ed istanziare gli oggetti; il daemon fornito nellSDK lutility rmid. Senza entrare nei dettagli implementativi, per poter essere attivati su richiesta, gli oggetti server, per, debbono avere speciali caratteristiche. In particolare, essi debbono estendere la superclasse java.rmi.activation.Activable, ed implementare uno speciale costruttore, che viene invocato dal daemon, per lallocazione dinamica delloggetto.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

382

21
21.1 Introduzione

ENTERPRISE JAVA BEANS

Come anticipato nei precedenti capitoli, gli Enterprise JavaBeans (EJB) rappresentano uno degli elementi principali dellarchitettura J2EE. Si potrebbe, anzi, dire che siano, in realt, il vero elemento di novit dellarchitettura software, e ne costituiscono la sintesi suprema. Gli EJB non sono una nuova tecnica di programmazione o un set di API da utilizzare, sono uno strumento potentissimo il cui corretto uso prevede un impatto forte sullintero ciclo di vita del software, con conseguente ripensamento delle normali tecniche di analisi, progettazione, sviluppo, messa in esercizio e manutenzione evolutiva. Queste caratteristiche di sintesi fanno s che questi elementi siano, in verit, molto complessi da comprendere; per questo motivo i paragrafi che seguono cercheranno di tradurre in un linguaggio per quanto possibile semplice i principali aspetti legati a questa tecnologia. Non nostra intenzione esplorare interamente la complessit dellargomento in questo libro, per chi fosse interessato ad approfondire le proprie conoscenze in materia consigliamo di seguire i riferimenti presenti nella bibliografia.

21.2 Gli EJB: concetti base


Il contesto di riferimento per lutilizzo degli EJB sono gli ambienti complessi con architetture distribuite. In passato gli ambienti software complessi, tipici delle grandi organizzazioni, venivano sviluppati in modo disorganico, frutto della divisione funzionale dei task e dei progetti, e sviluppati in maniera eterogenea, utilizzando le tecniche che venivano ritenute, di volta in volta, pi opportune per la singola funzionalit. Il risultato di questo processo era, nella gran parte dei casi, una diabolica alchimia di sistemi, ciascuno dei quali sviluppato con logiche, metodologie e tecnologie ad hoc che, preso singolarmente, assolveva ai propri compiti, ma senza una visione dinsieme. In tale contesto, nei casi in cui emergeva la necessit di integrare sistemi funzionalmente separati, il compito del system integrator risultava il pi delle volte assai complesso, dovendo far parlare tra loro sistemi con architetture diverse, spesso scritti in linguaggi diversi e magari sviluppati su piattaforme differenti! La necessit di raggiungere lo scopo finiva, spesso, per generare un nuovo sistema, diverso da tutti quelli esistenti e da quelli da integrare, in architettura proprietarie e da mantenere e controllare separatamente, con tutti i problemi di esercizio connessi.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

383

Questa situazione vera ancora oggi in moltissime grandi organizzazioni (e, per inciso, non potrebbe essere altrimenti, dal momento che lo sviluppo dei sistemi informativi va avanti oramai da molti anni), ma sta emergendo la necessit di standard architetturali che semplifichino il processo di integrazione dei sistemi in ambienti eterogenei. Larchitettura J2EE (e, con gli opportuni distinguo che vedremo in seguito, i Web Services) vuole rappresentare, in questo senso, uno standard per lintegrazione dei sistemi, con gli EJB come passepartout per aprire ogni sistema da integrare. Il primo paradigma su cui si basa, utilizzato anche in RMI e CORBA, quello delle architetture distribuite, di cui stata data una breve spiegazione nel capitolo dedicato ad RMI. Il secondo, fondamentale, concetto su cui si basano gli EJB quello di componente: nellottica di decomporre un problema secondo la logica del divide et impera (non secondo unottica funzionale, ma Object Oriented), un componente un elemento software (un frammento di codice) che implementa la logica applicativa definita da un insieme di interfacce. Quello di componente un concetto astratto, non facile da definire, e per questo verr approfondito nel paragrafo successivo. Altri importanti elementi su cui si basa larchitettura EJB sono relativi ai concetti di controllo transazionale, load balancing, ciclo di vita degli oggetti, etc; non obiettivo di questo capitolo approfondire tutti questi aspetti. Ma cosa sono, in definitiva, gli EJB? E cosa li distingue da altri sistemi simili, come DCOM? Essi sono componenti software ciascuno autonomamente responsabile di una parte di logica applicativa delegata al lato server del sistema. Ogni EJB non un programma autoconsistente, esso incapsula solo una parte di logica applicativa ed il suo ruolo si esplica allinterno del contesto in cui inserito. In particolare, il paradigma EJB composto da:

la specifica SUN riguardante il modo in cui i componenti interagiscono con il loro ambiente di riferimento (ossia lapplication server) un insieme di interfacce che il componente deve integrare

Le caratteristiche distintive degli EJB sono:

linguaggio Java: possibile scrivere componenti in qualsiasi linguaggio, ma gli EJB possono essere scritti solo in Java; questo vincolo non rappresenta,, in pratica, un grande vincolo, considerando che grazie alla caratteristica di Java di essere cross-platform, ogni EJB pu essere installato su qualsiasi macchina con qualunque sistema operativo; la presenza di un ambiente (lapplication server) che contiene lEJB e che si occupa autonomamente del suo ciclo di vita, non delegandolo ad altre applicazioni;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

384

una netta distinzione tra interfacce e implementazione, dal momento che linterfaccia di un EJB, ossia il meccanismo che consente ad unentit di colloquiare con lEJB, fisicamente separata dal componente stesso contenente limplementazione; il fatto che siano componenti server-side, e non oggetti (come i beans o gli ActiveX) o librerie da includere per lo sviluppo; gli EJB sono microsistemi autonomi pronti al deploy, che espongono una sola interfaccia i cui metodi possono essere chiamati da qualsiasi client, sia esso un client GUI, un servlet o un altro EJB.

Il concetto di deploy (parola difficilmente traducibile in una parola italiana altrettanto efficace) riassumibile come il processo relativo alla configurazione del contesto di riferimento dellEJB, alla definizione delle sue interazioni con il suo ambiente circostante (lapplication server) ed alla messa in esercizio dellEJB stesso.

21.3 Ragionare per componenti


Progettare e sviluppare EJB vuol dire imparare a ragionare per componenti. Come detto, quello di componente un concetto astratto, che sfugge ad ogni tipo di definizione sintetica ed esaustiva; , per, possibile tracciarne qualche caratteristica utile per definirne i contorni. Un componente non un applicazione completa: esso in grado di eseguire una serie di task, ma non in modo autonomo come unapplicazione. Una caratteristica fondamentale del concetto di componente laderenza ad uno standard di riferimento, in modo da consentire ai software integrator di costruire applicazioni utilizzando componenti sviluppati da qualsiasi software houses: in questo senso EJB, cos come DCOM, sono basati sui rispettivi standard SUN e Microsoft. In gergo tecnico, si dice che il componente software, per essere tale, deve aderire ad un modello a componenti; un modello a componenti definisce in modo formale le regole di interazione fra i vari elementi componenti. Rispetto ad un oggetto, un componente presenta il concetto di interfaccia che disaccoppia la natura implementativa dalla sua specifica; questo consente (in teoria) di sostituire componenti aventi la stessa specifica senza doversi curare dellimplementazione sottostante. Un componente dovrebbe essere relativo ad un argomento chiuso, ad una problematica definita, ed in questo senso condivide ed estende il concetto di classe. E, infatti, possibile scrivere componenti composti da pi di una classe; in questo modo, senza distorcere il paradigma Object Oriented, possibile riunire dentro lo stesso componente un insieme di classi che collaborano tra loro per eseguire un task. Limportante che il componente esponga i propri metodi attraverso una sola interfaccia verso il mondo esterno.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

385

Ad esempio, immaginiamo di dover realizzare un modulo per lautenticazione e laccesso degli utenti ad un sistema. Se supponiamo di autenticare lutente attraverso login e password, ecco che la problematica dellautenticazione diventa un argomento limitato, e possiamo pensare di utilizzare un unico componente cui delegare limplementazione. Un componente deve esser pensato con lottica della riusabilit: mentre lo creiamo, cerchiamo di pensare che la sua funzione non dovrebbe essere solo quella di assolvere ai compiti di cui abbiamo bisogno al momento, ma (per astrazione) anche alle problematiche generali inerenti largomento, e alla possibilit di utilizzarlo in un contesto diverso. Se la necessit di astrazione vera nel disegno di oggetti, lo ancor di pi in quello di un componente. Tornando allesempio precedente, potremmo avere che, per la nostra applicazione, la lista degli utenti registrati contenuta in una tabella di un RDBMS. Ma, nello sviluppo del componente, sarebbe un errore pensare di cablare la logica di autenticazione sullintegrazione con un database (e se, infatti, un domani dovessimo interfacciare un server LDAP?). Ecco che, ad esempio, conviene pensare da subito al componente come non legato al sistema che contiene gli utenti; dovremmo cercare di pensare da subito ad una specifica generale, che incapsuli limplementazione delle varie tecnologie utilizzate per realizzare la funzionalit. Diverse, inoltre, sono le forme in cui si pu presentare un componente. Quando vogliamo utilizzare un componente, sia esso proveniente da una entit esterna oppure scritto da noi, la prima operazione da compiere quella di inserirlo nellambiente operativo in cui dovr fornire i suoi servizi: abbiamo, cio, la necessit di installarlo (o, in caso, ad esempio, di EJB, di effettuarne il deploy). In tale caso in componente assume la forma di un componente installato, differente da quella di componente da installare in quanto loperazione di deploy comporta anche altre operazioni (ad esempio, il componente deve essere registrato nellelenco dei componenti utilizzabili dallapplicazione). A run time, ossia durante lesecuzione del programma, il componente installato non viene utilizzato direttamente, ma ne viene creata una copia in memoria (istanza) contenente i dati e i risultati relativi allelaborazione in corso. Per analogia con i concetti di classe ed oggetto, listanza del componente viene detta componente oggetto. Si noti che, indipendentemente dai dati di ingresso e dal momento in cui viene fotografata lelaborazione, ogni componente oggetto, bench creato come copia dello stesso componente installato, ha almeno una caratteristica unica e distintiva, lObject Identificator (OID). Riassumendo i concetti esposti, nella tabella successiva vengono schematizzati i concetti espressi: Riepilogo

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

386

Vocabolo Componente Modello a componenti Specifica del componente Interfaccia del componente Implementazione del componente Componente installato Componente oggetto

Descrizione Parte di software che aderisce ad un modello a componenti Standard di riferimento per la costruzione di componenti Specifica della funzionalit del componente stesso (cosa deve fare) Insieme dei metodi che il componente espone per poter essere attivato (cosa fa) Realizzazione della specifica del componente stesso (come lo fa) Copia del componente inserita nel suo contesto applicativo Istanza del componente installato

Dallanalisi delle caratteristiche dei componenti evidente che JAVA definisce due tipologie di standard di componenti al suo interno. Il primo, pi datato, quello dei JavaBeans, laltro, introdotto in J2EE, degli EJB. Tra i due tipi di componenti c una profonda differenza: i Javabeans sono componenti di sviluppo, essi possono essere presi e utilizzati a piacimento per realizzare software, ed il loro ambiente di run-time costituito dallapplicazione che li utilizza. Gli EJB, invece, sono componenti pi complessi, che necessitano di un ambiente specifico per essere istanziati ed utilizzati.

21.4 Larchitettura a componenti


Il modello cui si fa riferimento negli EJB quello di unarchitettura a componenti. Abbiamo visto cos un componente; unarchitettura a componenti , pertanto, unarchitettura software di cui una parte (o tutta) progettata e realizzata attraverso la collaborazione di pi componenti. La realizzazione di unarchitettura a componenti prefigura uno scenario di sviluppo software in cui gli attori possono essere numerosi e con differenti ruoli. Nello schema seguente riassunto linsieme dei ruoli nel ciclo di sviluppo a componenti: Ruoli nel ciclo di sviluppo a componenti Descrizione d e i colui che fornisce i componenti. Si noti che non c differenza tra componenti sviluppati ad hoc o acquistati sul mercato; questo implica che, dal punto di vista della progettazione, va valutato il costo opportunit dello sviluppo per singolo componente.

Ruolo Fornitore componenti

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

387

Lassemblatore dei Nellattivit di realizzazione dellarchitettura a componenti componenti, colui che, sulla base della conoscenza del business e delle specifiche di progetto, sceglie e compone tra loro i componenti, come mattoni per la costruzione di un edificio. Si occupa anche dello sviluppo delle parti di applicazione non realizzabili per componenti, come le interfacce grafiche o gli elementi di integrazione. LEJB deployer Si occupa del deploy e della messa in esercizio del sistema a componenti Lamministratore del Ha il compito di mantenere in buona efficienza il sistema sistema in esercizio, monitorando il funzionamento ed effettuando interventi di tuning. Il f o r n i t o r e E lazienda che fornisce lambiente che contiene i dellapplication componenti e le utilit di gestione del ciclo di vita degli server stessi

21.5 Il ruolo dellapplication server e struttura degli EJB


Allinterno dellarchitettura generale di figura seguente

Figura 147:

architettura java2

gli EJB rappresentano i componenti, il cui ciclo di vita realizzato allinterno dei Contenitori. LApplication server (che chiameremo AS nel prosieguo del capitolo) svolge, tipicamente, il ruolo di contenitore degli EJB, ma sarebbe riduttivo pensare solo a questa funzione. Gli AS, infatti, forniscono una ulteriore serie di elementi e servizi di supporto, quali, ad esempio:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

388

disponibilit di un Web server per la pubblicazione dei servizi su protocollo HTTP disponibilit di un container servlet/jsp associato al Web server console di amministrazione per la gestione del deploy a caldo, sia di applicazioni Web che di applicazioni enterprise console di amministrazione per la gestione e configurazione dinamica dellambiente supporto e strumenti di gestione per il clustering disponibilit di servizi di naming (JNDI) disponibilit di un bus CORBA disponibilit di utilit per la gestione dei web services disponibilit dellinfrastruttura per i servizi di messaging (JMS)

Questo complesso insieme di servizi consente di manutenere facilmente le applicazioni sia in ambiente di sviluppo che di esercizio; lintegrazione dei servizi in un unico ambiente di monitoraggio facilita, inoltre, il lavoro di amministrazione. Visto che si tratta di suite di prodotti e servizi, ladozione di un AS, inoltre, non rappresenta solo linfrastruttura tecnologica di base per le architetture a componenti, ma, data la presenza di CORBA, JMS e Web services, anche per la system integration in generale. Gli AS, pertanto, rappresentano il cuore dellinfrastruttura tecnologica delle applicazioni J2EE; la scelta dellAS da utilizzare , quindi, cruciale in fase di progettazione del sistema. Senza addentrarci troppo nelle differenze tra i vari prodotti esistenti in commercio, ecco alcuni consigli da seguire nella scelta e nelluso di un AS, sia che siate dei progettisti che degli sviluppatori in architettura J2EE:

tutti gli AS java in commercio sono conformi allo standard SUN J2EE; tuttavia le loro caratteristiche progettuali possono essere anche molto differenti. Alcuni AS, infatti, nascono come continuazione ed estensione di esperienze specifiche dei produttori in prodotti storici (ad esempio, alcuni nascono da produttori di ORB CORBA, altri da produttori di sistemi di message queing, altri ancora da esperienze nei database relazionali); questo fatto ci consente di capire quale taglio progettuale (e quali siano gli elementi di forza di base) dei vari prodotti. Questa considerazione, se non ci d alcuna informazione sulla qualit del prodotto, pu essere utile per inquadrare la scelta dellAS nel contesto dei prodotti presenti nellambiente enterprise di riferimento. Anche le caratteristiche operative degli AS sono, tra i vari prodotti, diverse. Ogni produttore, vuoi per necessit progettuali, vuoi per integrare al meglio i servizi offerti, ha aggiunto allo standard SUN degli

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

389

elementi proprietari. Queste aggiunte non comportano, generalmente problemi in fase di sviluppo dei componenti (si tratta, di solito, di fare attenzione alle librerie da includere), ma possono comportare differenze anche sostanziali nella fase di deploy. Passare da un AS ad un altro vuol dire, nella gran parte dei casi, modificare il processo di deploy. Dal momento che i principali strumenti di sviluppo presentano anche utility per il deploy, la creazione automatica dei descrittori e la configurazione dellambiente, alla scelta di un AS deve corrispondere una scelta sullambiente di sviluppo pi consono.

Linserimento di un AS in una architettura software complessa comporta lintegrazione con prodotti sviluppati da differenti produttori software. Anche quando scritti in Java e conformi alle direttive SUN, non tutti i prodotti sono compatibili con tutti gli AS! A volte si tratta solo di versioni da aggiornare, altre volte di incompatibilit strutturali; in ogni caso, una buona ricerca sulle FAQ dei produttori degli AS e/o dei prodotti da integrare potr anticipare brutte sorprese.

Dal punto di vista degli EJB, lAS rappresenta lecosistema in cui vive una istanza EJB. LAS, nel suo ruolo di contenitore degli EJB:

gestisce la Java Virtual Machine necessaria ad istanziare il componente, con i parametri di configurazione e avvio prescelti; intercetta le chiamate verso il componente e si preoccupa di capire quale oggetto coinvolto, di verificare se creare o meno istanze delloggetto per gestire la richiesta e di gestire lunivocit degli oggetti identificandoli con lOID; gestisce le chiamate agli oggetti integrandosi con i servizi JNDI; gestisce i servizi di transazionalit; gestisce la persistenza degli oggetti; gestisce il connection pooling (insiemi di connessioni a database); gestisce il garbage collection degli oggetti.

Tutti queste funzionalit vengono svolte dallAS in modo trasparente al programmatore, semplificandone notevolmente il lavoro. LEJB, allinterno dellAS, quindi invisibile ed inaccessibile direttamente; per rendere le sue funzionalit disponibili allesterno, lAS crea una copia del bean detta EJB Object. La funzionalit di container degli EJB pu essere compresa con la figura seguente:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

390

Figura 148:

ruolo dei vari elementi componenti un EJB

Il client remoto accede allEJB attraverso le interfacce home e remote , incapsulate nei componenti stub-skeleton; lo strato di servizio dellapplication server espone i servizi EJB mediante due copie virtuali dellEJB: lEJBHome object e lEJB Object. LEjbObject rappresenta una vera copia dellEjb ( il componente oggetto) ed utile nelle chiamate ai metodi dellEJB. LEjbHome object rappresenta, invece, la rappresentazione esterna dellistanza del componente; in pratica rappresenta lelemento di gestione del ciclo di vita del componente, essendo responsabile della creazione, della rimozione e della ricerca, nellinsieme dei componenti in memoria, del componente oggetto ricercato dal client. Le interfacce remote e home sono, per il client, linterfaccia del componente, gli elementi che consentono la location trasparency e che ci dicono chi e cosa fa il componente. Nella specifica EJB 2.0, con significato analogo al caso precedente, vengono definite anche le componenti local; lEJB, con linterfaccia local, consente ai client locali (ossia che sono presenti nella stessa virtual machine dellAS, come ad esempio altri EJB) di evitare lo strato di comunicazione stub-skeleton nelle chiamate al bean, migliorando sensibilmente le prestazioni. In tale caso vengono definiti i due elementi di interfaccia, lEjbLocal (funzionalmente analoga alla remote) e lEjbLocalHome (analoga allEJBHome). Ad ognuno di questi elementi associato, nel codice, una chiamata ad un elemento della libreria javax.ejb.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

391

Corrispondenza tra componenti Elemento Logico Corrispondenza nel codice EJB implements javax.ejb. (qui si distinguono i tipi di bean) Home interface Extends javax.ejb.EjbHome Remote interface Extends javax.ejb.EjbObject LocalHome interface Extends javax.ejb.EjbLocalHome Local interface Extends javax.ejb.EjbLocalObject

1.1

Il concetto di pooling

Uno degli aspetti pi importanti da comprendere sugli EJB, e sul rapporto che essi hanno con lAS, il concetto di pooling. Si tratta della capacit dellAS di gestire un insieme di risorse (pool) in modo centralizzato, intendendo con la parola gestione tutto il processo legato allinsieme delle risorse coinvolte, che pertanto risulta ottimizzato e controllato. Due sono, in particolare, i servizi di pooling offerti dallAS: il bean pooling ed il connection pooling. Per bean pooling si intende la capacit dellAS di gestire gruppi di istanze di EJB. Quando, infatti, un client richiede i servizi di un EJB (ad esempio, ne richiede la creazione o ne invoca un metodo), la sua chiamata allEJBHome Object (o allEJBObject) viene intercettata dal container che in grado di decidere autonomamente se:

in caso di creazione, creare effettivamente una nuova istanza dellEJBObject; in caso di creazione, utilizzare una istanza nuova tra quelle da lui preparate e mantenute in memoria.

LAS, infatti, provvede a tenere sempre in memoria un certo numero di bean precaricati che non sono pronti alluso, dal momento che non sono stati inizializzati. Nel caso lAS decida di usare uno di questi, provvede a inizializzarne lo stato, creandogli il contesto di riferimento, e poi a renderlo disponibile. Questo processo consente allAS di risparmiare tempo in fase di caricamento del bean, anche se a scapito di un po di memoria. Questo aspetto deve essere, per, tenuto sotto controllo, altrimenti le risorse di sistema andrebbero esaurendosi rapidamente. Nel caso in cui esistano dei bean sviluppati in modo da restare in memoria per lungo tempo, cos da poter servire il client in momenti differenti, lAS deve utilizzare la strategia opposta, ossia cercare di risparmiare risorse. Per fare questo utilizza i concetti di passivazione e attivazione (di cui parleremo in seguito), con lobiettivo finale

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

392

di mantenere sempre il numero di bean in memoria entro un range controllato. In sostanza, il concetto di bean pooling prevede, a seconda del tipo di bean, delle politiche che cerchino ottimizzare le prestazioni del sistema a parit di risorse a disposizione dellAS. Diverso il concetto di connection pooling. Questo ha a che fare con un noto problema inerente lutilizzo di applicazioni Web con database relazionali (in particolare questo problema era frequente nei casi di uso di programmi CGI). Quando un client Web effettua una transazione verso un database, lapplicazione lato server deve, in corrispondenza di ogni richiesta, aprire una connessione verso il database, tenerla aperta per il tempo di lavoro e poi chiuderla. Questo continuo aprire e chiudere le connessioni al database ha un effetto devastante sulle prestazioni dellapplicazione; le operazioni di connect e disconnect su RDBMS sono, infatti, estremamente costose in termini di risorse macchina del sistema che ospita il database, ed anche per il complesso handshaking di protocollo tra client e RDBMS. Lindeterminatezza sul numero dei client, e quindi delle connessioni, unito alla impossibilit di limitare la concorrenza rendeva, inoltre, difficile effettuare un tuning sui sistemi in queste condizioni. Lidea, quindi, quella di aprire un certo numero predefinito di connessioni (pool di connessioni) e tenerle sempre aperte per le varie istanze dei processi che debbono utilizzarle. E il container ad assegnare, in modo trasparente allapplicazione stessa, la risorsa connessione, scegliendone una fra quelle disponibili al momento, ed occupandosi di gestire eventuali accodamenti. In questo modo il carico sul database costante, con valori di picco predefiniti in fase di configurazione e non aleatori.

21.6 I tre tipi di EJB


I tipi di Enterprise JavaBeans definiti da Sun nella specifica 1.1 sono due: SessionBeans e EntityBeans. Nella pi recente specifica 2.0 stato aggiunto anche un terzo tipo, i MessageDrivenBeans, otre ad essere cambiate alcuni elementi dellarchitettura degli altri due tipi. La definizione delle differenze tra le varie specifiche, cos come i dettagli implementativi, non saranno oggetto del libro; si pertanto, deciso di esporre una trattazione ristretta di casistiche e di concetti, al fine di consentire al lettore una panoramica agile anche se, necessariamente, non completa.

21.7 Session Beans


I SessionBeans sono i componenti che, nella architettura EJB, devono occuparsi di incapsulare e definire la business logic delle applicazioni lato server. Il loro nome deriva dalla considerazione che la logica applicativa un elemento che necessita di una memorizzazione persistente dei dati in essa residenti (le variabili locali necessarie allelaborazione) limitata alla sessione di

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

393

lavoro. Nel caso in cui i dati debbano essere resi persistenti per un tempo arbitrariamente lungo, allora necessario utilizzare un supporto logico dedicato, come un database, con una rappresentazione adeguata (che, nel modello EJB, data dagli EntityBean).

21.8 Tipi di Session Beans


Al fine di esplicitare ulteriormente questo concetto, il paradigma EJB distingue i SessionBeans in:

stateless: bean che limita la propria esistenza ad una chiamata da parte di un client. Loggetto viene creato, esegue il metodo invocato dal client (che deve passargli tutti i parametri necessari allelaborazione) e quindi viene rimosso dallAS (con politiche solo parzialmente definibili dallutente). Si noti che il bean pu avere parametri locali predefiniti e persistenti, ma tali parametri sono definiti in fase di configurazione (deploy) e non dipendono dal client. Un esempio di bean di questo genere pu essere quello che contiene funzioni matematiche di calcolo. Stateful: bean che vive per un tempo superiore a quello di esecuzione di un metodo, consentendo una conversazione con il client. La rimozione delloggetto pu essere richiesta dal programmatore nel codice, oppure gestita, tramite timeout, dallAS. Il bean, durante tutto il colloquio con il client, mantiene lo stato dellelaborazione e le informazioni sullo specifico client chiamante, consentendo una conversazione asincrona ed esclusiva. Esempi di bean stateful sono tutti quelli in cui necessario un colloquio continuo tra client e server, come la gestione di un carrello in una applicazione di commercio elettronico.

Una volta deciso il tipo di SessionBean da sviluppare, vediamo la struttura del codice dei vari elementi da sviluppare. Supponiamo, ad esempio, di voler sviluppare il classico metodo che ritorna una stringa come metodo di uno stateless SessionBean, con il solo ausilio della interfaccia remote. Come evidenziato anche in RMI, nei paradigmi ad oggetti distribuiti la prima cosa da fare definire linterfaccia remote, nella quale dichiareremo il nostro metodo di business ciao():
import javax.ejb.EJBObject; public interface SampleSession extends EJBObject { public String ciao() throws java.rmi.RemoteException; }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

394

Linterfaccia remote estende la classe javax.ejb.EJBObject; in questo modo viene detto al container quali sono i metodi delegati allistanza EJB Object. Dal momento che pu essere invocato da remoto, il metodo ciao() (la cui logica deve essere dichiarata nel codice del bean) emette una RemoteException i modo da poter segnalare eventuali problemi nella chiamata RMI (o, nella specifica 2.0, RMI-IIOP). Il secondo file da sviluppare (ma, di solito, il suo sviluppo automaticamente generato dai tools di sviluppo) quello inerente linterfaccia Ejb Home.

21.9 File SampleSessionHome.java


import import import import javax.ejb.EJBHome; javax.ejb.CreateException; javax.ejb.EJBException; java.rmi.RemoteException;

public interface SampleSessionHome extends EJBHome { SampleSession create() throws CreateException, RemoteException; }

Linterfaccia SampleSessionHome estende la classe javax.ejb.EJBHome, segnalando al container che questa classe quella delegata alle funzioni di Home object. Il metodo necessariamente presente deve essere il create(), necessario per la creazione delloggetto; tale metodo ritorna una istanza del bean SampleSession. Si noti come, oltre alla RemoteException, il metodo ritorni anche leccezione CreateException, nel caso in cui la richiesta di creazione delloggetto dovesse fallire. Il file contenente il vero session bean presentato nel listato seguente.

21.10 File SampleSessionBean.java


import import import import import javax.ejb.SessionBean; javax.ejb.SessionContext; java.rmi.RemoteException; javax.ejb.EJBException; javax.ejb.CreateException;

public class SampleSessionBean implements SessionBean { private SessionContext ctx; public void setSessionContext(SessionContext context) throws RemoteException { ctx = context; } public void ejbActivate() { }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

395

public void ejbPassivate() { } public void ejbRemove() { } public void ejbCreate() throws CreateException { // Qui va scritto eventuale codice da inserire nella creazione delloggetto } public String ciao() { return "ciao da SessionSample"; } }

Come si vede dallesempio, la classe SampleSessionBean dichiara il proprio status di EJB implementando linterfaccia javax.ejb.SessionBean; questo comporta la definizione di ben cinque metodi, oltre quelli definiti dallutente. Metodi da definire Nome Metodo Descrizione public void ejbCreate() Definisce la logica da implementare in fase di creazione del bean p u b l i c v o i d Definisce la creazione del contesto di riferimento setSessionContext(SessionContext del bean
context) public void ejbActivate()

public void ejbPassivate() public void ejbRemove()

Definisce le politiche da attuare nellattivazione delloggetto Definisce le politiche da attuare nella passivazione delloggetto Definisce le politiche da attuare nella rimozione delloggetto

I metodi ejbActivate() e ejbPassivate() richiamano al concetto di bean pooling ed introducono il concetto della persistenza del bean. Al fine di risparmiare risorse, lAS provvede a eliminare dalla memoria gli oggetti che, pur dovendo rimanere persistenti, sono meno usati. Quando un Ejb non viene utilizzato per un certo tempo (configurabile), lAS provvede passivarlo, ossia a salvarlo su disco, mantenedo in memoria solamente un riferimento allhandle delloggetto in modo da poterlo recuperare. Nel momento in cui un client tenta di invocare di nuovo lEJB passivato, lAS provvede a attivarlo , ossia a ricaricare in memoria loggetto. In verit, per poter ricostruire loggetto a partire dal suo salvataggio su disco, lAS potrebbe non avere la necessit di salvare tutto loggetto, ma solo il suo stato ed il suo contesto di riferimento. In tale caso il processo di attivazione associa lo stato ed il contesto salvati ad una istanza del bean (che, pertanto, potrebbe non essere quella originale).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

396

A queste due operazioni, eseguite per default dallAS, possono essere associate delle azioni da intraprendere, e la specifica delle azioni va inserita nei due metodi succitati. Si noti che:

il processo di passivazione, nel caso dei SessionBeans, coinvolge solo gli stateful beans, non gli stateless. Per questi ultimi, i metodi ejbPassivate() e ejbActivate() sono inutili; nella operazione di passivazione loggetto viene scritto su uno stream, e questa operazione sicura solo se tutti gli elementi in esso contenuti (che concorrono a determinarne lo stato) implementano linterfaccia java.io.Serializable; il processo di passivazione interrompe gli stream eventualmente aperti dalloggetto; questi vanno necessariamente chiusi nella ejbPassivate() ed eventualmente riaperti nella ejbActivate().

Ad esempio, nel caso lEjb abbia aperto un socket o sia in lettura su un file, necessario definire nella ejbPassivate() il codice necessario per chiudere il socket (o il file) ed il codice per riaprirlo nella ejbActivate(). Il metodo ejbCreate() utile per inizializzare loggetto nel momento in cui viene istanziato, cos come il metodo ejbRemove() ci consente una deallocazione pulita delloggetto, evitando carichi al garbage collector. Il metodo setSessionContext() necessario al container per comunicare allistanza del bean il suo contesto di riferimento, intendendo con contesto tutte le informazioni dambiente necessarie al bean per svolgere il proprio lavoro (ad esempio, variabili dambiente o informazioni utente sul client connesso). Il metodo setSessionContext() viene invocato dal container durante la fase di creazione delloggetto ed il context deve essere salvato in una variabile locale per poterne interrogare le propriet durante il ciclo di vita delloggetto.

21.11 Ciclo di vita di un SessionBean


I vari metodi definiti nel bean non possono essere chiamati in qualsiasi momento, ma la loro invocazione, da parte del client o del container, dipende dallo stato del bean. Nella figura seguente mostrato il ciclo di vita di uno stateful session bean:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

397

Figura 149:

statechart diagram relativo ciclo di vita di uno Stateful SessionBean

Uno stateful SessionBean possiede due stati possibili: attivo e passivato. Per poter essere definito come bean attivo, deve essere caricato dal container con le chiamate dei metodi proposti; in questo stato pu ricevere le chiamate verso i metodi di business provenienti dal client. Uno Stateless SessionBean, una volta creato, possiede solo lo stato attivo, come definito nella figura seguente:

Figura 150:

statechart diagram relativo ciclo di vita di uno Stateless SessionBean

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

398

In entrambi i casi la fase critica quella relativa alla creazione del bean e di tutta la catena degli oggetti associati. Per meglio comprendere la sequenza delle chiamate dei vari metodi definiti ed il ruolo del container, utile analizzare il seguente sequence diagram, nel quale esemplificata la sequenza delle chiamate dei metodi nella fase di creazione del SessionBean:

Figura 151:

sequence diagram relativo alla fase di creazione del bean

Alla invocazione della create() da parte di un client, il container provvede alla ceazione dellEJBObject, di un contesto e di una istanza del bean. Tutti questi elementi concorrono alla inizializzazione dellistanza del bean, sulla quale il container invoca il metodo setSessionContext() per inizializzarne lo stato e quindo ne invoca la ejbCreate() ove vengono definite le logiche definite dal programmatore per la creazione del bean.

21.12 Entity Bean: Definizioni e tipi


Uno dei componenti essenziali di qualsiasi architettura software, ed in particolare di quelle enterprise, rappresentato dalla necessit di trattare entit che rimangano persistenti per un periodo arbitrario di tempo.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

399

Generalmente lo strumento pi utilizzato per gestire la persistenza di dati rappresentato dai database, con particolare riguardo per quelli relazionali, che rappresentano la gran parte del mercato di riferiemento. Gli EntityBeans rappresentano il meccanismo J2EE per gestire la persistenza di oggetti; in particolare, secondo la specifica SUN, gli EntityBeans sono la rappresentazione Object Oriented di entit persistenti. Dal momento che il pi comune strumento di gestione di entit organizzate e persistenti rappresentato dai database relazionali, possiamo affermare che gli EntityBeans rappresentano il meccanismo J2EE per virtualizzare laccesso ai database relazionali. In estrema sintesi, un EntityBean (nei casi pi semplici, ma anche pi comuni) la rappresentazione ad oggetti di una tabella di un database relazionale (RDBMS), e listanza del bean la rappresentazione ad oggetti di una riga di una tabella di un RDBMS12. La domanda pu sorgere spontanea: che senso ha aggiungere un nuovo componente architetturale per laccesso ai database quando gi presente il paradigma JDBC? Innanzitutto JDBC rappresenta uno strato che disaccoppia il codice Java dalle caratteristiche specifiche del database, fornendo una interfaccia generica per la comunicazione con qualsiasi RDBMS standard. Tale interfaccia, per, non riduce nel codice la conoscenza dello schema fisico del database e soprattutto non elimina il codice SQL, costringendo il programmatore ad occuparsi della mappatura dei tipi dati del database e di gestire il binding. Il vantaggio di avere EntityBean quello di poter mappare nel modello ad oggetti il modello relazionale che, nativo del database, invece alieno alla programmazione Java. Questa mappatura, oltre a consentire una programmazione pi naturale ed omogenea, elimina parecchio codice SQL. E, inoltre, possibile utilizzare i servizi offerti dal container per gestire la comunicazione con il database, delegando dal codice alla configurazione la gestione delle transazioni e la gestione delle connessioni contemporanee (connection pooling). Unaltra problematica in cui ci utile il container quella della gestione della sincronizzazione tra i valori presenti sul database e la loro rappresentazione nel bean: ogni nuova istanza di un bean deve generare un nuovo record nella tabella, cos come ad ogni modifica dei valori delle variabili del bean deve corrispondere una update dei dati sul database. Questo processo di allineamento e di sincronizzazione della rappresentazione dei dati detto persistenza. Nella specifica 2 di Ejb vengono definiti due tipi di EntityBean, e la distinzione verte proprio sulla gestione della persistenza:

In realt la definizione non propriamente corretta, dal momento che quello di associare un bean ad una tabella solo uno dei possibili modi di disegno, ma fornisce una idea chiara e semplice del concetto associato ad un Entity Bean

12

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

400

Persistenza di un EJB Tipo di persistenza Descrizione CMP (Container Managed Nei bean di tipo CMP il container a gestire Persistence) automaticamente la persistenza. Il programmatore deve occuparsi solo di gestire la logica applicativa, senza doversi occupare della persistenza. Per poter utilizzare questo tipo di bean , per, necessario comunicare al container (in fase di configurazione) la lista di corrispondenza di quali campi del bean rappresentino attributi di tabelle. Questa configurazione pu essere anche complessa, ma consente una astrazione maggiore non legando lo sviluppo alla struttura del database. BMP (Bean Managed Nei bean di tipo BMP il carico della gestione Persistence) della persistenza a carico del programmatore. In pratica il programmatore che deve occuparsi, ogni volta che deve inserire o modificare dati sul database, di farlo esplicitamente attraverso codice di tipo SQL.

21.13 Struttura di un EntityBean


Oltre alla gestione della persistenza secondo gli schemi suddetti (CMP e BMP), il modello EntityBean ha, rispetto ai Session Beans un altro elemento distintivo, ossia la presenza di uno speciale identificativo delloggetto. Dal momento che la corrispondenza logica delloggetto una tabella di un database, lecito pensare che il bean debba (cos com in un database con la chiave primaria di una tabella) avere un identificativo speciale. Tale identificativo non un attributo, ma viene codificato con una classe da definire in un nuovo file, ed aggiunto alla struttura standard dei files degli EJB. Questo file , di solito, indicato con la desinenza PK (che sta per Primary Key chiave primaria) aggiunta al nome del file di interfaccia remote del bean. Quindi un entity bean (ad esempio, per un generico bean EntityDiEsempio) definito da: I file standard EJB: EntityDiEsempio.java (interfaccia) EntityDiEsempioBean.java (Codice del bean vero e proprio), EntityDiEsempioHome.java (Interfaccia Home) Il file di chiave primaria EntityDiEsempioPK.java

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

401

E importante non confondere la chiave primaria con lhandle del bean. Ogni componente oggetto (istanza home e componente oggetto di Session, Entity o MessageDriven) identificata dal container mediante un identificativo univoco generato dal container stesso, detto handle. Tale identificativo utile, ad esempio dal lato client, per poter riferire una stessa istanza durante pi chiamate succesive (per ottenere lhandle di un oggetto, definito il metodo getHandle()). La chiave primaria, invece, un identificativo logico, inerente il contenuto informativo del bean ed indipendente dallistanza. Vediamo, ora, la struttura di un EntityBean analizzando i metodi specifici definiti per i vari elementi costitutivi del componente. Per linterfaccia Home i metodi obbligatori sono: Metodi di un EntityBean Nome Metodo Descrizione public void create() Costruttore del bean public findByPrimaryKey () Metodo per la ricerca del bean a partire dalla chiave primaria public ejbFind () Metodi per la ricerca del bean Il metodo create ha una funzione speciale negli EntityBeans. Dal momento che gli EntityBean rappresentano i dati presenti nelle tabelle, il metodo create quello che consente di creare nuove istanze del bean, cui deve corrispondere una rappresentazione persistente, ossia nuovi record nella tabella. Di fatto, nel caso in cui un bean rappresenti una tabella, invocare la create equivale a creare un nuovo record su tabella. I finder methods , ossia i metodi di ricerca, sono metodi specifici degli EntityBean e la loro definizione e comprensione rappresenta un elemento fondamentale in fase di disegno del bean. Continuando ad associare un EntityBean ad una tabella di un database, lutilizzo di un finder method di un EntityBean equivale ad effettuare una query di selezione sul database. Cos come una SELECT sul database ritorna una serie di record, cos un finder method pu ritornare una collezione di oggetti che rappresentano il record. Nel caso in cui il metodo possa ritornare solo una istanza (ad esempio, nel caso della findByPrimaryKey ), il tipo ritornato dal metodo unistanza delloggetto bean stesso, in modo da consentire al chiamante di manipolare, attraverso i metodi esposti nelle interfacce local e/o remote, i valori del bean, e quindi del record della tabella corrispondente alla chiave primaria. Per linterfaccia EntityBean (che definisce il bean vero e proprio) i metodi da definire sono: Interfaccia EntityBean Descrizione

Nome Metodo

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

402

Definisce la logica da implementare in fase di creazione del bean public void ejbPostCreate() Definisce le azioni da compiere dopo la create() p u b l i c v o i d Definisce la creazione del contesto di riferimento setEntityContext(EntityContext del bean
public void ejbCreate() context) p u b l i c v o i d unsetEntityContext(EntityContext context) public void ejbLoad() public void ejbStore() public void ejbActivate() public void ejbPassivate() public void ejbRemove()

Invocato dal container, elimina lassociazione del bean con il suo contesto, preparando il bean alla sua distruzione. Serve per caricare i dati del database dentro al bean Serve per rendere persistenti sul database i dati contenuti nel bean Definisce le politiche da attuare nellattivazione delloggetto Definisce le politiche da attuare nella passivazione delloggetto Definisce le politiche da attuare nella rimozione delloggetto

Rispetto ai SessionBeans, troviamo il metodo setEntityContext che ha significato analogo al metodo setSessionContext , e sono presenti quattro nuovi metodi tipici degli EntityBean: unsetEntityContext ,ejbPostCreate , ejbLoad ed ejbStore. Il metodo ejbPostCreate viene eseguito, invocato dal container, immediatamente dopo lassociato metodo ejbCreate. Dal momento che lejbCreate il metodo che il container invoca alla chiamata del metodo create() della interfaccia Home, esso definisce la creazione del bean (e, quindi, della creazione di un record sulla sottostante tabella), non lasciando spazio alla creazione di altra logica oltre quella di creazione. Il metodo ejbPostCreate serve proprio per definire logiche locali e ad esempio, inizializzare variabili utili allimplementazione della logica di business. Si noti che possono essere definiti pi metodi ejbCreate e per ciascuno di essi va definito un corrispondente ejbPostCreate. Il metodo unsetEntityContext interviene in fase di distruzione dellistanza delloggetto e prepara il bean al ciclo di garbage collection liberando le risorse allocate con il metodo setEntityContext. Il metodo ejbLoad, invece, interviene qualora si voglia caricare dati dalla base dati, mentre il metodo ejbStore viene invocato quando necessario effettuare update sulla tabella sottostante, rendendo persistenti sul database le modifiche effettuate sui campi del bean.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

403

Per capire meglio come funzioni un entity bean, e quali siano i vantaggi nelluso di questa tecnologia, scriviamo un semplice esempio di bean per la tabella impiegati utilizzata nel capitolo JDBC.

21.14 Un esempio di EntityBean CMP


Lesempio di bean CMP ci consente di evidenziare maggiormente le differenze rispetto alla classica programmazione con JDBC. Al solito, iniziamo la scrittura del codice dallinterfaccia. In questo caso utilizzeremo, come nel caso del Session Bean del capitolo precedente, linterfaccia remote, ma anche una interfaccia local. E bene sapere, per, che il modo pi corretto per accedere ad un EntityBean quello di utilizzare SessionBeans per interfacciare gli EntityBeans, utilizzando solo le interfacce local per gli Entity e delegando laccesso da remoto per i Session. Nel caso di utilizzo di interfacce local, tipicamente la struttura dei nomi dei file (per il bean Nome) segue il seguente schema: Struttura dei nomi dei file Nome File Tipo di file Nome.java Interfaccia Local NomeHome.java Interfaccia localHome NomeBean.java Sorgente del Bean vero e proprio NomeRemote.java Interfaccia Remote NomeRemoteHome.java Interfaccia remoteHome Nome.java Interfaccia Local Il bean che creeremo una rappresentazione della tabella IMPIEGATI cos definita: Struttura della tabella IMPIEGATI Nome dellattributo Tipo Vincoli IDImpiegato Intero Chiave primaria Nome Stringa di caratteri Obbligatorio Cognome Stringa di caratteri Obbligatorio TelefonoUfficio Stringa di caratteri Iniziamo, quindi, con la scrittura delle interfacce local e remote
package sampleentity; import javax.ejb.*;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

404

import java.util.*; public interface ImpiegatiCMP extends javax.ejb.EJBLocalObject { public java.lang.Integer getIDImpiegato(); public void setNome(java.lang.String nome); public java.lang.String getNome(); public void setCognome(java.lang.String cognome); public java.lang.String getCognome(); public void setTelefonoUfficio(java.lang.String telefonoUfficio); public java.lang.String getTelefonoUfficio(); } package sampleentity; import javax.ejb.*; import java.util.*; import java.rmi.*; public interface ImpiegatiCMPRemote extends javax.ejb.EJBObject { public java.lang.Integer getIDImpiegato() throws RemoteException; public void setNome(java.lang.String nome) throws RemoteException; public java.lang.String getNome() throws RemoteException; public void setCognome(java.lang.String cognome) throws RemoteException; public java.lang.String getCognome() throws RemoteException; public void setTelefonoUfficio(java.lang.String telefonoUfficio) throws RemoteException; public java.lang.String getTelefonoUfficio() throws RemoteException; }

Come si vede dalla struttura dei files, i campi della tabella sono quattro: IDImpiegato (la chiave primaria), nome, cognome, TelefonoUfficio (campi informativi). A ciascuno dei campi informativi della tabella corrispondono due metodi set e get per la scrittura e la lettura del campo stesso, disponibili sia per linterfaccia locale che per quella remota. Per la chiave primaria presente solo il metodo di lettura del campo. Si noti, inoltre, come nellinterfaccia local i metodi non emettano alcuna eccezione; questo avviene poich non vengono chiamati da remoto, e pertanto non possibile che si verifichino problemi di RMI. A questo punto necessario definire le classi Home:
package sampleentity; import javax.ejb.*; public interface ImpiegatiCMPHome extends javax.ejb.EJBLocalHome { public ImpiegatiCMP create(java.lang.Integer iDImpiegato) throws CreateException; public ImpiegatiCMP findByPrimaryKey(java.lang.Integer iDImpiegato) throws FinderException; } package sampleentity; import javax.ejb.*;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

405

import java.util.*; import java.rmi.*; public interface ImpiegatiCMPRemoteHome extends javax.ejb.EJBHome { public ImpiegatiCMPRemote create(java.lang.Integer iDImpiegato) throws CreateException, RemoteException; public ImpiegatiCMPRemote findByPrimaryKey(java.lang.Integer iDImpiegato) throws FinderException, RemoteException; }

In questo caso lunico metodo di ricerca implementato nelle interfacce il findByPrimaryKey(); qualora sia necessario, possibile definire tutti i metodi di ricerca richiesti nelle interfacce Home. Si noti il ruolo delle eccezioni nelle definizioni dei metodi: luso di eccezioni specifiche per la creazione delloggetto (CreateException ) e per la ricerca (FinderException) consente di valutare immediatamente le conseguenze di errori applicativi nellinserimento e nella ricerca di righe nella tabella sottostante alla definizione del bean. Veniamo ora alla definizione del codice del bean.
package sampleentity; import javax.ejb.*; public abstract class ImpiegatiCMPBean implements EntityBean { EntityContext entityContext; public java.lang.Integer ejbCreate(java.lang.Integer iDImpiegato) throws CreateException { setIDImpiegato(iDImpiegato); return null; } public void ejbPostCreate(java.lang.Integer iDImpiegato) throws CreateException { } public void ejbRemove() throws RemoveException { } public abstract void setIDImpiegato(java.lang.Integer iDImpiegato); public abstract void setNome(java.lang.String nome); public abstract void setCognome(java.lang.String cognome); public abstract void setTelefonoUfficio(java.lang.String telefonoUfficio); public abstract java.lang.String getTelefonoUfficio(); public abstract java.lang.String getCognome(); public abstract java.lang.String getNome(); public abstract java.lang.Integer getIDImpiegato(); public void ejbLoad() { } public void ejbStore() { } public void ejbActivate() {

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

406

} public void ejbPassivate() { } public void unsetEntityContext() { this.entityContext = null; } public void setEntityContext(EntityContext entityContext) { this.entityContext = entityContext; } }

La prima cosa che si nota il fatto che la classe ed i metodi set e get relativi agli attributi informativi della tabella, siano tutti abstract. Questo derivato dalla scelta di definire il bean come CMP. La gestione degli accessi alla base dati e della persistenza , infatti, gestita interamente dal container che si occupa di generare automaticamente i metodi pi opportuni per interfacciarsi con la base dati. Questo vero anche nel caso in cui si vogliano creare, ad esempio, altri metodi di ricerca: le regole per effettuare query complesse non vanno comunque descritte nel codice del bean ma nel file descrittore del deploy. Questo disaccoppia la scrittura del codice dal processo di deploy, rendendo il codice pi snello e manutenibile. Per la scrittura delle eventuali query allinterno dei file di deploy viene utilizzato un linguaggio SQL-like, chiamato EJB-QL, di cui parleremo in seguito.

21.15 Un esempio di EntityBean BMP


Vediamo ora un esempio di EntityBean BMP; come detto, nel caso di uso di bean BMP, la scrittura delle query delegata al programmatore, che deve inserire il codice SQL allinterno dei metodi che ne hanno necessit (tipicamente almeno dentro la ejbCreate). Al solito iniziamo dalla scrittura delle interfacce:
package sampleentity; import javax.ejb.*; import java.util.*; public interface ImpiegatiBMP extends javax.ejb.EJBLocalObject { public java.lang.Integer getIDImpiegato(); public void setNome(java.lang.String nome); public java.lang.String getNome(); public void setCognome(java.lang.String cognome); public java.lang.String getCognome(); public void setTelefonoUfficio(java.lang.String telefonoUfficio); public java.lang.String getTelefonoUfficio(); } package sampleentity;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

407

import javax.ejb.*; import java.util.*; import java.rmi.*; public interface ImpiegatiBMPRemote extends javax.ejb.EJBObject { public java.lang.Integer getIDImpiegato() throws RemoteException; public void setNome(java.lang.String nome) throws RemoteException; public java.lang.String getNome() throws RemoteException; public void setCognome(java.lang.String cognome) throws RemoteException; public java.lang.String getCognome() throws RemoteException; public void setTelefonoUfficio(java.lang.String telefonoUfficio) throws RemoteException; public java.lang.String getTelefonoUfficio() throws RemoteException; } package sampleentity; import javax.ejb.*; public interface ImpiegatiBMPHome extends javax.ejb.EJBLocalHome { public ImpiegatiBMP create(java.lang.Integer iDImpiegato) throws CreateException; public ImpiegatiBMP findByPrimaryKey(java.lang.Integer iDImpiegato) throws FinderException; } package sampleentity; import javax.ejb.*; import java.util.*; import java.rmi.*; public interface ImpiegatiBMPRemoteHome extends javax.ejb.EJBHome { public ImpiegatiBMPRemote create(java.lang.Integer iDImpiegato) throws CreateException, RemoteException; public ImpiegatiBMPRemote findByPrimaryKey(java.lang.Integer iDImpiegato) throws FinderException, RemoteException; }

Le classi Home e Remote , rappresentando solo le interfacce ai metodi di business, non cambiano passando da CMP a BMP. Quello che cambia significativamente il codice del bean, ma prima di analizzarlo, necessario introdurre il file ImpiegatiPK contenente il codice relativo alla gestione della chiave primaria:
package sampleentity; import java.io.Serializable; public class ImpiegatiPK implements java.io.Serializable { java.lang.Integer iDImpiegato; public ImpiegatiPK (java.lang.Integer iDImpiegato) { this.iDImpiegato = iDImpiegato; }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

408

public String toString(){ return iDImpiegato.toString(); } public boolean equals(Object o){ if (o instanceof ImpiegatiPK) { ImpiegatiPK otherKey = (ImpiegatiPK)o; return (iDImpiegato == otherKey.iDImpiegato); } else return false; } public int hashCode(){ return String.valueOf(iDImpiegato).hashCode(); } }

Il file descrittivo della chiave primaria molto semplice e di fatto, definisce il campo i D I m p i e g a t o come un oggetto serializzabile. E richieta limplementazione di tre metodi necessari al container: File descrittivo della chiave primaria Descriozione Ritorna una descrizione testuale della chiave primaria; nel caso la chiave primaria sia complessa, ossia formata da pi attributi, necessario costruirla. Metodo invocato dal container per comparare chiavi primarie. Metodo necessario al container per poter utilizzare una Hashtable come meccanismo di memorizzazione delle chiavi primarie.

Nome Metodo toString()

equals(Object ) hashCode()

Vediamo ora il codice del bean:


package sampleentity; import javax.ejb.*; import java.sql.*; import java.naming.*; public class ImpiegatiBMPBean implements EntityBean { EntityContext entityContext; Integer iDImpiegato; String cognome; String nome; String telefonoUfficio;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

409

public java.lang.Integer ejbCreate(java.lang.Integer iDImpiegato, String nome, String cognome) throws CreateException { PreparedStatement ps =null; Connection conn =null; try { this.iDImpiegato = iDImpiegato ; this.nome = nome; this.cognome =cognome;

//Connessione al database conn =getConnection(); //Inserimento del record nel database ps = conn.prepareStatement("INSERT INTO IMPIEGATI (IDIMPIEGATO, NOME, COGNOME) VALUES (?,?,?)"); ps.setInt (1,iDImpiegato.intValue()); ps.setString(2,nome); ps.setString(3,cognome); ps.executeUpdate(); // Generazione della chiave primaria return new ImpiegatiPK(iDImpiegato);
} catch (Exception e){ throw new CreateException(e.toString()); } finally { // Chiudiamo la connessione al database try { if (ps !=null) ps.close(); if (conn !=null)conn.close(); } catch (Exception e){} } } public void ejbPostCreate(java.lang.Integer iDImpiegato) throws CreateException { } public void ejbRemove() throws RemoveException { ImpiegatiPK pk =(ImpiegatiPK)ctx.getPrimaryKey(); Integer id =pk.iDImpiegato; PreparedStatement pstmt =null; Connection conn =null; try { conn =getConnection(); // Rimozione del record dal database ps =conn.prepareStatement("DELETE FROM IMPIEGATI WHERE IDIMPIEGATO =?"); ps.setInt (1,id.intValue()); if (ps.execut eUpdate()==0){ throw new RemoveException("Errore in fase di delete"); } } catch (Exception ex){ } finally { // Chiudiamo la connessione al database

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

410

try { if (ps !=null) ps.close(); if (conn !=null)conn.close(); } catch (Exception e){} } } public void setIDImpiegato(java.lang.Integer iDImpiegato) { this.iDImpiegato = iDImpiegato; } public void setNome(java.lang.String nome) { this.nome = nome; } public void setCognome(java.lang.String cognome) { this.cognome = cognome; } public void setTelefonoUfficio(java.lang.String telefonoUfficio) { this.telefonoUfficio = telefonoUfficio; } public java.lang.String getTelefonoUfficio() { return this.telefonoUfficio; } public java.lang.String getCognome(){ return this.cognome; } public java.lang.String getNome(){ return this.nome; } public java.lang.Integer getIDImpiegato(){ return this.iDImpiegato; } public void ejbLoad() { ImpiegatiPK pk =(ImpiegatiPK)ctx.getPrimaryKey(); Integer id =pk.iDImpiegato; PreparedStatement ps =null; Connection conn =null; try { conn =getConnection(); // Otteniamo i dati dal database mediante query SQL ps =conn.prepareStatement("SELECT NOME, COGNOME, TELEFONOUFFICIO FROM IMPIEGATI WHERE IDIMPIEGATO=?"); ps.setInt (1,id.intValue()); ResultSet rs =ps.executeQuery(); rs.next(); this.nome =rs.getString("NOME"); this.cognome =rs.getString("COGNOME"); this.telefonoUfficio =rs.getString("TELEFONOUFFICIO"); } catch (Exception ex){ throw new EJBException(Fallito caricamento da database,ex); } finally { // Chiudiamo la connessione al database try { if (ps !=null) ps.close(); if (conn !=null)conn.close();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

411

} catch (Exception e){} } public void ejbStore() { PreparedStatement ps =null; Connection conn =null; try { // Connession al database conn =getConnection();

// Memorizzazione nel DB ps =conn.prepareStatement( "UPDATE IMPIEGATI SET NOME=?,COGNOME=?,TELEFONOUFFICIO=?", +"WHERE IDIMPIEGATO =?"); ps.setString(1,this.nome); ps.setString(2,this.cognome); ps.setString(3,this.telefonoUfficio); ps.setInt (4,this.iDImpiegato.intValue()); ps.executeUpdate();
} catch (Exception e){ } finally { //Rilascio la connessione con il database try { if (ps !=null) ps.close(); } catch (Exception e){} try { if (conn !=null) conn.close(); } catch (Exception e){} } } public void ejbActivate() { } public void ejbPassivate() { } public void unsetEntityContext() { this.entityContext = null; } public void setEntityContext(EntityContext entityContext) { this.entityContext = entityContext; } public ImpiegatiPK findByPrimaryKey(ImpiegatiPK pk) { PreparedStatement ps =null; Connection conn =null; try { conn =getConnection(); // Otteniamo i dati dal database mediante query SQL ps =conn.prepareStatement("SELECT IDIMPIEGATO FROM IMPIEGATI WHERE IDIMPIEGATO=?"); ps.setString(1,pk.toString()); ResultSet rs = ps.executeQuery();

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

412

rs.next(); // Se non ci sono stati errori ... return pk; } catch (Exception ex){ throw new EJBException(Fallito caricamento da database,ex); } finally { // Chiudiamo la connessione al database try { if (ps !=null) ps.close(); if (conn !=null)conn.close(); } catch (Exception e){} } }

/** * Recupera il riferimento al bean allinterno del connection pool. */ public Connection getConnection() throws Exception { try { Context ctx = new InitialContext(); javax.sql.DataSource ds =(javax.sql.DataSource) ctx.lookup("java:comp/env/jdbc/ejbPool"); return ds.getConnection(); } catch (Exception e){ e.printStackTrace(); throw e; } }
}

In estrema sintesi, si pu vedere che ad ogni operazione effettuata sul sottostante database (creazione, ricerca, update, delete), corrisponde una equivalente definizione esplicita del corrispondente statement SQL, con notevole appesantimento del codice. Si noti il metodo getConnection(): con tale metodo (non obbligatorio, ma utile per incapsulare la logica di connessione) viene effettuato il lookup sul pool delle connessioni, non direttamente sul database; in questo modo il numero di connessioni rimane sotto controllo.

21.16 MessageDriven Beans


I MessageDrive Beans sono strati introdotti nella specifica 2.0 per completare larchitettura J2EE con i paradigmi delle infrastrutture message oriented sfruttando lo strato JMS. Gli schemi concettuali utilizzati sono quelli tipici delle architetture a messaggi; in prima istanza il modello che prevede che un messaggio sia prodotto da un

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

413

ente denominato producer ed utilizzato da uno o pi enti denominati consumer.

Figura 152:

Modello Producer-Consumer

Il provider JMS si pone tra producer e consumer come strato di servizio di gestione dei meccanismi di memorizzazione del messaggio, sincronismo e certificazione della consegna. I modelli utilizzati per limplementazione dello schema logico precedente sono due: punto-punto e publisher-subscriber. Il modello punto-punto prevede un meccanismo di sincronizzazione dei messaggi basato sul concetto di coda. I messaggi entrano nella coda e ne escono, nellordine in cui sono entrati, solo quando il consumer, che deve conoscere i riferimenti esatti della coda per potersi agganciare ad essa, li preleva. Il modello publisher-subscriber prevede, invece, il concetto di topic, ossia di argomento: il producer produce dei messaggi a tema, con uno specifico topic, ed il consumer si registra per le notifiche solo sui messaggi aventi quel tema. In entrambi i casi il provider JMS si occupa di:

avvertire il consumer quando un nuovo messaggio arrivato, evitando che questi rimanga impegnato in inutili cicli di lettura sulla coda; mantenere il messaggio nella lista di consegna fino a che tutti i consumer che debbono ricevere il messaggio non lo hanno effettivamente ricevuto; questo implica limplementazione di logiche store and forward. certificare, con meccanismi di acknowledge, che il consumer abbia realmente ricevuto il messaggio in forma corretta.

Questo tipo di architettura presenta dei notevoli vantaggi rispetto alla normale chiamata remota di un metodo (ad esempio, utilizzando RMI-IIOP). Le chiamate RMI-IIOP sono essenzialmente delle chiamate in cui un client

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

414

invoca un metodo di un server ed attende, in modo sincrono, che questi gli risponda (e nel caso di malfunzionamento del server, il client deve attendere fino al timeout, senza peraltro, ottenere la risposta attesa). Nella architetture a messaggi (Message Oriented) il consumer ed il producer sono enti disaccoppiati, ciascuno effettua il proprio lavoro in modo autonomo e lincontro avviene solo per il tramite del middleware. Il consumer non deve, come il generico client RMI-IIOP, attendere in modo sincrono la risposta dal server, ma gestisce in modo autonomo i momenti di lettura dei messaggi; allo stesso modo il server non obbligato a produrre i risultati delle proprie elaborazioni solo su richiesta, ma pu farlo ottimizzando i propri task. E, inoltre, importante sottolineare la sicurezza e la semplicit di implementazione che vengono offerti dallo strato middleware: il programmatore non deve occuparsi di costruire un protocollo di handshake per garantirsi che la comunicazione client-server sia andata a buon fine, ma questo viene garantito dai servizi del middleware; inoltre il middleware fornisce anche gli strumenti di gestione delle eccezioni (nel caso, ad esempio, la comunicazione dovesse interrompersi o il server dovesse andare in blocco). Infine, ma non meno importante, la considerazione che un singolo task produttore di messaggi pu servire un numero qualsiasi di consumer di messaggi, eliminando le problematiche inerenti la gestione multitask o della programmazione concorrente. Il modello MessageDrivenBeans in grado di sfruttare tutte questi vantaggi in modo completo, dal momento che il deploy del bean avviene nellapplication server, che generalmente contiene un middleware JMS. Dal punto di vista dello sviluppo, la capacit offerta dal provider JMS di avvertire automaticamente il consumer dellarrivo di un messaggio la principale caratteristica sfruttata nellambito dei MessageDrivenBeans. Un MessageDrivenBean un componente che non contiene elementi di logica applicativa, ma sfrutta la intrinseca caratteristica di essere agganciato, in modo nativo, ad un provider JMS. Il suo scopo quello di essere un concentratore di messaggi, dal momento che il suo ruolo principale quello del consumer di messaggi. Per linterfaccia MessageDrivenBean (che definisce il bean vero e proprio) i metodi da definire sono: Interfaccia MessageDrivenBean Nome metodo Descrizione public void ejbCreate() Definisce la logica da implementare in fase di creazione del bean public v o i d Definisce la creazione del contesto di riferimento setMessageDrivenContext( del bean MessageDrivenContext context)

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

415

public v o i d E il metodo che, chiamato dallAS, avverte il onMessage(Message bean che un nuovo messaggio sta arrivando messaggio) public void ejbRemove() Definisce le politiche da attuare nella rimozione delloggetto Il metodo che caratterizza i MessageDriven Beans lonMessage(). Con tale metodo, il container avverte il bean dellarrivo di un nuovo messaggio, e lo passa come parametro del metodo. Questo metodo lunico in cui va scritta un p di logica applicativa, in particolare va scritto il codice di gestione del messaggio. Qualora la logica applicativa sia onerosa e complessa, conveniente utilizzare un SessionBean dedicato, e liberare il MessageDriven da ogni onere. Il MessageDriven Bean, infatti, non fa (e non deve fare) altro che recuperare i messaggi dalla coda o dalla topic cui agganciato, e in questo suo attendere il messaggio, esso lavora in modo sequenziale. Ogni istanza del bean pu lavorare uno ed un solo messaggio alla volta; la logica di gestione di pi messaggi concorrenti deve essere delegata al container, che ha la responsabilit di istanziare un numero sufficiente di bean.

21.17 Cenni sul deploy


Come detto in precedenza, il processo di deploy verte sulla installazione degli EJB allinterno del loro contesto di riferimento, ossia lAS. Principalmente il processo consiste nella creazione di un archivio (generalmente un file .jar) contenente tutto lalbero delle classi degli EJB compilate, completo di tutti i componenti generati durante la fase di compilazione. Insieme alle classi, necessario fornire allAS delle ulteriori informazioni inerenti i bean e le direttive su come allocarli in memoria. Ad esempio, nellesempio di session bean precedentemente descritto, non specificato se tale bean sia stateless o stateful. N specificato quale sia il JNDI name del bean cos da poterlo invocare da remoto. Queste informazioni, insieme, ad altre pi specifiche, vanno dichiarate in opportuni file detti descrittori del deploy. Tali file altro non sono che semplici file XML da includere allinterno del jar ove sono archiviate le classi. Il principale file dedicato al deploy di EJB il file ejb-jar.xml, ove vengono dichiarati tutti gli EJB inclusi nel jar e vengono inserite le informazioni necessarie allAS per il deploy. Di seguito presentato un esempio di tale file ove sono presenti le direttive di deploy per un session bean ed un entit bean:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

416

<?xml version="1.0"?> <!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/j2ee/dtds/ejb-jar_2_0.dtd'> <ejb-jar> <description></description> <enterprise-beans> <session> <display-name></display-name> <ejb-name>SampleSessionBean</ejb-name> <home>SampleSessionHome</home> <remote>SampleSession</remote> <ejb-class>SampleSessionBean</ejb-class> <session-type>Stateful</session-type> <transaction-type>Container</transaction-type> </session> <entity> <display-name></display-name> <ejb-name>SampleEntityBean</ejb-name> <home>SampleEntityHome</home> <remote>SampleEntity</remote> <ejb-class>SampleEntityBean</ejb-class> <persistence-type>Bean</persistence-type> <prim-key-class>SampleEntityPK</prim-key-class> <reentrant>False</reentrant> </entity> </enterprise-beans> <relationships> <ejb-relation> <ejb-relation-name></ejb-relation-name> <ejb-relationship-role> <multiplicity>1</multiplicity> <relationship-role-source> <description></description> <ejb-name></ejb-name> </relationship-role-source> </ejb-relationship-role> <ejb-relationship-role> <multiplicity>1,1</multiplicity> <relationship-role-source> <description></description> <ejb-name>SampleEntity</ejb-name> </relationship-role-source> </ejb-relationship-role> </ejb-relation> </relationships> <assembly-descriptor> <container-transaction> <method> <ejb-name>SampleSessionBean</ejb-name> <method-name>*</method-name> </method>

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

417

<trans-attribute>NotSupported</trans-attribute> </container-transaction> <container-transaction> <method> <ejb-name>SampleEntityBean</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>

Nella prima parte del file presente il tag <enterprise-beans>, al cui interno sono definiti i nomi del bean, delle sue interfacce home e remote (in questo caso non si fatto uso di local). Per il primo bean, il session SampleSessionBean, si noti il tag <Session-type> il cui valore indica se il bean stateful o stateless. Per il secondo bean, lentity SampleEntityBean, si noti, invece, la presenza del tag <persistence-type> che indica se si tratta di BMP o CMP. La generazione dei descrittori XML per il deploy , generalmente, a carico degli strumenti di sviluppo, i quali sono in grado di generare automaticamente sia lejb-jar.xml , sia gli eventuali altri file necessari per il deploy su AS specifici. Generalmente ogni AS, infatti, necessita di informazioni specifiche, che vengono inserite in altri file secondo le direttive fornite dal vendor dellAS. Unultima osservazione relativa alle direttive JNDI. Normalmente queste direttive sono presenti in un altro file XML, denominato jndi-definitions.xml, gi presente nellAS e che va integrato con le informazioni relative ai beans dei quali si sta effettuando il deploy.

21.18 Il linguaggio EJB-QL


Nella versione 2.0 della specifica EJB la SUN ha introdotto un linguaggio simile allSQL definire le query che riguardano gli EJB, da utilizzare sia allinterno del codice (nel caso di bean BMP) sia nei file di deploy. Questo linguaggio una sorta di SQL ad oggetti, con sintassi simile allo standard SQL, e che viene tradotto in SQL standard dal container. Senza approfondire tutti i dettagli del linguaggio, vediamo un semplice esempio. Supponiamo di voler effettuare, in EJB-QL, una semplice query sulla gi citata tabella IMPIEGATI, per trovare tutti gli impiegati con un certo nome; la query EJB-QL :
SELECT OBJECT(a) FROM IMPIEGATI AS a WHERE NOME = ?1

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

418

Come si nota immediatamente, la differenza fondamentale rispetto allSQL standard, che il ritorno della query non unelenco di attributi ma un oggetto (o una collezione di oggetti), che rappresenta il corrispondente recod.

21.19 Limportanza degli strumenti di sviluppo


Quando Java fece la sua comparsa nel 1995, non esistevano ambienti e strumenti di sviluppo specifici; di solito era sufficiente utilizzare un qualsiasi editor di testo e il Java Development Kit, per scrivere applet o piccole application era pi che sufficiente. Con il crescere delle aree di interesse coperte da linguaggio, le applicazioni sviluppate sono divenute sempre pi complesse ed articolate, rendendo utile lutilizzo di strumenti di sviluppo dedicati al linguaggio. Se questo normalmente vero nella realizzazione di interfacce grafiche, dove la presenza di strumenti GUI migliora notevolmente la produttivit, questo forse ancora pi vero nello sviluppo di componenti EJB. La complessit di tali componenti, in termini di numero di classi generate, generazione dei descrittori XML e creazione di ambienti di test, rende praticamente necessario lutilizzo di un buono strumento di sviluppo Java. Si consideri che uno strumento di sviluppo Java: crea automaticamente le strutture dei files degli EJB; spesso presenta utilit grafiche per la creazione rapida di metodi e propriet, generando automaticamente le corrispondenti interfacce home, local e remote; presenta utility per automatizzare la connessione a database e la creazione di query; presenta utility per la creazione automatica di bean CMP a partire dalla struttura della tabella; presenta utility per effettuare la comunicazione con provider JMS e/o CORBA; in grado di effettuare una compilazione completa ed incrementale; in grado di generare i descrittori XML standard per il deploy; fornisce utilit per la creazione automatica dellarchivio di cui effettuare il deploy; quando integrato con un certo AS, consente di generare anche i descrittori custom per il deploy sullo specifico AS; quando integrato con un certo AS, consente di effettuare il deploy in automatico sullAS; fornisce utility per la creazione automatica di test client, per verificare rapidamente le funzionalit dei componenti.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

419

22

INTRODUZIONE AI WEBSERVICES

22.1 Introduzione
La piattaforma J2EE la soluzione Sun per la costruzione di infrastrutture informatiche aziendali, grazie alle caratteristiche di integrazione con i sistemi legacy, cross platform, scalabilit ed i meccanismi di recovery e sicurezza. E pertanto, uninfrastruttura completa per la costruzione di quello che viene definito, con una frase che va molto di moda, il sistema nervoso digitale dellazienda. Ma cosa succede quando necessario far comunicare aziende differenti, appartenenti a mercati diversi ed aventi infrastrutture tecnologiche proprietarie (interoperabilit fra sistemi)? Le architetture Java Enterprise stanno rapidamente prendendo piede, ma rappresentano ancora un segmento di mercato specifico e non molto diffuso. Molte aziende utilizzano altre piattaforme tecnologiche, ed anche quando riconoscono il valore dellarchitettura Java cercano (dal loro punto di vista, giustamente) di preservare gli investimenti rinviando la costruzione di nuove architetture sino al momento in cui diventa effettivamente necessario. Questo problema di non facile soluzione, e, fino ad ora, era stato oggetto di analisi e sviluppi specifici, non essendo presente alcuna piattaforma tecnologica comune. La grande svolta nel processo di standardizzazione delle comunicazioni, avvenuta con lavvento e la diffusione su larga scala dei protocolli TCP/IP prima e HTTP poi, ha semplificato, ma non ha risolto, i problemi di incomunicabilit dei sistemi. Per fare un paragone, il fatto che tutti gli uomini usino le onde sonore come strumento per trasmettere, abbiano le orecchie per decodificare le onde sonore e abbiano la stessa struttura celebrale per interpretare i segnali giunti dallorecchio non garantisce che due persone possano riuscire a parlare fra loro. Immaginate di voler avere informazioni su un prodotto venduto in Giappone ma non ancora in Italia (e, supponiamo che ovviamente, ci siano negozi e/o fornitori pronti ad esaudire le vostre richieste). Il problema pu esser banalmente decomposto nei seguenti tre passi: primo, cercare quali posti possano avere linformazione da voi cercata; secondo, una volta scelta una fonte, verificare se in effetti quella fonte attendibile e contiene la risposta cercata; terzo, avviare la comunicazione, effettuare la richiesta ed ottenere linformazione. Ma la cosa pi importante trovare un linguaggio di comunicazione comprensibile, insomma capire e farsi capire! Un italiano ed un giapponese non riusciranno mai a comunicare, a meno che uno dei due non conosca la lingua dellaltro. Oppure entrambi devono conoscere, riconoscere, ed essere

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

420

disposi ad usare nella comunicazione un codice comune, ad esempio una terza lingua come linglese. Lidea alla base dei Web Services proprio questa, ossia cercare di definire un codice comune per le applicazioni informatiche, rispondendo alle domande, che dal lato cliente sono:

1. dove trovare ci che si cerca? 2. come fare a certificare che un certo ente offre i servizi richiesti? 3 . come inviare una richiesta comprensibile ed assicurarsi di saper interpretare la risposta? 4. con che lingua comunicare?
e rispettivamente dal lato fornitore dellinformazione sono:

1. come fare a farsi trovare dai clienti? 2 . come far capire ai potenziali utenti le possibilit offerte dai servizi offerti? 3. come consentire ai clienti di effettuare, in modo congruo e sicuro, le richieste, inviando risposte a loro comprensibili? 4. con che lingua comunicare?
Come vedremo, i Web Services riescono in questo compito, utilizzando strumenti standard e definendo dei protocolli di comunicazione perfettamente rispondenti allo scopo ed implementabili in qualunque linguaggio (Java, naturalmente, compreso). Lobiettivo di questo capitolo quello di fornire una panoramica sulle potenzialit del linguaggio Java nella implementazione dei Web Services, indicando gli strumenti e le tecnologie necessarie, ma rimanendo necessariamente ad un livello alto, dal momento che numerosi sono gli elementi che interagiscono. Ipotizziamo, inoltre, che chi arrivi a leggere questo capitolo del libro sia ormai giunto ad un livello di conoscenza del linguaggio sufficiente per poter iniziare ad approfondire largomento anche senza lausilio dettagliato di una guida di base.

22.2 Definizione di Web Service


Un Web Service un servizio disponibile in un qualsiasi punto della rete ed erogato sui protocolli HTTP e/o SMTP secondo una architettura Service Oriented (SOA Service Oriented Architecture). Larchitettura service oriented prevede linterazione di tre enti distinti: un fornitore di servizi, il quale proprietario e responsabile del software di erogazione del servizio un cliente dei servizi erogati dal fornitore

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

421

un ente, detto broker, che offre capacit di catalogazione e ricerca dei servizi offerti dai fornitori.

Figura 153:

Architettura service oriented

Un Web Service caratterizzato da due elementi: il servizio, inteso come implementazione della logica applicativa la descrizione del servizio, intesa come linterfaccia del servizio stesso.

La descrizione del servizio codificata in linguaggio XML e segue un insieme di standard che ne definiscono la formalizzazione, il riconoscimento e le regole di interoperabilit. Come si evince dalla figura 1, le operazioni definite tra gli enti in gioco nellarchitettura a servizi sono tre; la specifica dei Web Services individua meccanismi standard per codificare ciascuno di questi tre flussi. La pubblicazione dei servizi coinvolge il broker ed il fornitore. In questo flusso il broker mette a disposizione i suoi servizi specifici e si dichiara pronto ad accettare un nuovo servizio, secondo le specifiche che il fornitore deve dettare. La specifica che sovrintende questa comunicazione denominata UDDI (Universal Description, Discovery and Integration). La specifica UDDI consente al fornitore di definire le regole per la pubblicazione del servizio. Anche la ricerca dei servizi gestita dalle regole descritte nella specifica

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

422

UDDI. In questo caso, UDDI ci consente di vedere il broker come una sorta di motore di ricerca cui chiedere una lista di fornitori che rispondono ai requisiti da noi cercati. Per il processo di collegamento al servizio, i Web services standard ci forniscono due strumenti: il linguaggio WSDL (Web Services Description Language Linguaggio per la descrizione dei Web Services) ed il protocollo applicativo SOAP (Simple Object Access Protocol Semplice protocollo di accesso ad oggetti). Il linguaggio WSDL fornisce una specifica formale di descrizione, in formato XML, di un Web Services in termini comprensibili al client, e che consentono a questultimo di avere tutte le informazioni necessarie per collegarsi al fornitore, capire cosa fa il servizio, costruire le richieste (con descrizione dei tipi dei parametri), interpretare le risposte (con descrizione dei parametri ritornati e degli eventuali codici di errore). Il protocollo SOAP lanima dellarchitettura Web Services, in quanto rappresenta il vero protocollo contenente le regole per la comunicazione fra cliente e fornitore.

Figura 154:

Protocolli di collegamento

22.3 Il protocollo SOAP


SOAP il protocollo di riferimento per lo scambio di informazioni tra fornitore di servizi e cliente. E basato sul linguaggio XML, di cui rappresenta una estensione funzionale alla descrizione dei messaggi, ed caratterizzato dal fatto di appoggiarsi sul protocollo HTTP (o SMTP), dalla intrinseca capacit di far passare le richieste attraverso i firewall, dalla capacit di essere autodescrittivo e completo, in termini di capacit di trasporto, meccanismi di sicurezza e capacit di espressione di meccanismi differenti. E, infatti

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

423

possibile utilizzare SOAP per erogare semplici servizi Web, per effettuare chiamate di procedure remote (RPC), per scambiare documenti, per istaurare complessi colloqui tra sistemi interconnessi solo da una connessione TCP/IP. Come dice la definizione stessa, SOAP , inoltre, un protocollo basato sulla comunicazione tra oggetti, dal momento che in grado di descrivere completamente e correttamente linvocazione di un metodo su un oggetto remoto. Il cuore della tecnologia SOAP risiede nella struttura dei messaggi.

Figura 155:

Struttura dei messaggi SOAP

Un messaggio SOAP : un messaggio XML strutturato in un envelope, a sua volta composto da un body e da un header allinterno dellheader possono essere definite le direttive da inviare al client per il processamento del messaggio

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

424

allinterno del body vengono scritte le informazioni relative alla comunicazione vera e propria, in blocchi XML logici

22.4 Gli strumenti Java


Java contiene numerosi packages di classi realizzate con lo scopo di agevolare la realizzazione di Web Services. Questi strumenti, introdotti successivamente al rilascio della versione 1.3 di Java ed integrati nella versione 1.4, rappresentano il completamento dellarchitettura J2EE, che pertanto diventa completa anche dal punto di vista dello sviluppo di piattaforme service oriented. Tutto questo mantenendo inalterate le caratteristiche di scalabilit e portabilit della piattaforma ed aumentandone la flessibilit di disegno architetturale e di integrazione con sistemi eterogenei. Di seguito verr svolta una breve panoramica di sintesi su questi strumenti.

22.5 JAXP
JAXP (Java Api for Xml Processing) rappresenta lo strumento per il trattamento (processing) di file, messaggi o comunque dati scritti in XML. JAXP consente di effettuare il parsing di un messaggio XML e fornirne una rappresentazione comprensibile in linguaggio Java; i modelli utilizzati sono due: SAX (Simple API for XML Parsing) il modello che prevede il parsing di uno stream XML interpretando i dati mano a mano che arrivano; DOM (Document Object Model) il modello che consente di creare una rappresentazione ad oggetti dellintera struttura dellXML.

La scelta del modello da utilizzare delegata al programmatore ed , dal punto di vista della capacit di interpretazione dei dati, del tutto equivalente. SAX un modello pi semplice da utilizzare, ed ha il vantaggio di consentire la creazione di una arbitraria rappresentazione ad oggetti dellXML durante la sua lettura. In questo modo il programmatore pu utilizzare le informazioni contenute nellXML per popolare una rappresentazione ad oggetti da lui definita. Nel caso del DOM, invece, la rappresentazione ad oggetti gi data (anche se, essendo generale, potrebbe non essere conforme alle richieste), ma ha il vantaggio di essere gi pronta anche ad eseguire operazioni complesse, quali la ricerca di un elemento o di istanze. Si noti, inoltre, come tale rappresentazione non sia specifica per Java, ma standard per ogni linguaggio. Il package JAXP fornisce anche strumenti per la trasformazione o la creazione di XML integrati e trasformati con la tecnologia XSLT (XML Stylesheet Language for Trasformations).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

425

La struttura dei package JAXP la seguente: Struttura dei package JAXP Descrizione E il package che contiene le interfacce generiche per il trattamento dei modelli SAX e DOM. Package che contiene le classi per la definizione dello standard DOM Package che contiene API standard SAX Contiene API utili per le trasformazioni XSLT

Package javax.xml.parsers

org.w3c.dom org.xml.sax javax.xml.transform

Le API per effettuare il parsing di documenti XML sono contenute nel package java.xml.parsers, e definiscono degli oggetti generici per lutilizzo dei modelli DOM e SAX; queste classi definiscono interfacce standard che debbono essere implementate da coloro che realizzano parsers13.

22.6 JAXR
JAXR (Java Api for XML Registries) sono le API java per lintegrazione e/o la creazione di servizi di registry per la ricerca e pubblicazione di Web Services. Le API JAXR sono costituite da: un JAXR client, ossia un set di API che consentono ad un client di accedere ai servizi di registry di un provider remoto un JAXR provider, ossia una serie di interfacce per la creazione di un broker

Le API client di JAXR consentono: di effettuare il lookup su un broker di servizi, ottenendo una connessione; di effettuare ricerche sui servizi presenti nella struttura del provider, partendo dalla ricerca dellorganizzazione che fornisce il servizio (filtrando le ricerche per tassonomie predefinite quali nome, anche non completo, dellorganizzazione e per categoria merceologica dellazienda o del servizio) e ottenendo, infine, la lista dei servizi offerti dallazienda cercata; di pubblicare (qualora il provider lo consenta) servizi, creando e gestendo i dati relativi allorganizzazione e al tipo di classificazione (tipo

13 Per utilizzare JAXP , pertanto, necessario avere una implementazione di tali parser; i pi famosi sono quelli forniti dallApache Group, in particolare Xerces.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

426

di servizio e tipo di azienda, ma anche di definire classificazioni arbitrarie)

22.7 JAXM
JAXM (Java API for XML Messaging) un insieme di packages contenenti API per la creazione di messaggi scritti in XML. La creazione e linvio di messaggi XML secondo JAXM sono conformi al protocollo SOAP, e la struttura dei package prevede il package javax.xml.soap, che include le API per creare messaggi SOAP di richiesta e risposta, e il package javax.xml.messaging necessario per utilizzare un messaging provider (ossia un sistema che, in pieno stile MOM, provvede a recapitare messaggi a vari destinatari) e che pertanto fornisce un sistema unidirezionale di invio messaggi.

22.8 JAX-RPC
JAX-RPC fornisce una serie di API per effettuare chiamate ed eseguire procedure (RPC) su client remoti. JAX-RPC rappresenta un protocollo basato sulla specifica SOAP: ancora basato su XML, definisce le strutture di envelope e body secondo SOAP e le regole per la definizione delle chiamate remote, per il passaggio dei parametri di input, per riconoscere i risultati e gli eventuali messaggi derrore. Il vantaggio di questo sistema consiste nella estrema semplicit di sviluppo, sia dal lato client che da quello server, e nella potenza del paradigma RPC, mentre lo svantaggio risiede nel dover ricreare una struttura di architettura distribuita (compreso il concetto di stub), e che pertanto richiede la presenza di un sistema che funziona da middleware (detto JAXP-RPC runtime).

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

427

Parte Terza Design Pattern

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

428

23
23.1 Introduzione

DESIGN PATTERN

Pattern Mining significa raccogliere lesperienza degli analisti e dei programmatori per creare librerie di oggetti riutilizzabili. Lidea alla base di questa disciplina semplice: collezioniamo e cataloghiamo gli oggetti e le loro interazioni, se rappresentano soluzioni a problemi di interesse comune. Gli oggetti insieme alle loro interazioni sono detti Design Pattern. La storia dei Design Pattern inizia allinizio del 1980 quando Smaltalk era il linguaggio ad oggetti pi utilizzato. Come abbiamo visto allinizio del libro, in quel periodo il paradigma ad oggetti non era ancora diffuso ma, la crescente necessit di dotare sistemi di interfacce utente, spingeva sempre pi in questa direzione. Fu nel 1988 che due analisti, Krasner e Pope, crearono il primo pattern per Smaltalk chiamato Model-View-Controller che, divideva il problema dellinterfaccia utente nelle tre parti descritte nella prossima figura.

Figura 156:

Model-View-Controller

1. Data Model: contiene le logiche di business di un programma; 2. View: rappresenta linterfaccia utente; 3. Controller: rappresenta lo strato di interazione tra interfaccia e logiche di business.
Ogni strato del modello un oggetto a se stante con le sue regole per lelaborazione dei dati, ogni sistema ha caratteristiche proprietarie che definiscono tali regole, ma, il modello comune a tutti i sistemi che necessitano di interfacce utente.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

429

23.2 Cosa un design pattern


In molto hanno provato a definire con esattezza cosa sia un pattern e la letteratura contiene numerose definizioni:

Design patterns constitute a set of rules describing how to accomplishcertain tasks in the realm of software development. (I Design Pattern sono collezioni di regole che descrivono come eseguire alcuni compiti di programmazione) (Pree, 1994)

Design patterns focus more on reuse of recurring architectural design themes, while frameworks focus on detailed design and implementation. (I framework focalizzano lattenzione sul disegno e limplementazione di una architettura, I Design Pattern sul riutilizzo di disegni architetturali ricorrenti.) (Coplien & Schmidt, 1995).

A pattern addresses a recurring design problem that arises in specific design situations and presents a solution to it (Un pattern riconduce un problema di disegno ricorrente ad una specifica situazione e ne fornisce la soluzione.) (Buschmann, et. al. 1996)

Patterns identify and specify abstractions that are above the level of single classes and instances, or of components. (I pattern identificano e definiscono le astrazioni di classi, oggetti e componenti.) (Gamma, et al., 1993)

Tutte le definizioni concordano con lidentificare i pattern con soluzioni comuni ad un problema di disegno ricorrente.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

430

Questa definizione richiede per di essere approfondita; in particolare ci dobbiamo chiedere cosa sia esattamente un problema. In generale, un problema un quesito da chiarire, qualcosa che deve essere identificato, capito e risolto. In particolare, un problema un insieme di cause ed effetti. Un problema a sua volta dipendente da un contesto ovvero, dalle condizioni al contorno, che ne determina le cause. Risolvere un problema significa quindi identificare le risposte inquadrandole allinterno del contesto di riferimento. I pattern rappresentano quindi la soluzione migliore a problemi architetturali ricorrenti allinterno di determinati contesti applicativi e forniscono la migliore soluzione ai problemi che i programmatori incontrano tutti i giorni in fase di disegno di una applicazione:

1 . Identificazione dei concetti e la determinazione degli oggetti appropriati; 2. La specifica dellinterfaccia degli oggetti; 3. La specifica dellimplementazione degli oggetti; 4. La definizione delle interazioni tra gli oggetti; 5. Riutilizzabilit del codice, manutenibilit, scalabilit e prestazioni.

23.3 La banda dei quattro


Basta effettuare una semplice ricerca in internet per accorgerci che esistono migliaia di pattern a tutti i possibili livelli di astrazione. Dieci anni dopo la nascita del primo pattern, alla fine del 1990, si inizi formalmente a catalogare i Design Pattern e alla fine del 1995 fu pubblicato il libro Elements of reusable software scritto da quattro autori (Gamma, Helm, Jhonson e Vlissides) noti come Gang of Four (banda dei quattro). I ventitr pattern raccolti allinterno del libro, oggi noti come G o F sono considerati i fondamentali e sono suddivisi nei tre gruppi schematizzati nella prossima tabella: Pattern GoF Structural

Creational

Behavioral

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

431

Abstract Factory Builder Factory Method Prototype Singleton

Adapter Bridge Composite Decorator Faade Flyweight Proxy

Chain of responsibility Command Interpreter Iterator Mediator Memento Observer State Strategy Template Method Visitor

Nei prossimi capitoli introdurremo i ventitr pattern GoF. Per una trattazione dettagliata del problema in ogni modo consigliabile fare riferimento alla letteratura come riccamente suggerito allinterno della bibliografia.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

432

24
24.1 Introduzione

CREATIONAL PATTERN

Il linguaggio Java consente di creare oggetti semplicemente utilizzando loperatore new. In molti casi la natura di un oggetto pu variare e risulta comodo poter astrarre il processo di allocazione di una classe, utilizzando classi particolari dette creatrici. I pattern creational descrivono i modi migliori per creare oggetti allinterno di unapplicazione. Limportanza di questi pattern sta nel fatto che, unapplicazione deve essere indipendente dal modo in cui gli oggetti sono creati, costruiti e rappresentati. I pattern creational sono cinque: Creational Pattern Descrizione Fornisce uninterfaccia per creare e tornare oggetti appartenenti ad una stessa famiglia, selezionata tra un gruppo di famiglie. Fornisce una classe che, in base ad alcuni attributi di input, ritorna un oggetto scelto tra un insieme di oggetti derivati da una classe astratta. Separa la costruzione di un oggetto complesso dalla sua rappresentazione. In questo modo possibile creare differenti rappresentazioni dipendenti dalle caratteristiche dei dati da rappresentare. Crea un clone di un oggetto invece di allocare una nuova classe dello stesso tipo. Fornisce un modello per le classi delle quali deve esserci sempre una ed un solo oggetto in memoria. Fornisce inoltre un singolo punto di accesso alloggetto allocato.

Nome Abstract Factory

Factory Method

Builder

Prototype Singleton

24.2 Factory Method


Il Factory Method, il pattern che in base ad alcuni attributi di input, ritorna un oggetto scelto tra un insieme di oggetti. Tipicamente, gli oggetti sono selezionati tra un insieme di classi derivate da una classe astratta e, di conseguenza, aventi un metodo in comune.

Il problema:

Una classe non pu decidere in anticipo quale tipo di oggetto creare;

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

433

Una classe deve utilizzare le sue sottoclassi per determinare che tipo di oggetto creare; Abbiamo necessit di incapsulare le logiche necessarie alla creazione di nuovi oggetti.

Il disegno:

Figura 157:

Factory Method

Come lavora:
Mediante linterfaccia C r e a t o r , eseguendo il metodo factoryMethod deleghiamo alla classe ConcreateCreator la creazione di un oggetto di tipo Product come schematizzato nel prossimo sequence diagram.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

434

Figura 158:

Factory Method Sequence Diagram

Poich la classe Product una classe astratta, possibile crearne tante specializzazioni secondo le necessit imposte dal contesto applicativo. Il metodo factoryMethod di ConcreteCreator potr essere specializzato affinch, secondo gli attributi, possa decidere quale classe di tipo Product allocare e ritornare allutente. Mediante questo pattern, possiamo facilmente ottenere istanze diverse di una classe appartenente alla famiglia definita da Product, incapsulando allinterno della classe ConcreateCreator le logiche necessarie a creare loggetto adeguato a soddisfare le richieste dellutente.

Il codice:
Nel prossimo esempio, linterfaccia TokenCreator fornisce il metodo getTokenizer(String) che rappresenta il nostro metodo factory. Il metodo accetta una stringa come argomento e a seconda che la stringa contenga un punto od una virgola allinterno, ritorna una istanza rispettivamente di DotTokenizer o CommaTokenizer di tipo Tokenizer. Le classi DotTokenizer e CommaTokenizer costruiscono un oggetto di tipo java.util.StringTokenizer necessario a suddividere la stringa in token separati rispettivamente da un punto o da una virgola. Mediante il metodo getTokens() , entrambe restituiscono una enumerazione che rappresenta lelenco dei token della stringa.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

435

Figura 159:

Esempio di factory method

package src.pattern.factory; public interface TokenCreator { Tokenizer getTokenizer(String tokenlist); } package src.pattern.factory; public class ConcreteTokenCreator implements TokenCreator { public Tokenizer getTokenizer(String tokenlist){ if(tokenlist.indexOf(",")!=-1) return new CommaTokenizer(tokenlist); if(tokenlist.indexOf(".")!=-1) return new DotTokenizer(tokenlist); return null; } } package src.pattern.factory; import java.util.Enumeration; public interface Tokenizer { Enumeration getTokens(); }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

436

package src.pattern.factory; import java.util.Enumeration; import java.util.StringTokenizer; public class DotTokenizer implements Tokenizer { public DotTokenizer(String param) { setParam(param); } public Enumeration getTokens(){ StringTokenizer strtok = new StringTokenizer(getParam(),"."); return strtok; } public String getParam(){ return param; } public void setParam(String param){ this.param = param; } private String param; } package src.pattern.factory; import java.util.Enumeration; import java.util.StringTokenizer; public class CommaTokenizer implements Tokenizer { public CommaTokenizer (String param) { setParam(param); } public Enumeration getTokens(){ StringTokenizer strtok = new StringTokenizer(getParam(),","); return strtok; } public String getParam(){ return param; } public void setParam(String param){ this.param = param; } private String param; }

Infine, il metodo main della applicazione:


package src.pattern.factory; import java.util.Enumeration;

/** * stampa a video la lista di token di due stringhe. * I token sono separati rispettivamente * da un punto ed una virgola. */ public class TokenPrinter { public static void main(String[] argv) { TokenCreator token = new ConcreteTokenCreator(); String dotSeparatedToken = "a,b,c,d,e,f,g"; String commaSeparatedToken = "h.i.l.m.n.o.p";

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

437

Enumeration dotTokenList =token.getTokenizer(dotSeparatedToken).getTokens(); Enumeration commaTokenList = token.getTokenizer(commaSeparatedToken).getTokens();

//stampo i teken della prima stringa stringa while(dotTokenList.hasMoreElements()) System.out.print(dotTokenList.nextElement()+" ");


System.out.println();

//stampo i teken della seconda stringa stringa while(commaTokenList.hasMoreElements()) System.out.print(commaTokenList.nextElement()+" ");


System.out.println(); } }

24.3 Abstract Factory


Abstract Factory Pattern rappresenta unastrazione del pattern precedente e fornisce uninterfaccia con un metodo factory che, a sua volta, ritorna una famiglia di classi logicamente correlate tra loro. Un esempio tipico di questo pattern rappresentato in Java dal meccanismo di selezione delle sembianze di uninterfaccia utente. In particolare, le librerie swing di Java consentono di selezionare laspetto delle componenti di interfaccia fornendo diverse possibilit: Motif, Machintosh o Windows9x. Per realizzare questo meccanismo, viene utilizzata una classe factory cui segnaliamo, tramite un apposito metodo, quale sar laspetto desiderato per linterfaccia utente. La classe factory a sua volta ci ritorner una famiglia di oggetti, appartenenti allinsieme delle componenti, aventi in comune laspetto grafico.

Il problema:

Una classe non pu decidere in anticipo quale tipo di oggetto creare; Gli oggetti creati a loro volta appartengono ad un insieme di oggetti logicamente correlati tra loro. Abbiamo necessit di incapsulare le logiche necessarie alla creazione di nuovi oggetti.

Il disegno:

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

438

Figura 160:

Abstract Factory

Come lavora:
Questo pattern fornisce linterfaccia AbstractFactory contenente i metodi per la creazione delle famiglie di oggetti da restituire. Limplementazione di questi metodi fornita dalle sottoclassi Conc r e at e F ac t or y . Le interfacce AbstractProduct, rappresentano le famiglie di oggetti allinterno delle quali selezionare gli oggetti da ritornare rappresentati dalle classi ConcreteProduct.

Il codice:
Nel nostro esempio abbiamo due tipi di prodotti (Figura precedente):

AbstractProductA: ConcretePruductA1, ConcretePruductA2 AbstractProductB: ConcretePruductB1, ConcretePruductB2

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

439

che implementano le relative interfacce AbstractProduct . Gli oggetti ConcreteFactory sono a loro volta due e ritornano, rispettivamente, oggetti di tipo 1 (A1, B1) ed oggetti di tipo 2 (A2, B2) dalle famiglie di oggetti A e B
package src.pattern.abstractfactory.example; public interface AbstractFactory { AbstractProductA createAbstractProductA(); AbstractProductB createAbstractProductB(); } package src.pattern.abstractfactory; public class ConcreteFactory1 implements AbstractFactory { public AbstractProductA createAbstractProductA(){ return new ConcreteProductA1(); } public AbstractProductB createAbstractProductB(){ return new ConcreteProductB1(); } } package src.pattern.abstractfactory; public class ConcreteFactory2 implements AbstractFactory { public AbstractProductA createAbstractProductA(){ return new ConcreteProductA2(); } public AbstractProductB createAbstractProductB(){ return new ConcreteProductB2(); } } package src.pattern.abstractfactory; public interface AbstractProductA { } package src.pattern.abstractfactory; public class ConcreteProductA1 implements AbstractProductA { public ConcreteProductA1() { System.out.println("ConcreteProductA1"); } } package src.pattern.abstractfactory; public class ConcreteProductA2 implements AbstractProductA { public ConcreteProductA2() { System.out.println("ConcreteProductA2"); } } package src.pattern.abstractfactory; public interface AbstractProductB { }

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

440

package src.pattern.abstractfactory; public class ConcreteProductB1 implements AbstractProductB { public ConcreteProductB1() { System.out.println("ConcreteProductB1"); } } package src.pattern.abstractfactory; public class ConcreteProductB1 implements AbstractProductB { public ConcreteProductB2() { System.out.println("ConcreteProductB2"); } }

Per comodit, definiamo la classe FactoryHandler contenente un metodo statico che, a seconda del parametro di input ritorna gli oggetti per creare famiglie di nuovi oggetti di tipo uno o due.
package src.pattern.abstractfactory; public class FactoryHandler { private static AbstractFactory af = null; static AbstractFactory getFactory(int param) { if(param==1) af = new ConcreteFactory1(); if(param==2) af = new ConcreteFactory2(); return af; } }

Infine il metodo main della applicazione:


package src.pattern.abstractfactory; public class TestClient { public static void main(String[] argv) { AbstractFactory family1 = FactoryHandler.getFactory(1); AbstractProductA a1 = family1.createAbstractProductA(); AbstractProductB b1 = family1.createAbstractProductB(); AbstractFactory family2 = FactoryHandler.getFactory(2); AbstractProductA a2 = family2.createAbstractProductA(); AbstractProductB b2 = family2.createAbstractProductB(); } }

Nella prossima figura riportato il diagramme delle classi utilizzate.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

441

Figura 161:

Class-Diagram della applicazione di esempio

24.4 Singleton
Una classe singleton utile in tutti i contesti applicativi in cui necessario garantire che esista uno ed un solo oggetto di un determinato tipo in memoria. Lunico modo per risolvere il problema, quello di rendere la classe responsabile della allocazione in memoria di se stessa, impedendo ad altri oggetti di accedere al costruttore di classe.

http://www.4it.it/ -

http://www.javamattone.4it.it -

info@4it.it

pag.

442

Il problema:

Deve esistere esattamente una istanza di una classe, e deve essere facilmente accessibile dal client; Lunica istanza deve poter essere estesa mediante il meccanismo di ereditariet e il client, deve poter utilizzare lunica istanza della sottoclasse senza doverne modificare il codice.

Il disegno:

Figura 162:

Singleton

Come lavora:
Per comprendere il meccanismo di funzionamento di un Singleton, necessario fare un passo indietro ricordando alcuni fondamentali del linguaggio Java. 1. Un oggetto pu essere creato a partire dalla sua definizione di classe mediante loperatore new. 2. Loperatore new utilizza un metodo costruttore della classe. Nel caso in cui non