Pellegrino Principe - Java 8 Guida Completa

Share Embed Donate


Short Description

JRE...

Description

JAVA 8 Pellegrino Principe

© Apogeo - IF - Idee editoriali Feltrinelli s.r.l. Socio Unico Giangiacomo Feltrinelli Editore s.r.l.

ISBN edizione cartacea: 9788850333080

Il presente file può essere usato esclusivamente per finalità di carattere personale. Tutti i contenuti sono protetti dalla Legge sul diritto d’autore. Nomi e marchi citati nel testo sono generalmente depositati o registrati dalle rispettive case produttrici. L’edizione cartacea è in vendita nelle migliori librerie. ~ Sito web: www.apogeonline.com Scopri le novità di Apogeo su Facebook. Seguici su Twitter @apogeonline

Introduzione



Questo libro ha come obiettivo quello di insegnare la programmazione in Java in modo semplice e immediato, andando direttamente al sodo e cercando di concentrarsi sull’essenziale. Dire le cose essenziali, tuttavia, non comporta tralasciare informazioni importanti, e infatti per ogni argomento si è cercato di mostrare i punti salienti, corredandoli di esempi e listati. Il lettore tenga presente, quindi, che questo non è un libro leggero o superficiale, poiché affronta tutti gli argomenti che una guida completa si prefigge di trattare a cui sono associati innumerevoli listati e snippet di codice da studiare, compilare e provare.

Organizzazione del libro Il libro è organizzato nei capitoli elencati di seguito. Capitolo 1, “Introduzione al linguaggio”: introduciamo il lettore a una panoramica del linguaggio e spieghiamo i concetti propedeutici per compilare ed eseguire un programma Java. Capitolo 2, “Variabili, costanti, letterali e tipi”: mostriamo come dichiarare e definire i dati di un programma e come attribuire a essi un tipo di appartenenza. Capitolo 3, “Array”: spieghiamo la struttura di dati array e come manipolarla. Mostriamo altresì la gestione degli array multidimensionali. Capitolo 4, “Operatori”: mostriamo tutti gli operatori che il linguaggio mette a disposizione per manipolare i dati. Si passa dall’illustrazione dei semplici operatori aritmetici a quella dei complessi operatori bitwise. Capitolo 5, “Strutture di controllo”: vediamo come gestire l’esecuzione del flusso di un programma attraverso le strutture di iterazione e di selezione. Capitolo 6, “Metodi”: mostriamo come progettare i metodi, ovvero le funzioni basilari di un programma che eseguono determinate operazioni. NOTA Nell’organizzazione dei capitoli si è preferito introdurre i metodi prima del costrutto di classe poiché sono “strutture” sintattiche più semplici, in modo da affrontare i concetti essenziali del linguaggio in ordine crescente di complessità. Filosoficamente, questa scelta si sposa bene con l’inquadramento del paradigma ad oggetti (basato sulle classi) inteso come estensione del paradigma procedurale (basato sulle procedure). Non a caso, le strutture di controllo della programmazione ad oggetti, che vengono poi utilizzate all’interno dei metodi, sono le stesse del paradigma procedurale.

Capitolo 7, “Programmazione basata sugli oggetti”: vediamo come si crea un nuovo tipo di dato attraverso il costrutto di classe. Illustriamo che cosa sono i membri di una classe e la loro visibilità (information hiding) e come si possono definire al suo interno. Analizziamo i metodi costruttori, la keyword this e la progettazione di classi annidate. Infine, trattiamo i tipi enumerati. Capitolo 8, “Programmazione orientata agli oggetti”: descriviamo come si creano gerarchie di classi (ereditarietà) e che cos’è il polimorfismo. Parliamo, inoltre, di classi astratte e interfacce. Chiudiamo il capitolo con l’illustrazione delle classi anonime. Capitolo 9, “Programmazione generica”: spieghiamo come si creano metodi e classi generiche, ovvero come si effettua una programmazione che manipola tipi parametrizzati.

Capitolo 10, “Programmazione funzionale”: introduciamo il lettore allo studio del paradigma della programmazione funzionale attraverso un percorso di apprendimento graduale e completo, che parte dall’analisi dei concetti propedeutici quali il lambda calcolo, l’immutabilità dello stato, le closure e così via, e termina con un dettaglio di come i progettisti di Java hanno introdotto i pattern propri di tale paradigma nel linguaggio. Tra gli argomenti: le interfacce funzionali, i metodi di default, le lambda expression, il target typing. NOTA In alcuni listati, laddove necessario, si è preferito lasciare l’implementazione delle interfacce funzionali piuttosto che scrivere l’equivalente lambda expression. Ciò per due ragioni: la prima di carattere teorico-didattica, legata a garantire al lettore, in questa fase di studio preliminare, una maggiore comprensione di quello che effettivamente avviene con l’utilizzo delle interfacce funzionali; la seconda di carattere pratico-didattica, connessa a permettere al lettore di esercitarsi a cambiare, in quelle parti di codice dove è presente un’interfaccia funzionale, l’equivalente lambda expression. Quanto detto ha una sola eccezione nel Capitolo 17, dove viene trattata l’astrazione stream, che è stata pensata proprio per supportare nativamente le lambda expression; pertanto in quel contesto, nei listati relativi non sono state usate le interfacce funzionali ma direttamente tali lambda expression.

Capitolo 11, “Errori software”: illustriamo come si intercettano e gestiscono gli errori software grazie all’utilizzo di un meccanismo definito gestione delle eccezioni. Capitolo 12, “Package”: vediamo come si creano librerie di tipi correlati e come si rendono disponibili (deployment) in un ambiente di sviluppo o di produzione. Capitolo 13, “Annotazioni”: analizziamo come si creano e utilizzano dei metadati che annotano gli elementi (variabili, metodi e così via) del linguaggio Java. Capitolo 14, “Documentazione del codice sorgente”: mostriamo come utilizzare dei tag “speciali” che consentono di formattare il codice sorgente in modo che sia possibile generare per esso un’adeguata documentazione. Capitolo 15, “Caratteri e stringhe”: impariamo a utilizzare le classi principali che consentono di manipolare i caratteri e le stringhe (Character, String, e così via).

StringBuilder

Capitolo 16, “Espressioni regolari”: spieghiamo cosa sono e come impiegare le regex per costruire dei sofisticati pattern per la ricerca di corrispondenze di caratteri. Capitolo 17, “Collezioni”: studiamo come utilizzare le collezioni, o contenitori (Collection, List, Map e così via), costituite da gruppi di elementi tra loro naturalmente collegati che possono subire operazioni di manipolazione. Introduciamo, infine, allo studio dell’astrazione stream, che rappresenta una

sequenza di elementi su cui è possibile compiere operazioni aggregate, di tipo filter-map-reduce, sia sequenziali sia parallele. Capitolo 18, “Programmazione concorrente”: trattiamo i concetti di processo e thread e di come scrivere in Java programmi che sono in grado di eseguire più operazioni in parallelo. Capitolo 19, “Input/Output: stream e file”: illustriamo come effettuare le comuni operazioni di input e output dei dati (stream, file e così via). Capitolo 20, “Progettazione di interfacce utente”: impariamo a scrivere programmi Java dotati di una GUI (Graphical User Interface) mediante l’utilizzo del potente framework Swing. Capitolo 21, “Programmazione di rete”: studiamo cos’è una rete (suite TCP/IP) e le API che Java mette a disposizione per la programmazione dei socket e dei datagram. Capitolo 22, “Programmazione dei database”: analizziamo i concetti propedeutici dei database relazionali come le tabelle e le relazioni tra di esse, diamo uno sguardo al linguaggio SQL e studiamo l’architettura software JDBC, che consente di scrivere applicazioni che si interfacciano con una base di dati in modo semplice, consistente e indipendente dal database usato. Capitolo 23, “Sviluppo di applicazioni web”: impariamo a utilizzare la piattaforma JEE 7 (Java Enterprise Edition) per progettare e sviluppare applicazioni per il web (servlet, JavaServer Pages, JavaServer Faces e così via). Appendice A, “Installazione e configurazione della piattaforma JSE”: vediamo come installare e configurare la piattaforma standard di Java in ambiente Windows e in ambiente GNU/Linux. Appendice B, “Installazione e configurazione della piattaforma JEE”: vediamo come installare e configurare la piattaforma di Java destinata alla progettazione e allo sviluppo di applicazioni web. Appendice C, “Installazione e configurazione di MySQL”: illustriamo come installare e configurare il database server MySQL sia per sistemi Windows sia per sistemi GNU/Linux. Analizziamo, infine, come installare e configurare Connector/J, il driver JDBC ufficiale per il server MySQL. Appendice D, “Installazione e utilizzo di NetBeans”: mostriamo come installare e utilizzare l’IDE (Integrated Development Environment) NetBeans per la creazione di applicazioni Java per le piattaforme JSE e JEE. Appendice E, “Applet”: vediamo come scrivere dei programmi Java denominati applet, che girano all’interno di un browser web.

Struttura del libro e convenzioni Gli argomenti del libro sono, ovviamente, organizzati in capitoli. Ogni capitolo è numerato in ordine progressivo e denominato significativamente nel suo obiettivo didattico (per esempio, Capitolo 2, “Variabili, costanti, letterali e tipi”). I capitoli sono poi suddivisi in paragrafi di pertinenza. All’interno dei paragrafi possiamo avere dei blocchi di testo o di grafica, a supporto alla teoria, denominati come segue: Listato NrCapitolo.NrProgressivo Descrizione… per i listati del codice sorgente; Decompilato NrCapitolo.NrProgressivo Descrizione… per i listati dei file .class decompilati; Sintassi NrCapitolo.NrProgressivo Descrizione… per la sintassi di un costrutto del linguaggio; Snippet NrCapitolo.NrProgressivo Descrizione… per un frammento di codice sorgente; Shell NrCapitolo.NrProgressivo Descrizione… per un comando di shell; Warning NrCapitolo.NrProgressivo Descrizione… per un avviso (warning) del compilatore; Errore NrCapitolo.NrProgressivo Descrizione… per un errore di compilazione o di esecuzione; Output NrCapitolo.NrProgressivo Descrizione… per l’output di un programma; Figura NrCapitolo.NrProgressivo Descrizione… per una figura; Tabella NrCapitolo.NrProgressivo Descrizione… per una tabella. Per esempio, il blocco denominato “Listato 6.2 Classe ArgomentoImmodificabile” indica il listato di codice numero 2 del Capitolo 6 avente come descrizione la classe ArgomentoImmodificabile. Per quanto attiene ai listati, abbiamo adottato la seguente convenzione: i puntini di sospensione (…) eventualmente presenti indicano che in quel punto sono state omesse alcune parti del listato. Ovviamente, le medesime parti sono presenti nei relativi file .java allegati al libro. Gli stessi caratteri possono talvolta trovarsi anche negli output di un programma eccessivamente lungo.

Codice sorgente e classi All’indirizzo http://www.apogeonline.com/libri/9788850317042/scheda è possibile scaricare un archivio ZIP che contiene tante cartelle quanti sono i capitoli del libro. Ciascuna cartella, denominata Cap01, Cap02 e così via, contiene a sua volta due sottocartelle, denominate Listati e Snippets. All’interno della cartella Listati sono presenti, in modo indipendente, sia tutti i file sorgente .java del capitolo di pertinenza con le eventuali risorse complementari, sia altre sottocartelle direttamente correlate a specifici progetti caricabili con l’IDE NetBeans, che ricalcano come denominazione, laddove opportuno, i rispettivi file sorgenti. All’interno della cartella Snippets si trovano invece dei file .txt che contengono gli snippet di codice eventualmente presenti nel capitolo di pertinenza.

Utilizzo dei comandi javac e java per compilare ed eseguire i programmi Java Per rendere agevole la compilazione e l’esecuzione dei programmi Java, indipendentemente dall’IDE, riteniamo utili i consigli riportati di seguito. Se si utilizza Windows, creare le seguenti strutture di directory: per i sorgenti, C:\MY_JAVA_SOURCES; per le classi, C:\MY_JAVA_CLASSES; per i package, C:\MY_JAVA_PACKAGES; per gli archivi JAR, C:\MY_JAVA_JARS; per la documentazione, C:\MY_JAVA_DOCUMENTATION. Se si utilizza GNU/Linux, creare le seguenti strutture di directory: per i sorgenti, /opt/MY_JAVA_SOURCES; per le classi, /opt/MY_JAVA_CLASSES; per i package, /opt/MY_JAVA_PACKAGES; per gli archivi JAR, /opt/MY_JAVA_JARS; per la documentazione, /opt/MY_JAVA_DOCUMENTATION.

Copiare il listato da compilare nella cartella MY_JAVA_SOURCES. Compilare il listato utilizzando il comando javac con il flag -d che indica il percorso dove generare i file .class. Per esempio, per compilare il listato UsoDiChar.java invocare, dalla directory , il compilatore, per esempio javac -d C:\MY_JAVA_CLASSES UsoDiChar.java per

MY_JAVA_SOURCES

il sistema operativo Window e javac -d /opt/MY_JAVA_CLASSES UsoDiChar.java per il sistema operativo Gnu/Linux. Inoltre, se vi sono più classi da compilare, magari perché una classe utilizza i servizi di un’altra classe, si può lanciare il comando di compilazione, come javac -d



oppure javac -d /opt/MY_JAVA_CLASSES Time.java

c:\MY_JAVA_CLASSES Time.java Time_Client.java

.

Time_Client.java

Procedere poi come segue. 1. Eseguire il programma dalla cartella MY_JAVA_CLASSES, ricordandosi di anteporre il nome del package com.pellegrinoprincipe al nome della classe che lo rappresenta (tranne dove diversamente indicato). 2. Creare i package nella cartella MY_JAVA_PACKAGES. 3. Creare gli archivi nella cartella MY_JAVA_JARS. In conclusione, sottolineiamo quanto segue. Quando negli esempi del libro si invocheranno i comandi di compilazione ed esecuzione del codice (o altri comandi, come per esempio jar), daremo per assodato il rispetto dei percorsi di directory precedentemente indicati. Ciò significa che, se per esempio invocheremo il comando di compilazione, presupporremo che abbiate già cambiato la corrente directory in MY_JAVA_SOURCES e che in tale percorso si troverà il file sorgente indicato. Gli eventuali comandi di shell presentati sono indicati secondo le convenzioni del sistema Windows; per i sistemi GNU/Linux occorre attenersi alle regole specifiche di questo sistema operativo.

Utilizzo dell’IDE NetBeans per compilare ed eseguire i programmi Java Se lo si desidera, è possibile utilizzare l’IDE NetBeans per editare, compilare, eseguire e “debuggare” il codice sorgente presente nel libro. A tal fine consultare

l’Appendice D per una spiegazione in merito all’utilizzo introduttivo di tale IDE e alla creazione e all’impiego dei progetti. Infine, è importante precisare che tutti i progetti qui presentati presuppongono un’installazione di default del JDK nel percorso C:\Program Files (x86)\Java\jdk1.8.0 per Windows e /opt/jdk1.8.0 per GNU/Linux.

Capitolo 1

Introduzione al linguaggio

Java è un moderno linguaggio di programmazione le cui origini risalgono al 1991, quando presso Sun Microsystems un team di programmatori, formato principalmente da James Gosling, Patrick Naughton, Chris Warth, Ed Frank e Mike Sheridan, lavorò al suo sviluppo. Inizialmente il linguaggio fu chiamato OAK (in onore della quercia che Gosling vedeva dalla finestra del suo ufficio, infatti oak in inglese significa quercia) e fu sviluppato in seno a un progetto chiamato Green Project. Lo scopo del progetto era quello di dotare svariati dispositivi elettronici di consumo di un meccanismo tramite il quale fosse possibile far eseguire, in modo indipendente dalla loro differente architettura, i programmi scritti per essi. Questo meccanismo si sarebbe dovuto concretizzare nella scrittura di un componente software, denominato macchina virtuale, da implementare per il particolare hardware del dispositivo ma che, tuttavia, sarebbe stato in grado di far girare il codice intermedio, denominato bytecode, generato da un compilatore del linguaggio Java. Sostanzialmente l’obiettivo era quello di permettere agli sviluppatori di scrivere i programmi una sola volta e con la certezza che sarebbe stato possibile eseguirli dappertutto senza alcuna modifica sull’hardware destinatario (da qui lo slogan WORA, Write Once Run Anywhere). Nel maggio del 1995 fu completato lo sviluppo di OAK, con l’annuncio alla conferenza Sun World ’95. Con l’occasione, visto che il nome OAK apparteneva già a un altro linguaggio di programmazione, si decise di cambiare il nome del linguaggio di SUN in Java. CURIOSITÀ Probabilmente il nome Java fu deciso durante un incontro tra gli sviluppatori in un bar mentre bevevano caffè americano; infatti java è un termine utilizzato nello slang per indicare una bevanda fatta con miscele di chicchi di caffè.

Successivamente, nel 1996, alla prima JavaOne Developer Conference, venne rilasciata la versione 1.0 del JDK (Java Development Kit). A partire da quel momento iniziò una capillare e profonda diffusione di Java che è diventato un linguaggio di programmazione ampiamente diffuso e utilizzato per programmare ogni tipo di

dispositivi (dai cellulari ai computer, agli elettrodomestici e così via) dotati di una macchina virtuale. Il successo di Java aumentò notevolmente con l’esplosione di Internet e del Web dove, all’epoca, non esistevano programmi che permettessero di fornire contenuto dinamico alle pagine HTML (se si escludevano gli script CGI). Con Java infatti fu possibile, attraverso programmi detti applet, gestire nel Web contenuti dinamici e allo stesso tempo indipendenti dal sistema operativo e dal browser sottostante. Dopo di ciò il successo di Java fu inarrestabile e attorno al linguaggio furono create molteplici tecnologie, per esempio quelle per l’accesso indipendente ai database (JDBC), per lo sviluppo lato server del Web (Servlet/JSP/JSF) e così via. Al momento in cui scriviamo la versione ufficiale del linguaggio è la 1.8 (chiamata 8.0 per motivi di marketing), disponibile a partire dalla primavera del 2014. NOTA STORICA Sun Microsystems era una società multinazionale, produttrice di software e di hardware, fondata nel 1982 da tre studenti dell’università di Stanford: Vinod Khosla, Andy Bechtolsheim e Scott McNealy. Il nome è infatti l’acronimo di Stanford University Network. Nel gennaio del 2010 Sun è stata acquistata dal colosso informatico Oracle Corporation per la considerevole cifra di 7,4 miliardi di dollari. Tra i prodotti software ricordiamo il sistema operativo Solaris e il filesystem di rete NFS, mentre tra i prodotti hardware le workstation e i server basati sui processori RISC SPARC.

Paradigmi di programmazione Un paradigma, o stile di programmazione, indica un determinato modello concettuale e metodologico, offerto in termini concreti da un linguaggio di programmazione, al quale fa riferimento un programmatore per la progettazione e scrittura di un programma informatico e dunque per la risoluzione del suo particolare problema algoritmico. Si conoscono molti differenti paradigmi di programmazione, ma quelli che seguono ne rappresentano i più comuni. Il paradigma procedurale, dove l’unità principale di programmazione è, per l’appunto, la procedura o la funzione che ha lo scopo di manipolare i dati del programma. Questo paradigma è talune volte indicato anche come imperativo perché consente di costruire un programma indicando dei comandi (assegna, chiama una procedura, esegui un loop e così via) che esplicitano quali azioni si devono eseguire, e in quale ordine, per risolvere un determinato compito. Questo paradigma si basa dunque su due aspetti di rilevo: il primo è riferito al cambiamento di stato del programma che è causa delle istruzioni eseguite (si pensi al cambiamento del valore di una variabile in un determinato tempo durante l’esecuzione del programma); il secondo è inerente allo stile di programmazione adottato che è orientato al “come fare o come risolvere” piuttosto che al “cosa si desidera ottenere o cosa risolvere”. Esempi di linguaggi che supportano il paradigma procedurale sono FORTRAN, COBOL, Pascal e C. Il paradigma ad oggetti, dove l’unità principale di programmazione è l’oggetto (nei sistemi basati sui prototipi) oppure la classe (nei sistemi basati sulle classi). Questi oggetti, definibili come virtuali, rappresentano, in estrema sintesi, astrazioni concettuali degli oggetti reali del mondo fisico che si vogliono modellare. Questi ultimi possono essere oggetti più generali (pensate a un computer, per esempio) oppure oggetti più specifici, ovvero maggiormente specializzati (per esempio una scheda madre, una scheda video e così via). Noi utilizziamo tali oggetti senza sapere nulla della complessità con cui sono costruiti e comunichiamo con essi attraverso l’invio di messaggi (sposta il puntatore, digita dei caratteri) e mediante delle interfacce (il mouse, la tastiera). Inoltre, essi sono dotati di attributi (velocità del processore, colore del case e così via) che possono essere letti e, in alcuni casi, modificati. Questi oggetti reali vengono presi come modello per la costruzione di sistemi software a oggetti, dove l’oggetto (o la classe) avrà metodi per l’invio di messaggi e proprietà che rappresenteranno gli attributi da manipolare. Esempi di linguaggi che

supportano il paradigma ad oggetti sono: Java, C#, C++, JavaScript, Smalltalk e Python. TERMINOLOGIA Oggetto, tecnicamente, significa istanza di una classe; questa terminologia verrà introdotta nel Capitolo 7 relativo alle classi.

Il paradigma funzionale, dove l’unità principale di programmazione è la funzione vista in puro senso matematico. Infatti, il flusso esecutivo del codice è guidato da una serie di valutazioni di funzioni che, trasformando i dati che elaborano, conducono alla soluzione di un problema. Gli aspetti rilevanti di questo paradigma sono: nessuna mutabilità di stato (le funzioni sono side-effect free, ossia non modificano alcuna variabile); il programmatore non si deve preoccupare dei dettagli implementativi del “come” risolvere un problema ma piuttosto di “cosa” si vuole ottenere dalla computazione. Esempi di linguaggi che supportano il paradigma funzionale sono: Lisp, Haskell, F#, Erlang e Clojure. Il paradigma logico, dove l’unità principale di programmazione è il predicato logico. In pratica con questo paradigma il programmatore dichiara solo i “fatti” e le “proprietà” che descrivono il problema da risolvere lasciando al sistema il compito di “inferirne” la soluzione e dunque raggiungerne il “goal” (l’obiettivo). Esempi di linguaggi che supportano il paradigma logico sono Datalog, Mercury, Prolog e ROOP. Il linguaggio Java supporta, principalmente, il paradigma di programmazione ad oggetti, dove l’unità principale di astrazione è rappresentata dalla classe e dove vi è piena conformità con i principi fondamentali di tale paradigma: incapsulamento, ereditarietà e polimorfismo. NOTA Java è considerato un linguaggio di programmazione multiparadigma poiché, di fatto, supporta anche quello procedurale e, con la sua ultima versione, ha iniziato a supportare, seppure limitatamente all’introduzione delle lambda expression, anche quello funzionale.

Vediamo in breve che cosa significano questi principi, che saranno poi approfonditi nei capitoli di pertinenza. L’incapsulamento è un meccanismo attraverso il quale i dati e il codice di un oggetto sono protetti da accessi arbitrari (information hiding). Per dati e codice intendiamo tutti i membri di una classe, ovvero sia i dati membro (come le variabili), sia le funzioni membro (definite anche, in molti linguaggi di programmazione orientati agli oggetti, semplicemente come metodi). La

protezione dell’accesso viene effettuata applicando ai membri della classe degli specificatori di accesso, definibili come pubblico, con cui si consente l’accesso a un membro di una classe da parte di altri metodi di altre classi; protetto, con cui si consente l’accesso a un membro di una classe solo da parte di metodi appartenenti alle sue classi derivate; privato, con cui un membro di una classe non è accessibile né da metodi di altre classi né da quelli delle sue classi derivate, ma soltanto dai metodi della sua stessa classe. L’ereditarietà è un meccanismo attraverso il quale una classe può avere relazioni di ereditarietà nei confronti di altre classi. Per relazione di ereditarietà intendiamo una relazione gerarchica di parentela padre-figlio, dove una classe figlio (definita classe derivata o sottoclasse) deriva da una classe padre (definita classe base o superclasse) i metodi e le proprietà pubbliche e protette, e dove essa stessa ne definisce di proprie. Con l’ereditarietà si può costruire, di fatto, un modello orientato agli oggetti che in principio è generico e minimale (ha solo classi base) e poi, man mano che se ne presenta l’esigenza, può essere esteso attraverso la creazione di sottomodelli sempre più specializzati (ha anche classi derivate). Il polimorfismo è un meccanismo attraverso il quale si può scrivere codice in modo generico ed estendibile grazie al potente concetto che una classe base può riferirsi a tutte le sue classi derivate cambiando, di fatto, la sua forma. Ciò si traduce, in pratica, nella possibilità di assegnare a una variabile A (istanza di una classe base) il riferimento di una variabile B (istanza di una classe derivata da A) e, successivamente, riassegnare alla stessa variabile A il riferimento di una variabile C (istanza di un’altra classe derivata da A). La caratteristica appena indicata ci consentirà, attraverso il riferimento A, di invocare i metodi di A che B o hanno ridefinito in modo specialistico, con la garanzia che il sistema run-time

C

di Java saprà sempre a quale esatta classe derivata appartengono. TERMINOLOGIA La discriminazione automatica, effettuata dal sistema run-time di Java, di quale oggetto (istanza di una classe derivata) è contenuto in una variabile (istanza di una classe base) è effettuata con un meccanismo definito dynamic binding (binding dinamico).

Elementi di un ambiente Java Per poter programmare in Java è necessario scaricare il JDK (Java Development Kit) e installarlo sulla propria piattaforma (si rimanda all’Appendice A per spiegazioni più dettagliate). Dopo l’installazione del JDK avremo a disposizione tutti gli strumenti per compilare ed eseguire i programmi creati, incluse svariate librerie di classi dette API (Application Programming Interface). Nella Figura 1.1 è riportato un esempio di struttura di directory e file creata da un’installazione tipica (sono elencate, per motivi di spazio, tutte le directory e solo un file).

Figura 1.1 Struttura ad albero del JDK 1.8 a 32 bit creata in un sistema Windows.

La Figura 1.1 evidenzia: una cartella di nome bin che contiene tutti i file eseguibili dei tool di sviluppo di Java come javac.exe (il compilatore del codice sorgente Java), java.exe (l’interprete ed esecutore di un programma Java), jar.exe (il gestore per file di archivi basati sul formato Java ARchive o JAR), javap.exe (il disassemblatore di file .class) e così via; una cartella di nome db che contiene Java DB, un sistema per la gestione di basi di dati relazionali realizzato da Oracle e basato sul software open source Apache Derby che è anch’esso un sistema per la gestione di database ma realizzato in seno all’Apache Software Foundation. In dettaglio troviamo le directory: bin, al cui interno sono presenti file di script per la configurazione dell’ambiente di

utilizzo del server (modalità embedded o client/server), per l’avvio e per l’arresto del server e così via; lib, che contiene file di archivio .jar come (l’engine library), derbyclient.jar (la network client library, necessaria

derby.jar

per utilizzare il network client driver), derbynet.jar (la network server library, necessaria per avviare il network server) e così via; una cartella di nome include che contiene file header in linguaggio C per la programmazione in codice nativo mediante l’utilizzo della Java Native Interface (JNI) e della Java Virtual Machine Tools Interface (JVMTI); una cartella di nome lib che contiene librerie di classi e altri file utilizzati dagli eseguibili di sviluppo (per esempio tools.jar e dt.jar); una cartella di nome jre che contiene un’implementazione di un ambiente di runtime di Java (JRE, Java Runtime Environment) utilizzato dal JDK. In breve un JRE include una virtual machine (JVM, Java Virtual Machine), librerie di classi e altri file necessari per l’esecuzione dei programmi scritti con il linguaggio Java; un file di archivio basato sul formato ZIP di nome src.zip che contiene il codice sorgente di tutte quelli classi che rappresentano il core delle API di Java (per esempio quelle che si trovano nei package java.*, javax.* e così via); un file di nome release che contiene informazioni di servizio varie codificate come coppie di chiave/valore (per esempio, JAVA_VERSION="1.8.0", OS_NAME="Windows", e così via).

OS_VERSION="5.1"

TERMINOLOGIA Con il termine codice nativo si intende codice che è compilato per una specifica piattaforma/sistema operativo, come il codice C. Il codice Java, invece, nel momento in cui viene compilato produce un codice in linguaggio “intermedio” (detto bytecode) che è interpretabile da una qualsiasi macchina virtuale Java e quindi risulta indipendente dalla specifica piattaforma.

Il primo programma Java Vediamo, attraverso la disamina del Listato 1.1, quali sono gli elementi basilari per scrivere un programma in Java. Listato 1.1 PrimoProgramma. package com.pellegrinoprincipe; /* Primo programma in Java */ public class PrimoProgramma { private static int counter = 0; public static void main(String[] args) { String testo = "Primo programma in Java:", testo2 = " Buon divertimento!"; int a = 10, b = 20; // stampa qualcosa… System.out.println(testo + testo2); String s = "Stamperò un test condizionale: " + "tra a=" + a + " e b=" + b; System.out.println(s); if (a < b) { System.out.println("ab"); } s = "Stamperò un ciclo iterativo, " + "dove leggerò per 10 volte il valore di a"; System.out.println(s); for (int i = 0; i < 10; i++) { System.out.print("Passo " + i); System.out.println("--> " + "a=" + a); } } }

Il Listato 1.1 inizia con l’istruzione package, che consente di creare librerie di tipi correlati. Infatti la classe denominata PrimoProgramma, definita con la keyword class, è un nuovo tipo di dato che apparterrà al package (o libreria) denominato com.pellegrinoprincipe. Successivamente abbiamo un’istruzione di commento multiriga che consente di scrivere, tra i caratteri di inizio commento /* e fine commento */, qualsiasi cosa che possa servire a chiarire il codice. L’istruzione di commento a singola riga prevede l’utilizzo dei caratteri //.

Le note esplicative scritte come commento possono contenere qualsiasi carattere, poiché saranno ignorate dal compilatore. Dopo il commento multiriga abbiamo, come già detto, la definizione della classe denominata PrimoProgramma con la scrittura tra le parentesi graffe aperta { e chiusa } dei suoi membri (dati e metodi). La nostra classe ha un dato membro denominato counter e un metodo denominato main. Il dato membro counter è una variabile, ovvero una locazione di memoria che può contenere valori che possono cambiare nel tempo. A ogni variabile deve essere associato un insieme di valori che può contenere, tramite la specificazione di un tipo di dato di appartenenza. La variabile counter ha, infatti, associato un tipo di dato intero. Il metodo main rappresenta una sorta di metodo di avvio (o di entry point) per la nostra applicazione e deve essere sempre presente almeno in una classe, poiché è invocato automaticamente dall’interprete java all’atto dell’esecuzione dell’applicazione. è preceduto dalle keyword descritte di seguito.

main

indica che il metodo non restituisce alcun valore. Ovviamente un metodo

void

può restituire un valore e in questo caso occorre indicarne il tipo di appartenenza, per esempio int per la restituzione di un tipo intero. indica che si tratta di un metodo di classe che può essere invocato senza la

static

creazione del relativo oggetto. public indica che il metodo è accessibile da client esterni alla classe dove il metodo stesso è stato definito. Le keyword static e public permettono al metodo main di essere invocato pubblicamente dall’interprete java, in quanto client esterno, e di avviarne il relativo programma. Seguono il main una serie di parentesi tonde all’interno delle quali è posta una variabile denominata args che rappresenta il parametro del metodo. Ogni metodo, infatti, può avere zero o più parametri che rappresentano, se presenti, delle variabili che saranno riempite con valori (detti argomenti) passati al metodo medesimo all’atto della sua invocazione. I parametri di un metodo devono avere, inoltre, un tipo di dato associato; infatti, il parametro args è dichiarato come di tipo array di stringhe (String[]).

Il parametro args permette al metodo main di ottenere in input gli argomenti eventualmente passati quando si invoca dalla riga di comando il programma che lo contiene. All’interno del metodo main sono scritte delle istruzioni che nel loro complesso rappresentano le operazioni che il metodo deve eseguire. Troviamo infatti: la dichiarazione di variabili locali come String testo, int a e così via; l’invocazione di un metodo di stampa di dati su console (println); l’esecuzione di un’istruzione di selezione doppia if/else che valuta se una data espressione è vera o falsa eseguendone, a seconda del risultato della valutazione, il codice corrispondente (quello del ramo valutato vero oppure quello del ramo valutato falso); l’esecuzione di un’istruzione di iterazione for che consente di eseguire ciclicamente una serie di istruzioni finché una data espressione è vera. Concludiamo con alcune indicazioni. Ogni istruzione deve terminare con il carattere ; (punto e virgola). Le parentesi graffe aperta { e chiusa } delimitano un blocco di codice contenente delle istruzioni. Il codice può essere scritto secondo il proprio personale stile di indentazione utilizzando i caratteri di spaziatura (spazio, tabulazione, Invio e così via) desiderati.

Compilazione ed esecuzione del codice Dopo aver scritto, con un qualunque editor di testo, il programma del Listato 1.1, vediamo come eseguirne la compilazione che, lo ricordiamo, è un processo mediante il quale il compilatore javac di Java legge un file sorgente (nel nostro caso ) per trasformarlo in un file (PrimoProgramma.class) che conterrà

PrimoProgramma.java

istruzioni scritte in un linguaggio intermedio detto bytecode. Shell 1.1 Invocazione del comando di compilazione (Windows). javac -d c:\MY_JAVA_CLASSES PrimoProgramma.java

Shell 1.2 Invocazione del comando di compilazione (GNU/Linux). javac -d /opt/MY_JAVA_CLASSES PrimoProgramma.java

Dopo la fase di compilazione segue la fase di esecuzione, nella quale un file .class (nel nostro caso PrimoProgramma.class) viene letto dall’interprete java per convertire il bytecode in esso contenuto in codice nativo del sistema dove eseguire il programma stesso. Shell 1.3 Invocazione dell’interprete java che esegue il programma. java com.pellegrinoprincipe.PrimoProgramma

Output 1.1 Esecuzione di Shell 1.3. Primo programma in Java: Buon divertimento! Stamperò un test condizionale: tra a=10 e b=20 a a=10 Passo 1--> a=10 Passo 2--> a=10 Passo 3--> a=10 Passo 4--> a=10 Passo 5--> a=10 Passo 6--> a=10 Passo 7--> a=10 Passo 8--> a=10 Passo 9--> a=10

Dalla Shell 1.3 vediamo che il comando java esegue il programma PrimoProgramma che stampa quanto mostrato nell’Output 1.1. È utile sottolineare alcuni aspetti. Il nome del programma PrimoProgramma è il nome della classe contenuta nel file omonimo. Il nome del file PrimoProgramma.class contenente il programma da eseguire viene passato al comando java senza l’indicazione dell’estensione .class.

La classe PrimoProgramma invocata è preceduta dal nome del package di appartenenza com.pellegrinoprincipe, poiché quando una classe appartiene a un package, il suo nome deve sempre far parte di quel package.

Problemi di compilazione ed esecuzione? Elenchiamo alcuni problemi che si potrebbero incontrare durante la fase di compilazione o di esecuzione del programma appena esaminato. I comandi javac o java sono inesistenti? Verificare che il path del sistema operativo contenga la directory bin del JDK tra i percorsi di risoluzione. Si rimanda all’Appendice A per i dettagli su come impostare correttamente il path. Il compilatore javac non trova il file PrimoProgramma.java? Verificare che la directory corrente sia c:\MY_JAVA_SOURCES (per Windows) oppure /opt/MY_JAVA_SOURCES (per GNU/Linux). L’interprete java non trova il file PrimoProgramma? Verificare che la directory corrente sia c:\MY_JAVA_CLASSES (per Windows) oppure /opt/MY_JAVA_CLASSES (per GNU/Linux).

Capitolo 2

Variabili, costanti, letterali e tipi

Una variabile rappresenta uno spazio di memoria alterabile dove vengono memorizzati dei valori. Prima di poter utilizzare tale variabile, ovvero prima di poterle assegnare (o leggere) un valore, è necessario dichiararne il tipo, cioè determinare che specie di dato essa potrà gestire. Ciò è necessario poiché Java è un linguaggio di programmazione strongly typed, ovvero fortemente tipizzato. TERMINOLOGIA Oltre ai linguaggi fortemente tipizzati (strongly typed), esistono anche linguaggi debolmente tipizzati (weakly typed o loosely typed) in cui le variabili non sono dichiarate con un tipo predefinito e nelle stesse, in tempi successivi, possono essere contenuti valori di tipo diverso: oggetti, stringhe, numeri e così via. Tra questi ultimi linguaggi vi sono, solo per citarne i più comuni, JavaScript, PHP e Python.

Il compilatore all’atto della compilazione effettuerà un controllo rigoroso di come le variabili sono utilizzate nelle espressioni, nei metodi e negli assegnamenti, verificando se queste operazioni rispettano i tipi in esse dichiarati non generando conversioni in conflitto. Dopo aver dichiarato il tipo, si deve scrivere un identificatore, ovvero un nome simbolico con cui referenziare la variabile per il suo utilizzo. Tale identificatore può essere scritto utilizzando qualsiasi combinazione di lettere minuscole, maiuscole, numeri, caratteri di dollaro, di sottolineatura, di euro, di sterlina, ma non può essere composto da più parole separate da spazi e non può iniziare con un numero e con caratteri quali /, % e in genere con caratteri che hanno a che fare con la sintassi del linguaggio (si pensi alle parentesi ( ), ai simboli di relazione > < e così via). Inoltre gli identificatori sono case-sensitive, nel senso che si fa distinzione tra lettere minuscole e lettere maiuscole. NAMING CONVENTION P e r naming convention si intende la regola di scrittura utilizzata per la denominazione degli elementi di un programma. Nel linguaggio Java le classi si dovrebbero scrivere usando la notazione UpperCamelCase (Pascal Case) in cui l’identificatore, se formato da più parole, deve essere scritto tutto unito e ogni parola deve iniziare con la lettera maiuscola (per esempio UsoDiChar), mentre le variabili e i metodi si dovrebbero scrivere utilizzando la notazione definita lowerCamelCase in cui, se l’identificatore è formato da più parole, deve essere scritto tutto unito, e la prima parola deve iniziare con la minuscola mentre le altre con la maiuscola (per esempio myStr, myMethod). Snippet 2.1 Alcuni identificatori.

int number_1; // CORRETTO int number 1; // ERRORE - ';' expected int 1number; // ERRORE - not a statement // a e A sono variabili DIVERSE!!! int a; int A;

Quando una variabile viene scritta nella forma dello Snippet 2.1 si dice che essa è dichiarata. Con tale dichiarazione, di fatto, si comunica al compilatore di allocare per l’identificatore un determinato spazio di memoria relativo al suo tipo. La dichiarazione può essere accompagnata da un’operazione di inizializzazione (effettuata utilizzando l’operatore di assegnamento =), cioè dalla scrittura di un valore nella variabile, ricordando che tale valore deve essere dello stesso tipo della variabile in questione. Ogni dichiarazione/inizializzazione può essere effettuata su più variabili in una sola riga utilizzando il simbolo di virgola (,) oppure scrivendo prima la dichiarazione e poi l’inizializzazione. Snippet 2.2 Alcune dichiarazioni e inizializzazioni. // dichiarazione e inizializzazione di variabili primitive int nr1 = 44, nr2 = 55; // dichiarazione e inizializzazione di variabili riferimento String my_str = new String("Java is a great programming language!!!"); // dichiarazione float fl1, fl2; // inizializzazione fl1 = 33.33f; fl2 = 44.44f;

Le inizializzazioni dello Snippet 2.2 sono tutte effettuate con valori definiti letterali (tranne quella della variabile my_str). Una variabile può ottenere un valore anche in modo dinamico, ovvero mediante la valutazione di un’espressione. Snippet 2.3 Valore dinamico. double db = Math.sqrt(44.44);

Nello Snippet 2.3 la variabile db di tipo double otterrà un valore che è il risultato del calcolo della radice quadrata di 44.44, dopo l’invocazione del metodo sqrt della classe .

Math

Variabili primitive Le variabili primitive sono variabili che contengono direttamente al loro interno un valore (Figura 2.1) che può derivare da uno dei seguenti tipi di dato: di 8 bit con un range di valori true o false;

boolean

di 16 bit con un range di valori da \u0000 a \uFFFF come definiti dallo standard

char

ISO Unicode; byte di 8 bit con un range di valori da -128 a +127; di 16 bit con un range di valori da –32.768 a +32.767;

short

di 32 bit con un range di valori da –2.147.483.648 a +2.147.483.647;

int

di 64 bit con un range di valori da –9.223.372.036.854.775.808 a

long

;

+9.223.372.036.854.775.807

di 32 bit con un range di valori da 1.4E-45 a +3.4028235E+38;

float

di 64 bit con un range di valori da 4.9E-324 a +1.7976931348623157E+308.

double

Figura 2.1 Variabile primitiva denominata my_var, di tipo intero e contenente il valore 450. APPROFONDIMENTO I tipi di dato float e double sono stati progettati secondo lo standard internazionale IEEE 754 (IEEE Standard for Binary Floating-Point Arithmetic), il quale definisce delle regole per i sistemi di computazione in virgola mobile, ovvero formalizza come devono essere rappresentati, quali operazioni possono essere compiute, le conversioni operabili e come devono essere gestite le condizioni di eccezione come, per esempio, la divisione per 0. I formati esistenti sono: a precisione singola (32 bit), a precisione singola estesa (>= 43 bit), a precisione doppia (64 bit) e a precisione doppia estesa (>= 79 bit).

Come si nota dall’elenco, i tipi, tranne char, sono con segno, cioè accettano sia valori negativi sia positivi, e non possiamo modificare tale impostazione rendendoli unsigned. In Java, inoltre, la dimensione in byte stabilita per i tipi di dato è fissa e non varia se la macchina virtuale è installata su sistemi operativi o processori differenti. I tipi byte, short e int sono utilizzati per eseguire calcoli con valori senza parte frazionaria, mentre i tipi float e double possono essere usati per calcoli con componente frazionaria a precisione singola o doppia. Il tipo char è utile per

memorizzare un singolo carattere e occupa 16 bit; è in grado di contenere caratteri in tutte le lingue del mondo come stabilito dallo standard Unicode. Il range va da 0 a , dove da 0 a 127 sono contenuti i caratteri del set ASCII.

65535

UNICODE Unicode è un sistema di codifica universale per i caratteri (sviluppato e mantenuto da un’organizzazione non-profit denominata Unicode Consortium), indipendente dal sistema informatico e dalla lingua in uso, che assegna a ciascun carattere un valore numerico. Il sistema nasce con l’obiettivo di rappresentare i caratteri di tutte le lingue del mondo (anche di quelle antiche), i simboli scientifici, gli ideogrammi e così via. Nella prima versione di Unicode, dal 1991 al 1995, la codifica dei caratteri era a 16 bit, con cui si potevano codificare fino a 65.536 caratteri, ma successivamente, con la versione 2.0 del 1996 la codifica passò a 21 bit con la possibilità di rappresentare circa 2 milioni di caratteri. Dopo di allora vi sono state altre versioni dello standard che lo hanno migliorato sia dal punto di vista formale (per esempio attraverso il cambiamento di definizioni terminologiche poco chiare delle versioni precedenti) sia da quello più pratico grazie all’aggiunta, via via, di ulteriori caratteri (per esempio per la lingua etiopica, cherokee e così via). Attualmente Unicode è giunto alla versione 6.3, mentre la corrente versione di Java ne supporta la versione 6.2.

Con le variabili del tipo char, visto che in esso si possono scrivere o leggere valori numerici a cui sono associati i relativi simboli dei caratteri, è lecito compiere un’operazione come spostarsi al carattere successivo, semplicemente incrementando di uno il suo valore (Listato 2.1). Listato 2.1 Classe UsoDiChar. package com.pellegrinoprincipe; public class UsoDiChar { public static void main(String[] args) { char ch = 82; System.out.println(ch); // stampa R ch++; // sposta al carattere successivo System.out.println(ch); // stampa S } }

Output 2.1 Dal Listato 2.1 Classe UsoDiChar. R S

Il tipo boolean può contenere solo valori true o false, cioè valori che derivano da valutazioni logiche di verità o falsità. Un’espressione ritorna in automatico il valore true o false a seconda che sia vera o falsa. Snippet 2.4 Valutazione di un’espressione booleana. int a = 82, b = 90; boolean c = a > b;

Nello Snippet 2.4 l’espressione a > b ritornerà un valore false che sarà memorizzato nella variabile c di tipo boolean.

Variabili riferimento Le variabili riferimento contengono al loro interno un valore che è un riferimento (un puntamento) a un’area di memoria dove è stato allocato un tipo di dato astratto e complesso (Figura 2.2). Questo tipo di dato è un oggetto che rappresenta un’istanza della sua classe di definizione. Ciò significa, per esempio, che la variabile riferimento my_str dello Snippet 2.2 non conterrà direttamente al suo interno il valore "Java is a , bensì conterrà un valore che è un riferimento a un’area di

great programming language!!!"

memoria dove sarà stato allocato un oggetto del tipo della classe String attraverso il cui stesso riferimento potrà poi manipolarne i dati.

Figura 2.2 Variabile riferimento denominata my_str, di tipo String. CHE COSA SONO ESATTAMENTE I RIFERIMENTI? Per meglio comprendere il concetto di riferimento a un’area di memoria, cerchiamo di rispondere alla seguente domanda: tale concetto di riferimento è assimilabile direttamente quello di puntatore presente in altri linguaggi come il C/C++, ovvero i tipi riferimento contengono al loro interno un semplice indirizzo di memoria dove è stato allocato l’oggetto che riferiscono? Se intendiamo come puntatore una variabile che contiene come valore un dato che “rappresenta ma non è” un indirizzo di memoria dove è stato allocato un determinato oggetto, allora la risposta è affermativa. Infatti, in Java il dato contenuto nella variabile non è esattamente il valore dell’indirizzo di memoria, ma un valore che lo rappresenta. Listato 2.2 Classe Riferimenti. package com.pellegrinoprincipe; class T { } public class Riferimenti { public static void main(String[] args) { int x[] = new int[2]; System.out.println("Valore riferimento dell'array x: " + x); T t = new T(); System.out.println("Valore riferimento dell'oggetto t: " + t); } }

Output 2.2 Dal Listato 2.2 Classe Riferimenti.

Valore riferimento dell'array x: [I@15fbaa4 Valore riferimento dell'oggetto t: com.pellegrinoprincipe.T@1ee12a7

L’Output 2.2 mostra chiaramente che il sistema stampa per la variabile x e per la variabile t un valore che ha una sintassi particolare. Infatti, il valore di un riferimento è costituito da due parti: la prima parte, a sinistra del simbolo @, sta a indicare il tipo di oggetto, mentre la seconda parte, a destra del medesimo simbolo, sta a indicare un valore esadecimale che è un hash code dell’effettivo indirizzo di memoria dell’oggetto (per hash code si intende un valore che deriva da un altro valore, trasformato da una funzione di hashing). Nel nostro caso, per la variabile x la prima parte [I indica che essa è un array di tipo intero, mentre la seconda parte dà come hash code il valore 15fbaa4; per la variabile t la prima parte indica che essa è un oggetto di tipo T appartenente al package com.pellegrinoprincipe, mentre la seconda parte dà come hash code il valore .

1ee12a7

ATTENZIONE La corrispondenza tra l’indirizzo di memoria dell’oggetto puntato e un valore derivato da una funzione di hashing non è garantita in tutte le implementazioni delle macchine virtuali Java, poiché l’implementazione di tale uguaglianza non è un requisito obbligatorio richiesto dalle specifiche del linguaggio. Ciò significa che in alcune implementazioni il valore potrebbe essere un riferimento che non è necessariamente una rappresentazione di un mero indirizzo di memoria.

Possiamo pertanto affermare che i riferimenti sono come i puntatori ma non sono esattamente la stessa cosa, e ciò sia per la spiegazione appena riportata, sia perché essi: non possono essere deallocati direttamente; infatti, la loro deallocazione è demandata a un componente software della virtual machine denominato garbage collector che provvede autonomamente a deallocare un oggetto che non ha più nessuna variabile che lo riferisce; non possono essere manipolati direttamente per svolgere operazioni di aritmetica dei puntatori (come invece è possibile fare in C/C++) e per svolgere operazioni di inizializzazione con valori di indirizzo arbitrari. Snippet 2.5 Aritmetica dei puntatori non permessa. int x[] = {1,2,3}; x++; // ERRORE - bad operand type int[] for unary operator '++'

Lo Snippet 2.5 evidenzia come non sia possibile far spostare in avanti di un’unità il riferimento della variabile x. Snippet 2.6 Inizializzazione con valori di indirizzo arbitrari.

int x[] = new int[2]; int y[] = null; int z[] = 1b67f74; // ERRORE - incompatible types: int cannot be converted to int[]

Lo Snippet 2.6 evidenzia che quando si crea un riferimento non lo si può inizializzare con valori arbitrari; lo si potrà inizializzare con l’indirizzo dell’oggetto contenente i dati a cui riferisce oppure con il valore speciale null.

Variabili locali, globali e scope In Java una variabile può essere definita come locale o come globale. Anche se tale definizione non è perfetta in un ambiente interamente ad oggetti, la utilizzeremo per permettere a chi ha già esperienza con un linguaggio procedurale come il C di avere un rapido confronto concettuale. Una variabile è locale quando il suo identificatore è usabile solo all’interno del blocco di codice ove è dichiarata. Un parametro di un metodo, una variabile dichiarata all’interno di un metodo e una variabile dichiarata all’interno di un blocco di codice sono tutti esempi di variabili locali. Le proprietà di una classe, definite variabili di istanza, sono variabili globali e sono utilizzabili dovunque all’interno della classe medesima. Quando si dichiara una variabile di istanza che ha un identificatore uguale a quello di una variabile locale a un metodo, allora la prima sarà nascosta, cioè il metodo userà quella a esso locale (quando studieremo le classi vedremo come sia comunque possibile accedere alla variabile di istanza). Possiamo dire, pertanto, che in Java le variabili locali hanno uno scope (o ambito di utilizzo) relativo al blocco di codice ove sono state dichiarate, mentre le variabili globali hanno uno scope di classe. NOTA Lo scope “globale” in Java è comunque confinato all’interno della classe, e questo riduce le problematiche di accesso e manipolazione di dati globali che sono invece presenti in linguaggi procedurali come C, dove, appunto, lo scope globale si estende a tutto il programma.

La dichiarazione della variabile può avvenire ovunque all’interno di un blocco di codice, il quale è rappresentato, ripetiamo, da un gruppo di istruzioni poste tra le parentesi graffe di apertura { e chiusura }, e dopo tale dichiarazione la variabile medesima può essere utilizzata. Se si creano blocchi annidati, la variabile del blocco più esterno è visibile all’interno del blocco interno, ma non vale il contrario. Listato 2.3 Classe BlocchiAnnidati. package com.pellegrinoprincipe; public class BlocchiAnnidati { public static void main(String[] args) { int x = 20; if (x < 20) { int y = 11; System.out.println("X " + x); } System.out.println("Y " + y); // errore 'y' non è visibile

} }

Errore 2.1 Dal Listato 2.3 Classe BlocchiAnnidati. …com\pellegrinoprincipe\BlocchiAnnidati.java:13: error: cannot find symbol System.out.println("Y " + y); // errore 'y' non è visibile symbol: variable y location: class BlocchiAnnidati 1 error

Il Listato 2.3 evidenzia che la variabile x dichiarata nel blocco del main è visibile nel blocco interno (annidato) rappresentato dalle istruzioni poste tra le parentesi graffe del costrutto if, mentre la variabile y, dichiarata all’interno del blocco if, non è visibile nel blocco esterno del main, poiché alla chiusura del blocco dell’if cessa di esistere. È importante rilevare, infine, che se dichiariamo una stessa variabile in blocchi annidati avremo un errore di duplicazione di una variabile locale: Snippet 2.7 Dichiarazione di uno stesso identificatore in blocchi annidati. // dichiarazione stessa variabile int x = 20; // blocco di codice { int x = 11; // ERRORE - variable x is already defined }

Costanti Una costante rappresenta uno spazio di memoria in cui è memorizzato un valore che non può essere più alterato dopo che vi è stato assegnato. In Java una costante si dichiara utilizzando la keyword final. Snippet 2.8 Dichiarazione di una costante. final int a = 82; a = 90; // ERRORE - cannot assign a value to final variable a

La dichiarazione di una costante impone delle regole relative a quando e se essa debba essere inizializzata, e tali regole presentano delle differenze a seconda che la costante sia locale a un metodo o globale di classe. Vedremo tali differenze quando studieremo i metodi e le classi.

Letterali Un letterale è un valore che viene assegnato a una variabile e che il programma non può alterare. Può essere intero se è rappresentato da valori interi come 10, 100, 4567 e così via. Per assegnare un letterale di tipo intero a una variabile di tipo byte o short: basta semplicemente scrivere il suo valore prestando però attenzione che non ecceda il range di valori accettato. Se invece si vuole scrivere un valore numerico di tipo long, si dovrà scrivere anche un suffisso, precisamente inserendo la lettera L subito dopo l’ultima cifra del valore. Inoltre, un letterale numerico intero può essere espresso in una base diversa da 10 come quella ottale, esadecimale o binaria, ponendo prima dei numeri rispettivamente i prefissi 0, 0x o 0B (0b). Infine, le cifre che compongono un letterale numerico possono essere, arbitrariamente, separate dal carattere underscore (_) al fine di rendere più leggibile il numero stesso. Listato 2.4 Classe LetteraliNumerici. package com.pellegrinoprincipe; class LetteraliNumerici { public static void main(String[] args) { int d = 10_000_000; // decimale con separatore int o = 010; // ottale int x = 0x10; // esadecimale int b = 0B0000_1111; // binario con separatore long l = 435435435345345L; // valore long System.out.println("d = " + d); System.out.println("o = " + o); System.out.println("x = " + x); System.out.println("b = " + b); System.out.println("l = " + l); } }

Output 2.3 Dal Listato 2.4 Classe LetteraliNumerici. d = 10000000 o = 8 x = 16 b = 15 l = 435435435345345

È importante sottolineare che il carattere di separazione tra le cifre di un numero può essere posto solo tra di esse e mai: all’inizio o alla fine di un numero (123_); adiacente al punto decimale (3_.1415F);

prima del suffisso L o F (12.55_F); in una posizione dove ci si aspetta di trovare una stringa numerica. I letterali in virgola mobile rappresentano numeri decimali e possono essere espressi sia in forma standard sia in forma scientifica. In forma standard basta scrivere il numero separato dalla parte frazionaria con un punto, per esempio 10.44, mentre in forma scientifica si scrive come nella forma standard con l’aggiunta del suffisso E e di un numero positivo o negativo che rappresenta una potenza di 10 per cui il numero deve essere moltiplicato. Tali letterali sono di tipo double, perciò per assegnarli a una variabile di tipo float bisogna aggiungere il suffisso F al numero rappresentato. Snippet 2.9 Letterali numerici in virgola mobile. // forma standard double db = 45.1453; // letterale double float fl = 45.3345F; // letterale float // forma scientifica double dbsp = 123.12E+4; // 123.12 * 10 ^ 4 ossia 1231200 double dbsn = 123.12E-4; // 123.12 * 10 ^ -4 ossia 0.012312

Un letterale booleano, invece, è un valore che può essere solo true o false e non è convertibile in nessun controvalore numerico. Un letterale carattere è un valore che rappresenta un carattere secondo il set di caratteri aderenti allo standard Unicode. Si scrive tra singoli apici ed è convertibile in un valore intero. Tali letterali possono essere espressi anche in una forma ottale o esadecimale, scrivendo tra gli apici la sequenza \ddd per l’ottale e la sequenza \udddd per l’esadecimale. Snippet 2.10 Letterali carattere. // tutte rappresentazioni del carattere j char ch_d = 106; // decimale char ch_x = '\u006A'; // esadecimale char ch_o = '\152'; // ottale

Infine, i letterali stringa sono valori scritti tra virgolette come "Sono un letterale". Snippet 2.11 Letterali stringa. // stampa le lettere j k l tra virgolette e separate da TAB String str = "\"\u006A\t\u006B\t\u006C\""; // "j k l"

All’interno dei letterali stringa o carattere, come mostrato dai precedenti snippet, possiamo utilizzare lo speciale carattere backslash, con simbolo \, definito carattere di escape, che consente di immettere sia caratteri speciali non inseribili dalla tastiera (di cui alcuni non saranno visualizzati in output, per esempio, il ritorno a capo), sia i

caratteri propri della definizione del letterale stesso. Utilizzando questo carattere insieme a uno dei caratteri indicati di seguito si forma una sequenza di escape: per l’apice;

'

per le virgolette doppie;

"

per il backslash;

\

per il ritorno a capo;

r

per l’avanzamento di riga;

n

per l’avanzamento di pagina;

f

per il Tab;

t

per il backspace.

b

ATTENZIONE I letterali stringa devono essere scritti su un’unica riga senza separazione, poiché non esiste un carattere di escape di continuazione di riga. Snippet 2.12 Letterali stringa separati in una nuova riga. // ERRORE - unclosed string literal String str = "Sono una stringa";

Conversione tra tipi Quando si lavora con tipi di dato diversi può capitare di dover assegnare a una variabile di un tipo un valore di un’altra variabile di un tipo diverso. Se i tipi sono compatibili, Java effettuerà una conversione automatica (implicita) con ampliamento dei valori. Tale conversione è attuata, tuttavia, solo se il tipo finale è più grande del tipo iniziale. Essa è sempre lecita fra tipi interi (int, byte, short e long), decimali (double e float) e char, ma non con i tipi boolean. Se invece i tipi sono incompatibili, si può provare comunque ad assegnare il valore incompatibile effettuando un’operazione di conversione esplicita con riduzione del valore tramite un operatore definito di cast che ha la sintassi che segue: Sintassi 2.1 Cast. (tipo-finale) valore

dove tipo-finale è il tipo di dato in cui si vuole convertire valore. Nell’attuare la conversione possono verificarsi i seguenti casi: se si forza l’assegnamento di una variabile contenente un valore decimale a una variabile di tipo intero, si avrà un troncamento della sua parte frazionaria; se si assegna un valore di una variabile che è più grande del valore massimo contenibile nell’altra variabile, sarà assegnato un valore (detto modulo) che rappresenta il resto della divisione tra i due valori. Listato 2.5 Classe ConversioneTipi. package com.pellegrinoprincipe; public class ConversioneTipi { public static void main(String[] args) { int a = 260; double d = 323.123; byte b; // il risultato sarà infatti 260 % 256 che darà come resto 4 System.out.println("b = (byte) a ---> " + (b = (byte) a)); // il risultato sarà 67 infatti prima 323.123 sarà troncato in 323 // e poi si farà 323 % 256 // che darà come resto appunto 67 System.out.println("b = (double) d ---> " + (b = (byte) d)); } }

Output 2.4 Dal Listato 2.5 Classe ConversioneTipi. b = (byte) a ---> 4 b = (double) d ---> 67

Dall’Output 2.4 si evidenzia come l’espressione (b = (byte) a) dà come risultato 4, il resto della divisione tra 260, che rappresenta il valore contenuto nella variabile a, e 256, che rappresenta il massimo valore assegnabile alla variabile b (poiché ricordiamo che in un tipo byte il range di valori assegnabile va da 0 a 255). L’espressione (b = (byte) d) invece dà come risultato 67, poiché prima il valore della variabile d (323.123) viene troncato della sua parte decimale (diventando 323) e poi viene generato il modulo tra e 256, per le stesse ragioni viste per la prima espressione.

323

Infine, in espressioni con valori diversi da un tipo int (per esempio valori di tipo o short) Java convertirà automaticamente gli operandi nel tipo int, e se tale valore

byte

deve essere assegnato a una variabile, la stessa dovrà essere sempre di tipo int; se è di tipo differente, allora il valore dovrà essere convertito verso tale tipo con un cast specifico. Snippet 2.13 Operandi promossi in int. byte b = 2; int i; short s = 111; // ok valido b e s sono stati convertiti in int e possono essere assegnati // a i che è di tipo int i = b * s; // non valido poiché anche se b è di tipo byte e il valore è nel suo range, // gli operandi b e s sono stati convertiti direttamente in int // b = b + s; // ERRORE - incompatible types: possible lossy conversion from int to byte // ... quindi si deve prevedere uno specifico cast b = (byte) (b + s); // CORRETTO

Se, invece, un operando è di tipo long, allora tutta l’espressione sarà long, se un operando è di tipo float allora tutta l’espressione sarà float e se un operando è di tipo allora tutta l’espressione sarà double.

double

Snippet 2.14 Espressione con operandi di differente tipo. byte b = 111; char c = 'd'; short s = 444; int i = 2131; long l = 2112; float f = 5.6f; double d = 3322.11; double ris = (c*i) + (f*b) - (d/s) / l;

Nello Snippet 2.14 l’intera espressione sarà valutata ed eseguita come segue: 1.

(c*i)

sarà convertito in un int con il valore 213100.

2.

(f*b)

3.

(d/s)

sarà convertito in un float con il valore 621.6. sarà convertito in un double con il valore 7.48222972972973.

4. il punto 3) / l è sempre un double con il valore 0.00354272240990991.

5. il punto 1) + il punto 2) dà un float con il valore 213721.6. 6. il punto 5) – il punto 4) dà un double con il valore 213721.5902072776. 7.

sarà uguale a un valore double ovvero quello di cui il punto 6.

ris

In conclusione, per valutare se un’espressione genera un valore dello stesso tipo della variabile di destinazione bisogna sempre guardare al tipo degli operandi che ha il massimo range di valori rappresentabili. Ciò significa, dunque, che se un’espressione ha un operando di tipo double e altri operandi di tipo differente, l’intera espressione darà sempre il valore convertito in double, perché un double sicuramente potrà contenere valori interi (long, int, short e byte) e valori float.

Capitolo 3

Array

Un array è una variabile che contiene un riferimento a un’area di memoria al cui interno vi sono dei dati del suo stesso tipo. È pertanto definibile come una sorta di struttura omogenea contenente un insieme di variabili a cui ci si riferisce come un’unica e indivisibile entità. Inoltre ha natura statica, poiché mantiene la propria dimensione – il numero di elementi che contiene – dall’inizio alla fine del programma (anche se a un array si può passare un riferimento di un altro array cambiandogli, di fatto, la dimensione).

Array monodimensionali Un array monodimensionale, definito anche vettore, è una struttura dati composta da una serie di variabili disposte in una singola riga o colonna, e si dichiara utilizzando la sintassi che segue. Sintassi 3.1 Dichiarazione di un array. type variable_name[] type[] another_variable_name

dove type indica il tipo di dato che avranno gli elementi costituenti l’array, mentre le parentesi quadre [] (dette operatore di subscript), poste indifferentemente o dopo l’identificatore o dopo il tipo, sono obbligatorie e stanno a indicare che la variabile sarà un array del tipo stabilito. QUALE SINTASSI SCEGLIERE La scelta della sintassi per la dichiarazione di un array assume una certa importanza quando si dichiarano in successione più variabili dello stesso tipo, poiché, se non si presta attenzione si può incorrere in errori di semantica. Per esempio, quando scriviamo un’istruzione come int[] a, b, c[], la variabile a e la variabile b sono array di interi, mentre la variabile c è un array di array. Se

invece scriviamo int a, b[], c, la variabile a e la variabile c sono normali variabili primitive di interi, mentre la variabile b è un array di interi. In pratica, ponendo l’operatore di subscript subito dopo il nome del tipo si indica che tutte le variabili dichiarate saranno degli array di quel tipo, mentre il contrario non sarà mai vero.

L’inizializzazione di un array si attua utilizzando la sintassi che segue. Sintassi 3.2 Inizializzazione di un array. variable_name = new type[nr_of_elements]

dove variable_name è una variabile di tipo array già dichiarata, type è il suo tipo e nr_of_elements rappresenta il numero di elementi che essa conterrà. La keyword new in questo contesto è obbligatoria. In concreto, un array si crea dichiarandolo e inizializzandolo nel modo seguente. Snippet 3.1 Creazione e inizializzazione di un array. int c[]; // dichiaro c = new int[10]; // inizializzo e alloco memoria

Analizzando lo Snippet 3.1 vediamo che: nella fase di dichiarazione comunichiamo al compilatore che la variabile c è di tipo array e gli elementi costituenti saranno di tipo intero;

nella fase di inizializzazione comunichiamo al compilatore che per la variabile c dovrà creare (allocare) un’area di memoria capace di contenere 10 valori di tipo intero (Figura 3.1). L’allocazione è ottenuta utilizzando l’operatore new che, in sostanza, crea un nuovo oggetto di tipo array passandone il riferimento alla variabile c. L’array peserà in memoria 40 byte, valore che si ottiene moltiplicando il numero degli elementi (10) per lo spazio occupato dal tipo di dato (un int occupa 4 byte). Quando si crea un array, il compilatore inizializza automaticamente gli elementi con un valore che per i tipi numerici è 0, per i riferimenti è null, per i booleani è false e per i char è il valore Unicode \u0000. Snippet 3.2 Creazione di più array. int[] a, b, c; a = new int[3]; b = new int[4]; c = new int[5];

Ogni elemento dell’array è posizionato al suo interno secondo un indice numerico che parte dal valore 0 e termina con il valore espresso tra le parentesi quadre meno il valore 1. Gli array a, b e c di tipo intero dello Snippet 3.2 saranno strutturati come nella Figura 3.2.

Figura 3.1 Array di 10 elementi di tipo intero. CALCOLARE LA CORRETTA QUANTITÀ DI MEMORIA DI UN ARRAY In realtà il calcolo dello spazio in memoria occupato da un array è più complesso perché si deve tener conto anche di alcuni byte supplementari che la virtual machine alloca e che sono dipendenti dalle implementazioni. Infatti, in alcune implementazioni potrebbe essere allocato uno spazio supplementare di 12 byte, definito object header, dove vengono poste alcune informazioni di servizio per l’array (ID, flag di status e così via) e poi un eventuale spazio, definito di padding, se la

dimensione in memoria occorrente non è un multiplo di 8. Nel caso del nostro array la dimensione ricalcolata potrebbe essere di 56 byte, derivante dalla somma di: 12 (object header) + 4 * 10 (elementi di tipo int) + 4 (padding).

Figura 3.2 Array a di 3 elementi, b di 4 elementi e c di 5 elementi.

Per ottenere un particolare elemento, o per scrivere in esso, si userà il nome della variabile che riferisce l’array e l’operatore di subscript con un valore numerico (che può essere anche il risultato di un’espressione) che ne rappresenta l’indice. Snippet 3.3 Accesso e scrittura di un elemento di un array. int[] c = new int[10]; int u = 2, z = 4; c[1] = 333; // scrivo alla posizione con indice 2 int x = c[u + z]; // prendo dalla posizione con indice 6 c[10] = 1000; // ERRORE - ArrayIndexOutOfBoundsException

Lo Snippet 3.3 evidenzia come l’istruzione c[10] = 1000 generi un’eccezione software che ci dice che stiamo cercando di scrivere un valore in un elemento al di fuori dell’indice massimo dell’array. Infatti, come si è detto, la numerazione dell’indice per un array parte dal valore 0 e pertanto il calcolo dell’ultimo indice è pari al numero di elementi scritti in fase di creazione dell’array meno uno (–1). Così nel nostro caso gli indici referenziabili andranno dal valore 0 per l’elemento numero 1 al valore 9 per l’elemento numero 10. Un oggetto di tipo array conosce sempre il numero di elementi grazie alla proprietà length del suo riferimento. Snippet 3.4 Proprietà length. int[] b = new int[78]; int l = b.length; // l = 78

Gli elementi di un array si possono indicare anche all’atto dell’inizializzazione dell’array medesimo. Sintassi 3.3 Inizializzazione inline di un array. type variable_name[] = {value, …, value}

Qui, dopo aver dichiarato una variabile di tipo array, ne inizializziamo direttamente gli elementi scrivendone i valori tra parentesi graffe e separandoli con il carattere virgola (,). Snippet 3.5 Set di valori all’atto dell’inizializzazione dell’array. int c[] = {1, 5, 6, 7};

Nello Snippet 3.5 la variabile c è un array con quattro elementi i cui valori sono così indicati: c[0] ha valore 1; c[1] ha valore 5; c[2] ha valore 6; c[3] ha valore 7. Notiamo inoltre che non occorre scrivere il numero di elementi dell’array, poiché lo stesso sarà automaticamente determinato in base al numero di elementi posti tra le parentesi graffe per l’inizializzazione. Finora abbiamo visto come si creano array di valori numerici, ma è possibile creare array di diverso tipo, inclusi anche i tipi creati dal programmatore. Snippet 3.6 Array di differente tipo. class MyClass {}; boolean b[] = {false, false, true}; // array di booleani short[] s = new short[4]; // array di short byte[] by = {1, 3, 4}; // array di byte long l[] = new long[b.length]; // array di long float f[] = {12.44f, 678.12f}; // array di float double d[] = {f[0], f[1], 12E4}; // array di double String str[] = {"RED", new String("GREEN")}; // array di stringhe char ac[] = {'h', 'e', 'l', 'l', 'o'}; // array di caratteri Object[] obj = new Object[2]; // array di oggetti di tipo Object MyClass[] mc = new MyClass[11]; // array di oggetti di tipo MyClass

Per quanto attiene alla creazione di array di tipo carattere è doveroso precisare che, mentre in altri linguaggi di programmazione come per esempio C o C++, un array di caratteri è di fatto una stringa, in Java tale equivalenza è falsa, perché una stringa è un oggetto, mentre un array di caratteri è un array in cui ogni elemento è un carattere. Snippet 3.7 Array di caratteri. char c[] = "Stringa"; // ERRORE - String cannot be converted to char[] char f[] = {'S', 't', 'r', 'i', 'n', 'g', 'a'}; // LECITO String s = "Stringa"; // LECITO

Lo Snippet 3.7 evidenzia che l’array c non può contenere direttamente una stringa, poiché per il compilatore il letterale "Stringa" è un oggetto di tipo stringa, mentre la variabile c è un oggetto di tipo array di caratteri. In conclusione vediamo un semplice esempio (Listato 3.1) che crea un array di caratteri contenente un nome e poi stampa i caratteri a video scorrendo, in una struttura iterativa (for), i singoli elementi dell’array ove sono contenuti. Listato 3.1 Classe ArrayMono.

package com.pellegrinoprincipe; public class ArrayMono { public static void main(String[] args) { char name[] = {'P', 'e', 'l', 'l', 'e', 'g', 'r', 'i', 'n', 'o'}; // array di // caratteri int div = 4; for (int i = 0; i < name.length; i++) { if (i minore, con simbolo < maggiore o uguale, con simbolo >= minore o uguale, con simbolo c; // ERRORE - bad operand types for binary operator '>'

Gli operatori di uguaglianza determinano eguaglianze o diseguaglianze tra due operandi: uguale a, con simbolo di == non uguale a o diverso da, con simbolo != I tipi di dati primitivi (anche i boolean) e i riferimenti possono fare da operandi. OPERATORE DI ASSEGNAMENTO E OPERATORE DI UGUAGLIANZA Spesso si tende a confondere l’operatore di assegnamento (simbolo =) con l’operatore di uguaglianza (simbolo ==). Occorre prestare sempre attenzione alla loro differente semantica: assegnare significa inserire un valore di una variabile, letterale o costante (detta anche rvalue o right value) situata a destra dell’operando in una variabile (detta lvalue o left value), mentre uguagliare significa confrontare se due variabili contengono lo stesso valore. Per discriminarli possiamo pensare semplicemente che quando vogliamo assegnare un valore dobbiamo utilizzare il simbolo = una sola volta, mentre quando vogliamo verificare l’uguaglianza tra due valori dobbiamo utilizzare il simbolo = due volte di seguito. Snippet 4.8 Espressione con entrambi gli operatori di assegnamento e di uguaglianza. int a = 120, b = 111, c = 111, d = 112; boolean e = a < b == c > d; // true

Nello Snippet 4.8 l’espressione sarà valutata come vera (true) perché, nell’ordine, verranno eseguiti: a < b che darà false; c > d che darà false; false == false che darà true. Snippet 4.9 Valutazione di più espressioni con entrambi gli operatori. int nr1 = 120, nr2 = 111; boolean b = nr1 > nr2; // true b = nr1 >= nr2; // true b = nr1 < nr2; // false b = nr1 10 && b < 15; // AND logico espressione false c = a > 10 || b < 15; // OR logico espressione true c = a > 10 & b-- < 15; // AND logico booleano espressione false e decremento di b int d = b; // d varrà 13 c = a == 10 | b-- < 15; // OR inclusivo logico booleano espressione true e decremento di b d = b; // d varrà, ora, 12 c = a == 10 ^ b < 15; // OR esclusivo logico booleano espressione false c = (!(a > 10)); // NOT logico espressione true

Operatore ternario L’operatore ternario permette di eseguire in modo abbreviato un’istruzione del tipo if-then-else e agisce su tre operandi. Il suo simbolo è costituito dal simbolo ? e dal simbolo : che vengono posti in un particolare ordine per dare senso all’espressione complessiva. Sintassi 4.1 Operatore ternario. espressione1 ? espressione2 : espressione3

La Sintassi 4.1 si legge nel seguente modo: “Se espressione1 è true allora (rappresentato dal simbolo ?) valuta espressione2 altrimenti (rappresentato dal simbolo ) valuta espressione3”. Sia espressione2 sia espressione3 devono ritornare un valore dello

:

stesso tipo di dato della variabile che lo conterrà. Snippet 4.11 Operatore ternario. int a = 10, b = 11; String c = a < b ? "si" : "no"; // c conterrà "si"

Nello Snippet 4.11 l’espressione si legge così: “Se a è minore di b allora il letterale stringa "si" sarà assegnato alla variabile c altrimenti le sarà assegnato il letterale stringa "no"”. L’esempio seguente (Listato 4.2) mostra un utilizzo dell’operatore ternario con cui verifichiamo se, dato un valore estratto dalla matrice values che sia pari, lo stesso sia maggiore del valore 33 (filter_value); se tale verifica è positiva, lo memorizziamo in un array il cui contenuto sarà poi mostrato a video. Listato 4.2 Classe LogicalOperators. package com.pellegrinoprincipe; public class LogicalOperators { public static void main(String args[]) { // matrice per la ricerca int[][] values = {{10, 100, 30}, {-22, -11, 66}, {105, 204, 333}}; int filter_value = 33; // valori da confrontare int found_values[] = new int[9]; // numero massimo di elementi da inserire // ciclo per la ricerca for (int i = 0; i < values.length; i++) { for (int j = 0; j < values[i].length; j++) { int value = values[i][j]; // posiziono il valore trovato nell'array spostandomi // alla corretta posizione if (value % 2 == 0) found_values[i * values[j].length + j] = value > filter_value ? value : 0; } } // valori trovati

for (int i = 0; i < found_values.length; i++) System.out.print(found_values[i] + " "); System.out.println(); } }

Output 4.2 Dal Listato 4.2 Classe LogicalOperators. 0 100 0 0 0 66 0 204 0

Operatori a livello di bit (bitwise) Gli operatori a livello di bit agiscono sui singoli bit di operandi di tipo byte, short, ,

e char:

int long

NOT unario, o complemento a uno a livello di bit (bitwise complement), con simbolo ~ AND a livello di bit (bitwise AND), con simbolo & OR inclusivo a livello di bit (bitwise inclusive OR) con simbolo | OR esclusivo a livello di bit, o XOR (bitwise exclusive OR), con simbolo ^ spostamento a sinistra dei bit (left shift) con simbolo > spostamento a destra dei bit senza conservazione del segno (unsigned right shift) con simbolo >>> CONSIGLIO Per meglio comprendere il seguente paragrafo sarebbe opportuno avere almeno una conoscenza basilare dei sistemi numerici, delle conversioni tra sistemi numerici e delle operazioni aritmetiche binarie.

Per vedere come tali operatori agiscono sui bit si può osservare la Tabella 4.5, dove ogni riga contiene la valutazione di un’espressione tra A e B rispetto all’operatore utilizzato. Tabella 4.5 Operatori bitwise. A

B

~A

A & B

A | B

A ^ B

0

0

1

0

0

0

1

0

0

0

1

1

0

1

1

0

1

1

1

1

0

1

1

0

Nella tabella vediamo che: l’operatore NOT (~) inverte tutti i bit dell’operando sui cui agisce: se c’è un bit con 1 allora lo stesso diventerà 0 e viceversa; l’operatore AND ( &) confronta i bit dei due operandi: se entrambi sono 1 allora il risultato sarà 1. Tale operatore è utile per creare maschere di bit che cancellano i bit di un altro operando ponendoli a 0; l’operatore OR (|) confronta i bit dei due operandi: il risultato sarà sempre 1 tranne se entrambi sono 0. Tale operatore è utile per creare maschere di bit che

impostano i bit di un altro operando a 1; l’operatore XOR (^) confronta i bit dei due operandi: il risultato sarà 1 solo se uno sarà 1 e l’altro sarà 0. Snippet 4.12 Operatori a livello di bit. // mostriamo solo i primi 8 bit dato che i numeri sono molto piccoli… int a = 5, b = 6; // c = -6 per rappresentazione numeri negativi con complemento a due // 00000101 --> 5 // ------- ---> ~ // 11111010 --> -6 int c = ~a; // c = 4 // 00000101 --> 5 // 00000110 --> 6 // ------- ---> & // 00000100 --> 4 c = a & b; // c = 7 // 00000101 --> 5 // 00000110 --> 6 // ------- ---> | // 00000111 --> 7 c = a | b; // c = 3 // 00000101 --> 5 // 00000110 --> 6 // ------- ---> ^ // 00000011 --> 3 c = a ^ b;

Per l’operatore di spostamento a sinistra dei bit la sintassi da adottare è la seguente. Sintassi 4.2 Shift a sinistra. valore max) max = b; if (c > max) max = c; return max; } public int maximum(int a, int b, int c) // massimo tra valori interi { int max = a; if (b > max) max = b; if (c > max) max = c; return max; } public char maximum(char a, char b, char c) // massimo tra valori carattere { char max = a; if (b > max) max = b; if (c > max) max = c; return max; } } public class CalculateMaxClient { public static void main(String[] args) { CalculateMax cm = new CalculateMax(); Double d[] = { 11.1, 11.2, 9.6 };

Integer i[] = { 12, 13, 3 }; Character c[] = { 'n', 'b', 'z' }; System.out.print("Max (double): " + cm.maximum(d[0], d[1], d[2])); System.out.print(" | Max (int): " + cm.maximum(i[0], i[1], i[2])); System.out.println(" | Max (char): " + cm.maximum(c[0], c[1], c[2])); } }

Output 9.2 Dal Listato 9.2 Classi CalculateMax e CalculateMaxClient. Max (double): 11.2 | Max (int): 13 | Max (char): z

Ora invece riscriviamo entrambe le classi con dei metodi generici, considerando la seguente sintassi (Sintassi 9.1). Sintassi 9.1 Definizione di metodi generici. [modifiers] return_type methodName(Type1 t1, Type2 t2, ..., TypeN tN) { Type1 n; String s; }

Qui vediamo che si deve scrivere, prima del tipo di ritorno, una sezione formata dalle parentesi angolari < > con all’interno degli identificatori di tipo generico separati dalla virgola (,) che sono definiti come variabili o parametri di tipo formale (formal type parameters). I tipi attuali, effettivi degli argomenti passati a un metodo generico sono invece definiti come argomenti di tipo attuale (actual type arguments). Tali parametri di tipo si possono usare alla stessa stregua dei normali tipi non parametrizzati ovvero come tipi per i parametri formali, tipi per i valori di ritorno e tipi per le variabili locali. Per convenzione i parametri di tipo (indicati con Type1, Type2 e così via) sono formalizzati mediante una sola lettera scritta in maiuscolo, che varia a seconda di ciò che parametrizzano; in particolare possiamo usare E per Element (tipo di un elemento in una collezione), K per Key (tipo di una chiave in una mappa), V per Value (tipo di un valore in una mappa), N per Number (tipo di un valore numerico), T per Type (un qualsiasi altro tipo generico), S, U e così via per ulteriori tipi generici. Listato 9.3 Classi PrintArrayGeneric e PrintArrayGenericClient. package com.pellegrinoprincipe; class PrintArrayGeneric { public PrintArrayGeneric() {} public void printArray(E el[]) { for (E i : el) // stampa in modo generico gli elementi dell'array di differente tipo System.out.print(i + " "); } } public class PrintArrayGenericClient {

public static void main(String[] args) { PrintArrayGeneric pag = new PrintArrayGeneric(); Double d[] = { 11.1, 11.2 }; Integer i[] = { 12, 13 }; Character c[] = { 'a', 'b' }; String s[] = { "sono", "una", "stringa" }; System.out.print("[ "); pag.printArray(d); pag.printArray(i); pag.printArray(c); pag.printArray(s); // sintassi alternativa di invocazione di un metodo // generico System.out.print("]"); } }

Output 9.3 Dal Listato 9.3 Classi PrintArrayGeneric e PrintArrayGenericClient. [ 11.1 11.2 12 13 a b sono una stringa ]

Il Listato 9.3 mostra come, definendo un metodo generico, si riduca drasticamente il codice scritto. Abbiamo infatti scritto un solo metodo, rispetto ai tre del Listato 9.1, che fa esattamente la stessa cosa, ovvero stampa gli elementi di un array che possono essere di tipo differente. In dettaglio, il metodo printArray ha, nella sezione di dichiarazione dei parametri di tipo, il tipo E, che è utilizzato poi come tipo del parametro formale nella lista dei parametri del metodo (E el[]) e poi come tipo della variabile locale nel ciclo for (E i). Notiamo, inoltre, che nella classe PrintArrayGenericClient abbiamo aggiunto un array di oggetti String e l’abbiamo fatto stampare sempre dal metodo printArray. Quest’aggiunta, tuttavia, non ha inficiato la corretta compilazione del programma. Infatti, poiché printArray è un metodo generico, non è stato necessario scrivere nella classe PrintArrayGeneric il corrispondente metodo in overloading che accettasse un argomento di tipo array di String (cosa che, invece, avremmo dovuto fare se il linguaggio non avesse supportato i generici). Infine, è interessante evidenziare l’istruzione pag.printArray(s), che mostra un modo alternativo per invocare un metodo generico che si concretizza nel passare il tipo effettivo tra le parentesi angolari < > prima del nome del metodo stesso. Tuttavia tale sintassi non è necessaria; infatti si può tranquillamente omettere di scrivere il tipo tra le parentesi angolari, poiché il compilatore è in grado di “capirlo” autonomamente analizzando il tipo passato come argomento, come mostrato per le altre invocazioni del metodo printArray (type inference). MIGLIORAMENTI DELLA TYPE INFERENCE PER I METODI GENERICI Con la versione 8 di Java si è migliorata la capacità del compilatore di determinare automaticamente il tipo di argomento di un metodo generico (Snippet 9.1). Infatti, grazie alle

indicazioni del documento di proposta accettato per la corrente versione di Java – JDK Enhancement Proposal (JEP) 101: Generalized Target-Type Inference – il compilatore è ora in grado di inferire in autonomia il tipo di argomento quando il risultato di un’invocazione di metodo è passato come argomento a un altro metodo (inference in argument position o inference in method context). Snippet 9.1 Inference in method context. static void printListElements(List list) { for (Integer elem : list) System.out.println(elem); } static List factorList(int capacity) { List list = new ArrayList(capacity); return list; } // inference in method context con Java 8 - OK - nessun errore di compilazione // con Java 7 avremo, però, il seguente errore di compilazione: // incompatible types: List cannot be converted to List printListElements(factorList(10));

NOTA È bene rammentare che non tutti i goal del JEP 101 sono stati raggiunti. Infatti, l’inference in chained calls, ovvero l’inferenza del tipo di argomento quando si hanno chiamate a catena di metodi generici, non è stato implementato. Per esempio, l’istruzione Iterator iterator = new ArrayList().iterator()

genererà il seguente errore di compilazione: incompatible

types: Iterator cannot be converted to Iterator.

Listato 9.4 Classi CalculateMaxGeneric e CalculateMaxGenericClient. package com.pellegrinoprincipe; class CalculateMaxGeneric { public T maximum(T a, T b, T c) { T max = a; if (b.compareTo(max) > 0) max = b; if (c.compareTo(max) > 0) max = c; return max; } } public class CalculateMaxGenericClient { public static void main(String[] args) { CalculateMaxGeneric cmg = new CalculateMaxGeneric(); Double d[] = { 11.1, 11.2, 9.6 }; Integer i[] = { 12, 13, 3 }; Character c[] = { 'n', 'b', 'z' }; String s[] = { "sono", "una", "stringa" }; Double d_max = cmg.maximum(d[0], d[1], d[2]); Integer i_max = cmg.maximum(i[0], i[1], i[2]); Character c_max = cmg.maximum(c[0], c[1], c[2]); String s_max = cmg.maximum(s[0], s[1], s[2]); // stampa del valore massimo trovato System.out.print("Max (double): " + d_max);

System.out.print(" | Max (int): " + i_max); System.out.print(" | Max (char): " + c_max); System.out.println(" | Max (String): " + s_max); } }

Output 9.4 Dal Listato 9.4 Classi CalculateMaxGeneric e CalculateMaxGenericClient. Max (double): 11.2 | Max (int): 13 | Max (char): z | Max (String): una

Il Listato 9.4 definisce la classe CalculateMaxGeneric con il metodo maximum che ha, nella sezione dei parametri di tipo formali, un parametro di tipo T che estende (extends) un’interfaccia generica di tipo Comparable. NOTA Se si devono estendere più classi o più interfacce (nei generici la keyword extends si usa anche per le interfacce) occorre utilizzare il carattere & (Snippet 9.2). Snippet 9.2 Utilizzo del carattere &. public static HelloWorldFromAServlet com.pellegrinoprincipe.HelloWorldFromAServlet HelloWorldFromAServlet /HW_servlet

Figura 23.2 servlets.war in esecuzione.

Il Listato 23.2 è costituito principalmente dal tag servlet con i tag annidati servlet, che indica il nome logico della servlet, e servlet-class, che indica il nome della

name

classe Java della servlet. Abbiamo poi il tag servlet-mapping con i tag annidati servlet-

, che ha lo stesso significato visto in precedenza, e url-pattern, che imposta l’URL

name

tramite il quale viene eseguita la servlet. Tale URL può essere indicato, mediante un pattern, in modalità prefissa oppure mediante un modalità definita come suffisso di estensione. Nel nostro caso il tag url-pattern contiene la stringa prefissa /HW_servlet, che indica che un path con tale nome sarà servito dalla servlet indicata nel relativo tag servlet-name. La forma suffissa deve essere scritta utilizzando il carattere asterisco (*) seguito dal carattere punto (.) e da qualsiasi altro carattere. Per esempio, se nel nostro file web.xml avessimo scritto nel tag url-pattern la stringa *._servlet, avremmo potuto invocare la relativa servlet utilizzando URL quali hello._servlet, ciao._servlet e così via. ATTENZIONE Il carattere asterisco (*), nell’ambito della definizione di un URL pattern ha solo i seguenti significati: se è indicato come /* saranno validi tutti i caratteri scritti da quel punto in poi; se è indicato come *.extension saranno validi tutti i caratteri scritti prima di .extension. Se l’asterisco è scritto in qualsiasi altra posizione, verrà interpretato letteralmente. Così, per esempio, un URL pattern come /HW_servlet/* sarà valido se scriveremo /HW_servlet/XX, /HW_servlet/AHAH e così via, mentre un URL pattern come /HW_serv* sarà valido solo se scriveremo /HW_serv*.

Utilizzo delle annotazioni La versione 3.0 delle API delle servlet, inserita nella release 6 della piattaforma JEE (in JEE 7 è inclusa la versione 3.1), ha introdotto numerose novità, tra cui quella di poterle configurarle senza utilizzare l’XML e il relativo file di descrizione, ma solamente con l’ausilio delle annotazioni: per la dichiarazione di una servlet; ha delle chiavi con cui indicare: se

@WebServlet

supporta il processing asincrono (asyncSupported); una descrizione (description); un breve nome (displayName); dei parametri di inizializzazione (initParams); dei nomi di file di icone GIF o JPEG (largeIcon e smallIcon); l’ordine in cui deve essere caricata (loadOnStartup); un nome (name); degli URL pattern (urlPatterns); per la dichiarazione di una classe che implementa un’interfaccia di tipo

@WebFilter

che consente di intercettare e manipolare una richiesta prima che essa

Filter

giunga alla servlet (pre-processing), così come di manipolare una risposta dopo che è stata gestita dalla servlet per esempio nel metodo doGet (post-processing), ma prima che il suo output sia inoltrato al client. Ha le chiavi: asyncSupported; ;

;

;

;

;

;

, per

description displayName initParams largeIcon smallIcon urlPatterns servletNames

indicare le servlet a cui un filtro si applica; filterName, per indicare il nome di un

filtro; dispatcherTypes, per indicare il tipo di dispatcher a cui il filtro è applicato (per default è javax.servlet.DispatcherType.REQUEST); per la dichiarazione di una classe che implementa un’interfaccia di

@WebListener

tipo ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ,

oppure HttpSessionAttributeListener.

ServletRequestAttributeListener HttpSessionListener

L’implementazione di tali tipi consente di gestire gli eventi che possono accadere durante il ciclo di vita di una servlet, quali la creazione e la distruzione delle richieste HTTP, la creazione e la distruzione delle sessioni, l’aggiunta e la rimozione di attributi a una sessione e così via. Listato 23.3 Classe TimerServlet. ... @WebServlet(name = "TimerServlet", urlPatterns = {"/timer"}, initParams = { @WebInitParam(name = "develop", value = "true")}) public class TimerServlet extends HttpServlet { protected void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); String devel_txt = "Ok Servlet pronta…"; String prod_txt = "Ok Servlet in produzione…"; try (PrintWriter out = response.getWriter()) { boolean is_develop = Boolean.valueOf(getInitParameter("develop")); ... } } ... }

Listato 23.4 Classe TimerFilter. ... @WebFilter(filterName = "TimerFilter", urlPatterns = {"/timer"}) public class TimerFilter implements Filter { private FilterConfig fc; private String valid_ip[] = {"192.168.0.66", "192.168.0.22", "192.168.0.11"}; public void init(FilterConfig filterConfig) throws ServletException { fc = filterConfig; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { PrintWriter pw = response.getWriter(); // controlla se la servlet è invocata da un IP valido // e solo in quel caso procedi con la sua esecuzione. // Permetti l'accesso se si è in localhost!!! String ip = request.getRemoteAddr(); if (Arrays.asList(valid_ip).contains(ip) || ip.equals(request.getLocalAddr())) { Calendar cal = Calendar.getInstance(); SimpleDateFormat sd_f = new SimpleDateFormat(); pw.println("Richiesta inoltrata in data: " + sd_f.format(cal.getTime())); chain.doFilter(request, response); // propaga alla servlet } else

pw.println("Spiacenti non si è autorizzati ad eseguire l'applicazione dall'IP: " + ip); } public void destroy() { fc = null; } }

Figura 23.3 annotated_servlets.war in esecuzione.

Il Listato 23.3 definisce la classe TimerServlet decorandola con l’annotazione dove abbiamo utilizzato gli attributi name, urlPatterns e initParams. In

@WebServlet

particolare quest’ultima chiave consente di impostare dei parametri di inizializzazione per la servlet, attraverso l’annotazione @WebInitParam, definendone gli attributi name per il nome del parametro e value per il suo valore. Nel nostro caso abbiamo definito un parametro di nome develop con valore true, che viene poi ottenuto tramite il metodo getInitParameter della classe GenericServlet, e che ci serve per verificare se la nostra servlet si trova ancora nello stadio di sviluppo oppure in quello di produzione. Il Listato 23.4 definisce la classe TimerFilter, che implementa l’interfaccia Filter, consentendoci di programmare un filtro tramite il quale, prima che giunga la richiesta alla servlet TimerServlet, nel metodo doFilter, verifichiamo che la stessa sia stata inoltrata da un client valido, comparando il suo IP con quelli definiti nella white list dell’array di stringhe valid_ip. Se tale comparazione ha esito positivo, visualizzeremo nel browser la data e l’ora corrente della richiesta e invocheremo nuovamente il metodo doFilter, tramite però il riferimento chain di tipo FilterChain, al fine di “passare” il controllo alla risorsa successiva, la servlet TimerServlet, che eseguirà i propri compiti. Se, invece, la richiesta è stata inoltrata da un client non autorizzato, sarà visualizzata nel browser una stringa informativa adeguata, interrompendo di fatto la richiesta della servlet, che non sarà pertanto mai eseguita. Per quanto attiene alle annotazioni utilizzate, la chiave urlPatterns è fondamentale perché deve avere lo stesso valore di quello impostato per la servlet TimerServlet, in modo da “intercettarla” prima che venga eseguita.

Infine, notiamo la definizione dei metodi init e destroy invocati dal web container rispettivamente quando il filtro è istanziato e quando è rimosso. In dettaglio vediamo che il metodo init ha un parametro di tipo FilterConfig che dispone di metodi utili sia a ottenere informazioni quali il nome del filtro, il nome dei parametri di inizializzazione e così via, sia per ottenere (metodo getServletContext) un riferimento al contesto di esecuzione della servlet, attraverso il quale quest’ultima potrà comunicare. ATTENZIONE Per testare il funzionamento del programma entrambe le classi TimerServlet e TimerFilter devono essere inserite nella cartella classes del file annotated_servlets.war.

Processing dei parametri di una richiesta L’impiego più frequente di una web application è quello in cui un utente immette dei dati in appositi controlli grafici (caselle di testo, di selezione, menu a tendina e così via) posti in un form e poi ne esegue il cosiddetto submit, ovvero invia i relativi valori a una pagina che è in grado di processarli. Nel caso delle servlet, per ottenere tali dati dobbiamo utilizzare i metodi getParameter e getParameterValues, dichiarati nell’interfaccia ServletRequest, laddove il primo ritorna il valore di un parametro come stringa mentre il secondo ritorna più valori associati a un parametro come array di stringhe (si pensi al caso delle caselle di controllo possiamo selezionarne più di una alla volta). Se il parametro non esiste viene ritornato il valore null. NOTA I metodi getParameter e getParameterValues sono utilizzabili anche con le richieste di tipo GET dove i parametri sono passati nella query string. Listato 23.5 File Data.html. ... ... ... ...

Il Listato 23.5 definisce la pagina HTML visibile nella Figura 23.4 (che dovrà essere stata copiata nella document root della nostra applicazione web, ovvero nella sua directory padre nella quale si troveranno altre directory e le directory WEB-INF), che contiene un form con vari controlli grafici che consentono di inserire informazioni che saranno processate dall’URL params indicato nell’attributo action del tag form.

Figura 23.4 Pagina web Data.html. Listato 23.6 Classe ProcessingParamsServlet. ... @WebServlet(name = "ProcessingParamsServlet", urlPatterns = {"/params"}) public class ProcessingParamsServlet extends HttpServlet { protected void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); // ottenimento dei parametri String nome = request.getParameter("nome") != null ? request.getParameter("nome") : "n.d"; String cognome = request.getParameter("cognome") != null ? request.getParameter("cognome") : "n.d"; String sesso = request.getParameter("sesso") != null ? request.getParameter("sesso") : "n.d"; String linguaggi[] = request.getParameterValues("linguaggi") != null ? request.getParameterValues("linguaggi") : new String[]{"n.d"}; String sistemi = request.getParameter("sistemi") != null ? request.getParameter("sistemi") : "n.d"; try (PrintWriter out = response.getWriter()) { ... // tabella di formattazione StringBuilder datas = new StringBuilder(""); datas.append("Nome:  " + nome + ""); datas.append("Cognome:  " + cognome + ""); ... } } ... }

Figura 23.5 process_params.war in esecuzione (avviare l’applicazione mediante Data.html).

Il Listato 23.6 definisce la classe servlet ProcessingParamsServlet, eseguibile invocando l’URL params impostato tramite la chiave urlPatterns nell’annotazione , la quale nel metodo doRequest utilizza i metodi getParameter e

@WebServlet

per ottenere i valori dei widget contenuti nella pagina web Data.html.

getParameterValues

Tali metodi accettano come argomento una stringa che indica il nome del parametro da processare e il cui valore deve essere uguale a quello dell’attributo name del tag HTML che ha definito il controllo grafico relativo, oppure a quello di un parametro inserito nella query string. I valori ricevibili possono essere impostabili, per quanto riguarda i widget di un form HTML, tramite il loro attributo value, mentre per quanto riguarda una query string scrivendoli dopo il segno di uguale relativo al parametro da inizializzare. DETTAGLIO In un form HTML, se in un pulsante d’opzione o in una casella di controllo non scriviamo l’attributo value, il loro valore, se selezionati, sarà automaticamente definito come on. In un menu a tendina (elemento select), il valore sarà invece ricavato dal testo inserito all’interno del tag relativo, ma solo se non l’avremo indicato nel corrispettivo attributo value.

Alcuni metodi del tipo HttpServletRequest ritorna un array di oggetti Cookie inviati dal client con una

Cookie[] getCookies()

richiesta. Ricordiamo che un cookie è un piccolo file di testo al cui interno possono essere memorizzate delle informazioni per un successivo utilizzo. String getHeader(String name) ritorna il valore dell’header specificato dal parametro .

name

ritorna un’enumerazione contenente tutti

Enumeration getHeaders(String name)

i valori dell’header specificato dal parametro name. ritorna un’enumerazione con il nome di tutti gli

Enumeration getHeaderNames()

header di una richiesta. String getQueryString() ritorna la query string. ritorna una stringa indicante l’ID di sessione

String getRequestedSessionId()

assegnato dal server. ritorna una stringa contenente l’URL completo della

StringBuffer getRequestURL()

richiesta senza però l’eventuale query string.

ritorna una stringa contenente la “parte” dell’URL che ha

String getServletPath()

mappato la relativa servlet.

Alcuni metodi del tipo HttpServletResponse aggiunge alla risposta il cookie del parametro cookie.

void addCookie(Cookie cookie)

ritorna il valore dell’header di una risposta (parametro

String getHeader(String name)

).

name

ritorna una collezione di tutti i valori di un

Collection getHeaders(String name)

response header (parametro name). ritorna una collezione di tutti i nomi degli -

Collection getHeaderNames()

header di una risposta. int getStatus() ritorna il codice di stato di una risposta, per esempio 200 (OK, richiesta soddisfatta), 404 (Not Found, la risorsa richiesta non è presente), 500 (Internal Server Error) e così via. aggiunge un header di una risposta con il

void addHeader(String name, String value)

nome fornito dal parametro name e il valore fornito dal parametro value. Se l’header indicato è già stato impostato, il nuovo valore indicato sarà aggiunto agli altri eventualmente presenti. void setHeader(String name, String value) imposta un header di una risposta con il nome fornito dal parametro name e il valore fornito dal parametro value. Se l’header indicato è già stato impostato, il nuovo valore indicato sostituirà quello già presente. ATTENZIONE I metodi getHeader, getHeaders e getHeaderNames ritornano i rispettivi valori solo se gli header di una risposta sono stati precedentemente impostati tramite appositi metodi di set quali setHeader, addHeader e così via.

Persistenza dei dati Quando si sviluppano applicazioni web, può essere necessario memorizzare informazioni o dati che possono essere successivamente utilizzati nell’ambito di altre pagine web (nel nostro specifico caso, da altre servlet). A tal fine, le API JEE mettono a disposizione due meccanismi di storage: il primo fa uso di oggetti collegati a delle sessioni e i cui dati sono relativi solo all’attuale utente connesso (session

scope), mentre il secondo fa uso di oggetti collegati al contesto esecutivo delle servlet e i cui dati sono visibili anche a più utenti (application scope). DETTAGLIO La memorizzazione e il mantenimento dei dati che persistono tra differenti pagine web sono demandati a una web application e alle API che il relativo framework di sviluppo mette a disposizione, perché il protocollo HTTP è per sua natura stateless (senza memoria), ovvero non è fornito di un meccanismo per il mantenimento dello stato di un client. Ciò implica che esso tratta ogni richiesta di un client in modo unico e indipendente senza alcuna relazione tra le precedenti richieste.

Vediamo ora un’applicazione web che consente a un utente di scegliere, dalla pagina web Colors.html, un colore di sfondo e un colore per il testo che saranno utilizzati dalla servlet ManagingDataServlet (URL managedata) nella composizione di una pagina web personale nella quale sarà anche mostrato quanti utenti sono connessi. La definizione di questi dati, sia nella sessione sia nel contesto applicativo, è effettuata nella servlet PersistingDataServlet (URL persistingdata) richiamata quando si effettua il submit del form dalla pagina web Color.html. Listato 23.7 File Colors.html. ... ... ... ...

Il Listato 23.7 mostra il file Colors.html nel quale abbiamo creato un form con due campi di testo (colore_back e colore_front) tramite i quali possiamo impostare i colori di sfondo e di primo piano, i cui valori saranno inoltrati alla servlet persistingdata dell’attributo action del modulo. Listato 23.8 Classe PersistingDataServlet. ... @WebServlet(name = "PersistingDataServlet", urlPatterns = {"/persistingdata"}) public class PersistingDataServlet extends HttpServlet { private HttpSession session; private ServletContext context; protected void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // ottengo i parametri String colore_back = request.getParameter("colore_back") != null ? request.getParameter("colore_back") : "black"; String colore_front = request.getParameter("colore_front") != null ? request.getParameter("colore_front") : "white"; // utilizzo delle sessioni per memorizzare i colori session = request.getSession();

session.setAttribute("colore_back", colore_back); session.setAttribute("colore_front", colore_front); // utilizzo del contesto per memorizzare quanti utenti sono collegati context = request.getServletContext(); Integer users = (Integer) context.getAttribute("users"); context.setAttribute("users", users == null ? 1 : ++users); // reindirizzo la risposta a un'altra servlet response.sendRedirect("managedata"); } ... }

Il Listato 23.8 mostra la definizione della classe PersistingDataServlet dove, all’interno del metodo doRequest, otteniamo un oggetto di tipo HttpSession, tramite il metodo getSession del riferimento request, per gestire la sessione corrente associata a tale richiesta. A tale oggetto sessione sono collegati, tramite il metodo setAttribute, due oggetti stringa (colore_back e colore_front) che conterranno i valori relativi al colore di sfondo e di primo piano scelti dall’utente e ottenuti tramite il consueto metodo getParameter dell’oggetto request. In particolare vediamo che il metodo setAttribute accetta due argomenti di cui il primo rappresenta il nome dell’oggetto da collegare e il secondo rappresenta un’istanza dell’oggetto stesso. Dopo aver effettuato la memorizzazione dei valori dei colori nella sessione utente, otteniamo il contesto esecutivo della servlet corrente (metodo getServletContext dell’interfaccia ServletRequest) al fine di memorizzare, sempre con il metodo invocato però sul riferimento context di tipo ServletContext, globalmente e

setAttribute

per tutti gli utenti, il numero di utenti connessi. Infine, utilizziamo il metodo sendRedirect dell’oggetto response per reindirizzare il browser verso la servlet indicata dall’argomento passato, che nel nostro caso è la classe ManagingDataServlet mappata all’URL managedata. Listato 23.9 Classe ManagingDataServlet. ... @WebServlet(name = "ManagingDataServlet", urlPatterns = {"/managedata"}) public class ManagingDataServlet extends HttpServlet { private HttpSession session; private ServletContext context; protected void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); // ottengo la sessione per sapere i colori scelti session = request.getSession(); String colore_back = (String) session.getAttribute("colore_back"); String colore_front = (String) session.getAttribute("colore_front"); // ottengo il contesto per sapere il numero di utenti connessi

context = request.getServletContext(); Integer users = (Integer) context.getAttribute("users"); // formattazione della risposta try (PrintWriter out = response.getWriter()) { ... out.println(""); out.print("Benvenuto nella tua personal home page"); out.println(" [utenti online: " + users + "]"); ... } } ... }

Figura 23.6 data_servlets.war in esecuzione (avviare l’applicazione mediante Colors.html).

Il Listato 23.9 mostra la definizione della servlet ManagingDataServlet che, nel metodo , utilizza il metodo getAttribute dell’oggetto session per ottenere gli oggetti

doRequest

stringa impostati precedentemente tramite cui leggere i valori dei colori scelti e dell’oggetto context per ottenere il valore relativo al numero di utenti connessi. Tale metodo getAttribute accetta come argomento il nome dell’oggetto collegato di cui si vuole conoscere il corrispondete valore. Infine costruiamo la pagina HTML di risposta, dove l’elemento body avrà il colore di sfondo come indicato dalla variabile colore_back, mentre l’elemento h1 avrà, oltre al colore di primo piano come indicato dalla variabile colore_front, anche del testo con l’indicazione del numero di utenti collegati. NOTA Occorre ricordare di avviare l’applicazione puntando al file Colors.html e poi, per verificare che ogni utente abbia una sua sessione indipendente dalle altre, utilizzare per esempio un altro browser cambiando il valore dei colori. È utile controllare anche che il numero di utenti sia maggiore di uno. Precisiamo che il calcolo del numero di utenti è piuttosto semplice, avendo solo una valenza didattica e nessuna utilità pratica, in quanto si incrementa la variabile users ogni qualvolta l’utente sceglie i colori senza controllare se lo stesso è già stato considerato nel conteggio (magari memorizzandone e verificandone successivamente l’indirizzo IP).

Alcuni metodi del tipo HttpSession ritorna un’enumerazione di stringhe

Enumeration getAttributeNames()

contenenti il nome di tutti gli oggetti collegati a una sessione.

ritorna un valore long indicante il tempo di creazione di

long getCreationTime()

questa sessione. Tale valore rappresenta i millisecondi passati dalla data di riferimento 1/1/1970 GMT (Unix o POSIX time). String getId() ritorna una stringa contenente il valore univoco dell’identificatore assegnato alla sessione. ritorna un intero indicante il numero di secondi di

int getMaxInactiveInterval()

inattività di un client allo scadere dei quali una sessione viene invalidata. void setMaxInactiveInterval(int interval) imposta, tramite il parametro interval, il numero di secondi di inattività di un client allo scadere dei quali una sessione viene invalidata. void removeAttribute(String name) rimuove l’oggetto collegato alla sessione del parametro name.

JavaServer Pages Le servlet, come abbiamo visto, nonostante permettano di sviluppare applicazioni web in modo relativamente semplice, hanno un grosso svantaggio legato al fatto che si è costretti a “incapsulare” il layer strutturale (o di contenuto) e quello di presentazione direttamente nella servlet stessa, in apposite istruzioni di output. Ovviamente non è una strada praticabile, soprattutto quando si sviluppano applicazioni di un certo livello dove è necessario, ai fini della scalabilità e della manutenibilità del codice, avere una netta separazione tra i layer. Per far fronte a tale problema è stata creata la tecnologia delle JavaServer Pages, tramite la quale si creano pagine web con estensione .jsp dove si scrive comune codice HTML e codice Java inserito in appositi marcatori che consente la generazione di contenuto dinamico. Per lo sviluppo di una pagina JSP possiamo utilizzare i seguenti elementi. Le direttive: con esse si forniscono disposizioni o istruzioni di vario tipo utilizzabili dall’engine delle pagine JSP. Si classificano in: direttive di pagina (page directive), con le quali si definiscono gli attributi per la pagina corrente (linguaggio usato, tipo di contenuto, encoding dei caratteri, se si ha accesso alle sessioni, se tale pagina è una pagina di errore e così via); direttive di inclusione (include directive), con le quali si consente l’inclusione, in una pagina JSP, di altre risorse (file HTML, JSP, XHTML e così via); direttive per l’utilizzo di librerie di tag custom (taglib directive), con le quali si specificano le librerie di tag custom impiegabili nella pagina corrente. Tutte le direttive indicate sono utilizzabili mediante i marcatori dove, dopo il simbolo di @, porremo il nome del tipo di direttiva da usare: page, include o taglib. Gli scriptlet: sono rappresentati da codice Java inserito tra i marcatori . I commenti: sono costituiti da frammenti di testo utili a documentare il codice JSP. Sono scritti tra i marcatori . Le espressioni: sono rappresentate, per l’appunto, da espressioni Java, valutate e convertite in stringhe, il cui risultato viene inviato automaticamente in output al browser senza la necessità di utilizzare un apposito oggetto. Sono poste tra i marcatori . Le dichiarazioni: sono rappresentate da dichiarazioni di variabili e metodi che diventano disponibili “globalmente” per tutti gli altri elementi di scripting della pagina. Tali dichiarazioni devono essere poste tra i marcatori .

Gli oggetti impliciti: sono rappresentati da variabili contenenti riferimenti di oggetti automaticamente disponibili nella pagina JSP in quanto sono stati già dichiarati e inizializzati dall’application server. Tra di essi abbiamo: out di tipo ,

di tipo HttpServletRequest, response di tipo HttpServletResponse, session

JspWriter request

di tipo HttpSession, application di tipo ServletContext e config di tipo ServletConfig. Le azioni: sono rappresentate da tag, inseriti in apposite librerie e scritti secondo una sintassi XML, in grado di compiere determinate funzionalità quando una pagina JSP viene richiesta (inserire altre risorse, inoltrare una richiesta a un’altra pagina, inviare un messaggio e-mail, scrivere un file sul server e così via). L’utilizzo di tali azioni consente sia di migliorare la leggibilità e la manutenibilità delle pagine JSP, perché possono essere scritte senza l’embedding diretto di codice Java (scriptlet, espressioni e così via), sia di poter impiegare per il loro sviluppo dei web developer che non necessitano di conoscere il linguaggio Java. Le azioni si suddividono in tre categorie: standard, che consentono di utilizzare un piccolo insieme di azioni comuni e sono disponibili sin dalla versione 1.0 della specifica; custom, sviluppate mediante le API JSP o appositi file di tag, che consentono di dotare un’applicazione JSP di tag personalizzati che permettono di utilizzare funzionalità particolari non previste dalla specifica; JSTL, implementate come le azioni custom, che estendono di fatto le azioni standard fornendo un ricco set di tag che copre svariate funzionalità (processing con condizioni e loop, internazionalizzazione e formattazione, uso di SQL e così via). Qualsiasi tipo di azione è scritta secondo la convenzione della Sintassi 23.1 che, in pratica, è piuttosto simile a quella usata per i comuni tag HTML. Nel caso delle azioni abbiamo: un tag di apertura al cui interno porre il nome di un prefisso, indicante un namespace XML, seguito dal carattere due punti e dal nome dell’azione; degli attributi con i corrispettivi valori; un eventuale corpo che può contenere del testo o altri tag; un tag di chiusura con la ripetizione del prefisso più il nome. Sintassi 23.1 Azione. body

Il linguaggio di espressione unificato (UEL): è rappresentato da un vero e proprio linguaggio, con una sua sintassi, operatori, keyword riservate e così via, che è nato con lo scopo di eliminare l’utilizzo del codice Java all’interno delle pagine JPS. La sua sintassi di base (Sintassi 23.2 e 23.3) è data dall’utilizzo del

carattere dollaro $ o cancelletto # seguito dalle parentesi graffe { } al cui interno porre l’espressione da valutare. Sintassi 23.2 UEL per espressioni con una valutazione immediata. ${expression}

Sintassi 23.3 UEL per espressioni con una valutazione differita. #{expression}

IL LINGUAGGIO DI ESPRESSIONE Il linguaggio di espressione o EL (Expression Language) fu introdotto la prima volta nella versione 1.0 delle JSTL, traendo ispirazione dai linguaggi ECMAScript (di cui di JavaScript ne è un illustre dialetto) e XPath (un linguaggio per ricercare le informazioni in un documento XML) con l’obiettivo di facilitare ai web developer l’accesso e la manipolazione dei dati dell’applicativo senza utilizzare Java. In seguito, grazie al successo ottenuto, fu compreso nelle specifiche JSP 2.0/JSTL 1.1 rendendolo disponibile non solo per gli attributi dei tag ma anche per le pagine JSP. Quando però fu realizzata la specifica 1.0 delle JavaServer Faces, si constatò che l’expression language disponibile per le JSP non era idoneo, e così se ne sviluppo un’apposita variante. Tuttavia, successivamente ci si rese conto che non era opportuno e conveniente avere due linguaggi separati, e così si decise di unire gli sforzi e di unificarli in uno solo che ottemperasse alle diverse esigenze. Nacque così lo Unified Expression Language, che venne definito in un’apposita specifica (Expression Language Specification Version 2.1) e che fu operativo a partire dalla versione 2.1 delle JSP e 1.2 delle JSF. Attualmente è giunto alla versione 3.0.

Una prima applicazione Per illustrare come scrivere pagine JSP riprogettiamo l’applicazione precedentemente vista per le servlet, che consente di scegliere dei colori da impiegare per la personalizzazione di una pagina web, definendo i seguenti file: ColorsJSP.html, ,

e DataManagementError.jsp.

PersistingDataJSP.jsp ManagingDataJSP.jsp

Listato 23.10 File ColorsJSP.html. ... ... ...

Il Listato 23.10 è simile al Listato 23.7 (Colors.html) con la sola differenza che indica per l’attributo action dell’elemento form non più l’URL mapping della servlet , ma direttamente il file JSP PersistingDataJSP.jsp.

persistingdata

Listato 23.11 File PersistingDataJSP.jsp.

:: Personal Web Page :: Benvenuto nella tua personal home page [utenti online: ]

Figura 23.7 data_JSP.war in esecuzione (avviare l’applicazione mediante ColorsJSP.html).

Il Listato 23.12 mostra la definizione della pagina JSP che si occuperà di ottenere i dati memorizzati e di utilizzarli per cambiare i colori dello sfondo e del testo della pagina, così come di visualizzare il numero di utenti connessi. Anche in questo caso abbiamo utilizzato una direttiva di pagina, una sezione per la dichiarazione delle variabili, dei metodi globalmente visibili e una sezione di scriptlet dove sfruttiamo l’oggetto implicito out per stampare il numero di utenti collegati. Inoltre, notiamo l’impiego delle espressioni tramite le quali impostiamo il valore per la proprietà background-color dell’attributo style dell’elemento body, ottenuto dalla valutazione della variabile colore_back, e il valore per la proprietà color dell’attributo dell’elemento h1, ottenuto dalla valutazione della variabile colore_front.

style

Listato 23.13 File DataManagementError.jsp. ... ...

NOTA Tutti i file JSP sin qui esaminati vanno inseriti nella cartella root del file data_JSP.war.

Il Listato 23.13 mostra la definizione della pagina JSP che sarà richiesta e utilizzata automaticamente dal container, se sarà sollevata un’eccezione durante l’esecuzione del codice scritto all’interno delle pagine PersistingDataJSP.jsp e

, che è stata indicata mediante l’impostazione dell’attributo errorPage

ManagingDataJSP.jsp

della direttiva page. Abbiamo definito una direttiva page che ha, tra gli altri, l’attributo isErrorPage con valore true a indicare che tale pagina deve essere considerata come una pagina per la gestione degli errori software. Successivamente abbiamo scritto un elemento HTML pre al cui interno vi è una sezione di tipo scriptlet che utilizza l’oggetto implicito exception (disponibile solo nelle pagine di tipo errore) che mostrerà all’utente lo stack trace dell’eccezione occorsa. DIETRO LE QUINTE DELLE PAGINE JSP Una pagina JSP, quando è richiesta per la prima volta da un client, viene tradotta dal container in un file .java contenente il codice di definizione di una classe servlet che viene poi compilata nell’equivalente file .class. Per quanto attiene ai file .java prodotti, nel server GlassFish si potranno

trovare

nel

percorso [dirserver]/glassfish/domains/[domain_name]/generated/jsp/[application]/org/apache/jsp. Per esempio, tornando alla nostra precedente applicazione, il file ManagingDataJSP.jsp è stato tradotto nel file ManagingDataJSP_jsp.java; possiamo vedere: che in esso vi è la definizione della relativa classe servlet; che le variabili e i metodi scritti nella sezione delle dichiarazioni sono diventati, di fatto, variabili e metodi d’istanza; che esiste il metodo _jspService che, invocato quando è richiesta la pagina JSP, rappresenta il “cuore” della servlet dove sono dichiarati e inizializzati gli oggetti impliciti e dove è posto il codice di output della risposta; che i marcatori delle espressioni sono stati tradotti in istruzioni Java che utilizzano il metodo print dell’oggetto out al quale è stato passato come argomento ciò che è stato scritto all’interno del marcatore medesimo.

Azioni standard Le azioni standard sono rappresentate da una serie di tag predefiniti, con il prefisso jsp, eseguiti quando avviene il processing di una richiesta HTTP ovvero quando una pagina JSP viene, per l’appunto, richiesta. L’ultima versione della specifica sulle JSP, la 2.3, definisce i seguenti elementi: per associare un oggetto JavaBean in un determinato scope e



renderlo disponibile per l’utilizzo; e per ottenere o impostare un valore di una proprietà di un bean; per includere risorse statiche o dinamiche nello stesso contesto della pagina corrente;

per inoltrare la richiesta corrente a un’altra risorsa nello stesso



contesto della pagina corrente; per generare codice HTML (elementi OBJECT o EMBED) che consente il download del software Java Plugin per la successiva esecuzione degli applet Java o del componente JavaBean specificato; per includere uno o più tag . È valido solo se posto all’interno del tag ; per fornire dei parametri sotto forma di coppie chiave/valore. È valido



solo se posto all’interno dei tag: , e ; per fornire al browser del contenuto alternativo da visualizzare se il



plug-in non può essere avviato. È valido solo se posto all’interno del tag ; per generare dinamicamente un elemento XML;



per definire un attributo come elemento nel corpo di un tag



piuttosto che come attributo del tag medesimo, oppure per specificare gli attributi di un elemento quando usato come figlio del tag ; per definire esplicitamente il body di un tag;



per racchiudere del testo da visualizzare letteralmente;



per modificare alcune proprietà dell’output di un documento JSP o



un file di tag; per invocare, in un file di tag, un attributo che è un frammento, mandandone il risultato in output; per invocare, in un file di tag, il body di un tag, mandandone il risultato in output. CHE COS’È UN JAVABEAN? Un JavaBean o bean è una classe Java scritta secondo le seguenti regole che la qualificano come tale: deve avere un costruttore pubblico senza argomenti; le variabili d’istanza dovrebbero essere private; deve contenere dei metodi di tipo getter e di tipo setter con i quali accedere alle variabili desiderate, scritti secondo una determinata convenzione; dovrebbe implementare l’interfaccia Serializable. Per quanto attiene alla denominazione dei metodi di accesso alle variabili, i metodi di scrittura devono iniziare con il prefisso set, mentre quelli di lettura con il prefisso get (o is se la proprietà è di tipo boolean) a cui segue il nome della proprietà dove, per convenzione, la lettera iniziale deve essere scritta in maiuscolo. È bene rimarcare che il nome della proprietà è desunto dai caratteri scritti dopo le parole set, get o is, dove il primo carattere è minuscolo, e non deve fare riferimento necessariamente a una variabile d’istanza, anche se generalmente si tende a far combaciare le due cose per una maggiore leggibilità del codice. Ciò significa, per esempio, che se abbiamo definito i metodi getName e getLastName, le proprietà relative saranno name e lastName.

Illustriamo ora un esempio di utilizzo delle azioni , e con le quali accederemo a una classe bean per leggere e scrivere le



sue proprietà. Listato 23.14 File PersonInfos.java. package com.pellegrinoprincipe; public class PersonInfos { // proprietà private String first_name; private String last_name; private int age; private String address; private String job; public PersonInfos() {} // metodi getter/setter public String getFirst_name() { return first_name; } public void setFirst_name(String first_name) { this.first_name = first_name; } public String getLast_name() { return last_name; } public void setLast_name(String last_name) { this.last_name = last_name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getJob(){ return job; } public void setJob(String job) { this.job = job; } }

Il Listato 23.14 definisce la classe PersonInfos, che rappresenta un bean in quanto sono state usate tutte le regole precedentemente esposte che la categorizzano come tale. Listato 23.15 File PersonForm.html. ... ... ... ...

Il Listato 23.15 definisce la pagina HTML che contiene il form e i relativi campi di testo per l’inserimento dei dati relativi a una persona. È importante notare l’impostazione dell’attributo action dell’elemento form che punta alla pagina , che si occuperà di gestire le informazioni inserite nei relativi campi

PersonSetInfos.jsp

di testo. Listato 23.16 File PersonSetInfos.jsp. ... Informazioni memorizzate: Nome: ... ...

Figura 23.8 use_bean.war in esecuzione (avviare l’applicazione mediante PersonForm.html). NOTA Prima di eseguire l’applicazione occorre ricordare di inserire il file PersonInfos.class nella cartella WEB-INF/classes/com/pellegrinoprincipe dell’archivio use_bean.war.

Il Listato 23.16 definisce una pagina JSP che mostrerà, formattate in una tabella, le informazioni di una persona i cui valori sono stati ottenuti dai parametri della richiesta e sono stati inseriti e poi estratti dal relativo oggetto bean, utilizzando solo dei tag e senza alcuna riga di codice scritta in Java. Vediamo in dettaglio le azioni utilizzate per compiere le citate operazioni. Con abbiamo impostato l’attributo id, che specifica un nome tramite il quale possiamo identificare il bean per il suo utilizzo successivo (in pratica, dietro le quinte è creata una variabile con quel nome che contiene il riferimento all’oggetto del tipo di bean creato, oppure ottenuto, dallo scope specificato, se già esistente); l’attributo class, che specifica il nome della classe dove è stato definito il bean unitamente al package di appartenenza; l’attributo scope, che specifica l’ambito di visibilità del bean: page (default), request, session o .

application

Con abbiamo impostato i valori di tutte le proprietà del bean tramite i valori ottenuti dai parametri della richiesta. In pratica, in tale tag si utilizzano l’attributo name, con il quale si indica il nome del bean da utilizzare e il cui valore deve essere uguale a quello dell’attributo id usato nel tag ; l’attributo property, con cui si specifica il nome della proprietà del bean da impostare; l’attributo value, con cui si indica il valore da assegnare a una proprietà; l’attributo param, con cui si specifica il nome di un parametro della richiesta da cui prelevare il valore da assegnare (chiaramente in alternativa all’attributo value). È importante tenere presente che, se il nome di un parametro della richiesta è uguale al nome di una proprietà del bean, l’attributo param può essere omesso in quanto viene utilizzato direttamente l’attributo property, dove è possibile anche scrivere il carattere asterisco (*), che consente di impostare tutte le proprietà del bean con un nome uguale a tutti i parametri della richiesta. Con abbiamo ottenuto i valori delle proprietà del bean precedentemente impostati. Anche in questo caso gli attributi utilizzati sono name e property.

Unified Expression Language Il linguaggio di espressione unificato o UEL (Unified Expression Language) consente, in modo compatto, semplice ed elegante, di compiere delle operazioni all’interno di una pagina web senza utilizzare codice Java. Possiamo: accedere alle proprietà di oggetti JavaBean: Snippet 23.1 Accesso alle proprietà di un bean. ${person.age} ${person["address"]} ${person[prop]}

Lo Snippet 23.1 permette di ottenere varie proprietà dal bean person. In particolare notiamo l’utilizzo del consueto operatore punto (.) per la proprietà age, dell’operatore di subscript [] sia per la proprietà address (indicata come letterale stringa) sia per la proprietà indicata dalla valutazione di prop che deve essere il nome di un attributo da cui ricavare il relativo valore. Utilizzare oggetti di tipo Map: Snippet 23.2 Accesso ai valori di una mappa.

${computer_hardware.printer} ${computer_hardware["monitor"]} ${computer_hardware[key]}

Lo Snippet 23.2 permette di ottenere dei valori dalla mappa computer_hardware. Anche in questo caso abbiamo usato gli stessi operatori già visti per il bean dello Snippet 23.1, ma questa volta la “discriminante” per la ricerca del valore è il nome della chiave della mappa piuttosto che della proprietà. Utilizzare degli array: Snippet 23.3 Accesso agli elementi di un array. ${books[0]} ${books["1"]}

Lo Snippet 23.3 consente di ottenere gli elementi dell’array books mediante l’utilizzo del comune operatore di subscript [] dove possiamo indicare il valore dell’elemento da estrarre sia come numero sia come stringa. Utilizzare degli oggetti di tipo List: Snippet 23.4 Accesso agli elementi di una lista. ${programming_languages[0]} ${programming_languages["1"]}

Lo Snippet 23.4 consente di ottenere gli elementi dalla lista programming_languages utilizzando la medesima sintassi vista per gli array. Ottenere i parametri di una richiesta: Snippet 23.5 Accesso ai parametri di una richiesta. ${param.a_name} ${paramValues.tel_numbers[0]}

Lo Snippet 23.5 permette di ottenere il valore del parametro a_name e i valori del parametro tel_numbers. A tal fine abbiamo utilizzato gli oggetti impliciti messi a disposizione da UEL quali param e paramValues. È importante rilevare che entrambi gli oggetti sono delle mappe e pertanto ogni parametro della richiesta diventa un suo elemento dove il nome della chiave da utilizzare per ottenere il relativo valore è uguale al nome del parametro stesso. Accedere agli header HTTP: Snippet 23.6 Accesso agli header HTTP.

${header.host} ${headerValues.accept[0]}

Lo Snippet 23.6 consente di ottenere il valore dell’header host e i valori dell’header mediante gli oggetti impliciti di tipo mappa header e headerValues;

accept

Ottenere il valore di un cookie: Snippet 23.7 Ottenimento di un valore di un cookie. ${cookie.ID_access.value}

Lo Snippet 23.7 consente di ottenere il valore del cookie ID_access utilizzando l’oggetto implicito di tipo mappa cookie, dove ogni chiave rappresenta il nome del cookie precedentemente impostato e dove il valore ritornato rappresenta un riferimento al relativo oggetto di tipo Cookie dal quale, mediante la proprietà value, si ottiene il valore. Ottenere i valori dei parametri di inizializzazione del contesto applicativo: Snippet 23.8 Ottenimento dei valori del contesto applicativo. ${initParam.admin_email}

Lo Snippet 23.8 consente di ottenere, attraverso l’utilizzo dell’oggetto implicito initParam di tipo mappa, il valore del parametro admin_email, dove la relativa coppia nome/valore è stata definita nel file web.xml mediante gli elementi e

Utilizzare informazioni di contesto della pagina JSP: Snippet 23.9 Utilizzo dell’oggetto pageContext. ${pageContext.request}

Lo Snippet 23.9 permette di ottenere il riferimento request di tipo ServletRequest tramite l’oggetto implicito di tipo bean pageContext. Tramite quest’oggetto, che in pratica fornisce informazioni di contesto della pagina JSP, possiamo utilizzare anche altri tipi di oggetti quali page, response, session e così via. Ottenere un attributo collegato a uno scope: Snippet 23.10 Ottenimento degli attributi collegati ai vari scope. ${pageScope.name} ${requestScope.age} ${sessionScope.min_value} ${applicationScope.person.address}

Lo Snippet 23.10 permette di ottenere il valore degli attributi name, age, min_value e (da cui si ottiene poi il valore della proprietà address) rispettivamente collegati

person

agli ambiti identificati dagli oggetti di tipo mappa pageScope, requestScope, sessionSope e . Generalmente, quando nell’espressione UEL si indica il nome di un

applicationScope

attributo, non è necessario specificare in quale scope esso è stato collegato, perché automaticamente l’engine del linguaggio lo ricerca partendo dal page scope fino all’application scope. L’indicazione di uno scope si rende tuttavia necessario se un attributo è stato specificato in più ambiti con lo stesso nome, per evitare un conflitto di nomi. compiere operazioni aritmetiche (operatori +, -, * e così via): Snippet 23.11 Esecuzione di un’operazione aritmetica. ${10 * 4 / 2}

Lo Snippet 23.11 ritorna il risultato della valutazione dell’operazione aritmetica indicata come espressione. Tra gli operatori è possibile utilizzare anche div per la divisione e mod per il modulo. Effettuare comparazioni (operatori ==, !=, e così via): Snippet 23.12 Esecuzione di una comparazione. ${10 != 4}

Lo Snippet 23.12 ritorna il risultato della valutazione della comparazione dell’espressione indicata. Tra gli operatori è possibile utilizzare anche i seguenti simboli: eq, uguale a; ne, non uguale a; lt, minore di; gt, maggiore di; le, minore o uguale a; ge, maggiore o uguale a. Effettuare operazioni logiche (operatori &&, || e !): Snippet 23.13 Esecuzione di un’operazione logica. ${10 != 4 && data > num}

Lo Snippet 23.13 ritorna il risultato della valutazione dell’operazione logica indicata. Tra gli operatori è possibile utilizzare anche i simboli and, or e not. NOTA Oltre agli operatori citati è possibile utilizzare anche empty, che valuta se il valore del suo operando è nullo o vuoto, e l’operatore ternario A ? B : C.

Al di là della quantità di operazioni effettuabili, possiamo dire che un’espressione UEL, per quanto riguarda l’accesso ai dati, è costruita sempre allo stesso modo, ovvero con l’indicazione di un oggetto implicito o del nome di un attributo (presente in uno dei scope validi) eventualmente seguito dall’operatore punto (valido solo per le mappe e per i bean) o di subscript (valido per le mappe, i bean, gli array e le liste) e, infine, dall’indicazione del nome di una proprietà (per un bean) o del nome di una chiave (per una mappa) o della posizione di un elemento di un array o di una lista specificata come valore numerico o come letterale stringa. Inoltre, se l’espressione è utilizzata per valorizzare un attributo di un tag, il valore ritornato a seguito della sua valutazione è, in caso di necessità, convertito nel tipo di dato che l’attributo stesso si aspetta di ricevere. NOTA Il linguaggio UEL è abilitato per default, ma se si vuole disabilitarlo per la pagina JSP corrente, si può utilizzare l’attributo isELIgnored con valore true nella relativa direttiva di pagina, mentre per disabilitarlo per tutte le pagine JSP della nostra web application possiamo usare l’elemento definito nel file web.xml (Snippet 23.14). Allo stesso modo possiamo disabilitare l’utilizzo

di tutti gli elementi di scripting (scriptlet, dichiarazioni ed espressioni Java), per forzare gli sviluppatori a non usare codice Java all’interno delle pagine JSP, mediante l’elemento definito sempre nel file web.xml (Snippet 23.15). Snippet 23.14 Disabilitazione di UEL nel file web.xml. *.jsp true

Snippet 23.15 Disabilitazione degli elementi di scripting nel file web.xml. *.jsp true

Grazie alle conoscenze acquisite sul linguaggio UEL rielaboriamo, a questo punto, il file PersonSetInfos.jsp del Listato 23.16 modificando il codice di accesso alle proprietà del bean person, effettuato mediante l’azione standard , con le equivalenti espressioni UEL che oggi sono considerate il mezzo più indicato per leggere le proprietà di un bean. Listato 23.17 File PersonSetInfosUEL.jsp. ... ... ...

${person.first_name} ... ${person.last_name} ... ...

Il Listato 23.17 mostra come l’utilizzo dello UEL renda la scrittura del codice per l’accesso alle proprietà di un bean molto più compatta ed elegante rispetto all’utilizzo delle equivalenti azioni standard. Listato 23.18 File PersonFormUEL.html. ... ...

Il Listato 23.18 definisce il file PersonFormUEL.html che sarà l’entry point della web application use_bean_UEL.war, dove l’attributo action dell’elemento form punterà al file per la gestione dei dati di una persona.

PersonSetInfosUEL.jsp

JSP Standard Tag Library (JSTL) La tecnologia JSTL è formata da un insieme di librerie di tag standardizzati che consentono di svolgere sia operazioni comuni come impostare il valore di una variabile, compiere iterazioni, selezioni e così via, sia operazioni complesse come manipolare file XML, accedere a un database e così via. Queste librerie rappresentano, di fatto, un ulteriore passo avanti nel consentire la costruzione di applicazioni web dove le pagine del presentation layer siano realizzate senza scrivere codice Java frammisto a codice HTML. In effetti, grazie alla JSTL e allo UEL è realmente possibile demandare la scrittura delle pagine web solo ai web developer o ai web designer, il cui unico “sacrificio” sarà quello di apprendere i tag relativi alle azioni che dovranno usare, e ciò senza dover conoscere il linguaggio Java. Ovviamente, oltre al citato beneficio, vi sarebbe anche un guadagno in termini di leggibilità, scalabilità e manutenibilità dell’applicazione web perché, ripetiamo, la pagina JSP non sarebbe più infarcita di scriptlet, dichiarazioni e quant’altro, e perché avremmo anche una netta separazione dei ruoli del team di sviluppo: finalmente il programmatore Java potrà dedicarsi alla codifica della business logic o della parte “dietro le quinte”, mentre il web designer o il web developer potrà concentrarsi solo sull’aspetto del Client Tier utilizzando sia i comuni tag HTML sia i tag della standard library di JSTL, oltre chiaramente a CSS, JavaScript e così via. NOTA STORICA La prima specifica della JSTL, versione 1.0, è stata resa disponibile nel giugno del 2002. Sono seguite le maintenance release versione 1.1 del novembre del 2003 e 1.2 del maggio del 2006. Allo stato attuale esiste una maintenance draft versione 1.3 dell’ottobre del 2006.

La Tabella 23.1 mostra le librerie di tag utilizzabili, ricordando che per importarle in una pagina JSP bisogna utilizzare una direttiva taglib con gli attributi URI e prefix opportunamente valorizzati. Area funzionale

Tabella 23.1 Librerie di tag JSTL. URI

Prefisso

Core

http://java.sun.com/jsp/jstl/core

c

Formatting e i18n

http://java.sun.com/jsp/jstl/fmt

fmt

Processing dell’XML

http://java.sun.com/jsp/jstl/xml

x

SQL

http://java.sun.com/jsp/jstl/sql

sql

Funzioni

http://java.sun.com/jsp/jstl/functions

fn

Libreria dei tag core La libreria denominata core contiene i seguenti tag che consentono di compiere azioni comuni o general purpose. : permette di inviare in output il risultato della valutazione di



un’espressione. Tra gli attributi abbiamo: value, per indicare l’espressione da valutare; default, per indicare un valore da visualizzare se la valutazione dell’espressione ha dato come risultato il valore null. : permette di definire una variabile collegata a un determinato scope oppure di impostare una proprietà di un oggetto. Tra gli attributi abbiamo: value, per indicare l’espressione da valutare; var, per indicare il nome della variabile da esportare; scope, per indicare lo scope di collegamento di var; target, per indicare un oggetto JavaBean oppure un oggetto di tipo mappa di cui impostare la proprietà; property, per indicare il nome della proprietà da impostare nel target. : permette di rimuovere una variabile collegata a uno scope. Tra gli



attributi abbiamo: var, per indicare il nome della variabile da rimuovere; scope, per indicare lo scope della variabile. : permette di catturare un’eccezione di tipo java.lang.Throwable eventualmente causata da azioni annidate nel corpo di tale tag. Tra gli attributi abbiamo: var, per indicare il nome della variabile dove sarà posto il riferimento all’oggetto eccezione lanciato. : permette di eseguire delle istruzioni poste nel suo body solo se la valutazione di un’espressione è vera. Tra gli attributi abbiamo: test, per indicare l’espressione oggetto del test; var, per indicare il nome di una variabile dove sarà posto il risultato del test dell’espressione; scope, per indicare lo scope dove collegare var. : permette di fornire un contesto dove costruire blocchi condizionali di



tipo if/else. Questo tag non espone attributi. : permette di costruire una clausola condizionale. Tale tag va posto



all’interno del tag e ha l’attributo test con il quale specificare la condizione del test. : permette di costruire una clausola condizionale rappresentante una sorta di else finale. In pratica le istruzioni poste all’interno del suo body sono

eseguite solo se tutte le condizioni delle relative clausole sono risultate .

false

: permette di costruire un ciclo iterativo che processa il contenuto del



suo body rispetto a una collezione oppure rispetto a un numero di iterazioni stabilite. Tra gli attributi abbiamo: var, per indicare il nome di una variabile contenente il corrente item oppure l’indice numerico attuale dell’iterazione; items, per indicare una collezione di item su cui iterare (array, implementazioni di Collection, Iterator, Enumeration, Map, una stringa con dei valori separati dal carattere virgola); varStatus, per indicare il nome di una variabile che contiene lo status dell’iterazione (è di tipo LoopTagStatus e ha varie proprietà quali count, ,

,

e così via); begin, per indicare un indice numerico di partenza;

current first last

, per indicare un indice numerico di terminazione; step; per indicare un indice

end

numerico dei passi dell’iterazione. : permette di costruire un ciclo iterativo che processa il contenuto del suo body rispetto a una stringa di caratteri separati da un determinato delimitatore. Ha gli stessi attributi del tag dove, in particolare, nell’attributo items si deve indicare la stringa di token oggetto dell’iterazione; ha in più l’attributo delims con cui indicare i caratteri che separano i token nella stringa. : permette di importare il contenuto di una risorsa sia che essa si trovi



nell’ambito della nostra web application sia che essa sia ospitata in altri contesti esecutivi. Tra gli attributi abbiamo: url, per indicare l’URL della risorsa da importare; var, per indicare il nome di una variabile (che sarà di tipo String) dove inserire il contenuto importato piuttosto che inviarlo in output con un oggetto JspWriter; varReader, per indicare il nome di una variabile (che sarà di tipo Reader) dove inserire il contenuto importato; scope, per indicare lo scope di collegamento di var. : permette di costruire un URL. Tra gli attributi abbiamo: value, per



indicare l’URL; var, per indicare il nome di una variabile che conterrà l’URL; , per indicare lo scope di var. È importate evidenziare che, se un client ha i

scope

cookie disabilitati, il container effettuerà in automatico l’URL rewriting, ovvero aggiungerà alla fine dell’URL uno speciale parametro, denominato jsessionid, che consentirà di gestire correttamente il session tracking. : permette di ridirigere un utente a un altro URL. Ha l’attributo url per indicare l’URL della risorsa verso cui attuare il reindirizzamento.

: permette di aggiungere un request parameter a un URL. Va annidato



nei tag: , e e ha gli attributi name per indicare il nome di un parametro e value per indicarne il valore. Se necessario, il nome e il valore di un parametro vengono automaticamente codificati ovvero, in accordo alle regole definite nel documento RFC 2396, alcuni caratteri vengono “trasformati” in altri equivalenti che si possono utilizzare senza problemi in una query string. Per esempio, se il valore di un parametro contiene il carattere di spazio, questo viene codificato con il simbolo + oppure %20. Vediamo ora un semplice esempio che mostra come utilizzare alcuni dei tag precedentemente indicati, esaminando il file JSTL_core.jsp (Listato 23.19). Listato 23.19 File JSTL_core.jsp. ... ...

Figura 23.9 JSTL.war in esecuzione (avviare l’applicazione mediante JSTL_core.jsp).

Il Listato 23.19 definisce una pagina JSP che mostra una tabella dove le righe pari e le righe dispari avranno due colori differenti. Per raggiungere tale obiettivo abbiamo utilizzato un ciclo , con gli attributi begin ed end che contengono rispettivamente i valori 1 e 10 ottenuti mediante la valutazione delle variabili start ed inizializzate con il tag , il cui corpo contiene un test condizionale (,

end

e ) per verificare se lo step iterativo corrente contiene un numero



pari o dispari. Se il numero è pari allora scriviamo una riga e una colonna (tag e ) impostando per quest’ultima un colore di sfondo rosso e un testo appropriato di



output (). Se, viceversa, il numero è dispari scriviamo un’altra riga e un’altra colonna impostando per quest’ultima un colore di sfondo giallo e un testo di output indicativo.

Libreria dei tag formatting e i18n La libreria denominata formatting e i18n contiene i seguenti tag che consentono di internazionalizzare e di localizzare le applicazioni web. : permette di impostare il luogo geografico, politico e culturale



dove girerà l’applicazione. Tra gli attributi abbiamo value, per indicare il codice della lingua (ISO-639), seguito eventualmente dal codice del paese (ISO-3166). Per esempio, l’indicazione della lingua italiana e del paese Italia si può scrivere come it-IT o it_IT. : permette di creare un contesto di localizzazione dove verranno



caricati e utilizzati dei file (resource bundle) che conterranno, sotto forma di

coppie chiave/valore, il testo da visualizzare, ciascuno tradotto in una determinata lingua. Tra gli attributi abbiamo basename per indicare il nome del file di risorsa. : permette di creare un contesto di localizzazione che viene



immagazzinato nella variabile indicata dall’attributo var con lo scope indicato dall’attributo scope. Se la variabile var non è specificata, tale contesto viene memorizzato nella variabile di configurazione javax.servlet.jsp.jstl.fmt.localizationContext. Anche in questo caso dobbiamo indicare l’attributo basename con il nome del file della risorsa. : permette di visualizzare il messaggio localizzato. Tra gli attributi



abbiamo: key, per indicare il nome della chiave da ricercare, il cui valore rappresenta il messaggio localizzato; var, per indicare il nome della variabile dove memorizzare il messaggio; scope, per indicare lo scope di var; bundle, per indicare il contesto di localizzazione, eventualmente impostato tramite , da utilizzare per la ricerca delle chiavi. : permette di fornire un valore, tramite l’attributo value, che si sostituirà



a un parametro indicato nel file di risorsa. Tale tag può essere utilizzato solo se annidato all’interno del tag . : permette di impostare, attraverso l’attributo value, l’encoding



dei caratteri di una richiesta, al fine di poter decodificare correttamente i valori dei parametri della stessa se la codifica è differente dall’ISO-8859-1. : permette di impostare una regione con lo specifico orario locale (fuso orario) con il quale saranno formattate o sottoposte a parsing le date e gli orari. Ha l’attributo value per indicare l’ID della regione da utilizzare come time zone (è possibile conoscere tutti gli ID utilizzabili invocando il metodo statico getAvailableIDs della classe java.util.TimeZone). : permette di memorizzare nella variabile specificata



dall’attributo var, collegata allo scope indicato dall’attributo scope, il fuso orario dell’attributo value. : permette di formattare una data e/o un orario secondo la corrente



localizzazione. Tra gli attributi abbiamo: value, per indicare la data e/o l’orario da formattare (deve essere di tipo java.util.Date); type, per indicare se la data (date), l’orario (time) o entrambi (both) devono essere formattati; timeZone, per indicare il fuso orario, eventualmente impostato da , da utilizzare per la formattazione della data e/o dell’orario.

: permette di eseguire il parsing di una stringa contenente una data



e/o un orario e convertirla in un oggetto di tipo java.util.Date. Tra gli attributi abbiamo: value, per indicare la stringa da sottoporre a parsing; type, per indicare se la stringa dell’attributo value contiene una data, un orario o entrambi; var, per indicare il nome della variabile dove memorizzare il risultato della conversione; timeZone, per indicare il fuso orario, eventualmente impostato da , da utilizzare per la formattazione della data e/o dell’orario. : permette di formattare un valore numerico secondo la localizzazione corrente. Tra gli attributi abbiamo: value, per indicare il valore numerico da formattare; type, per indicare se il valore deve essere formattato come numero (number), valuta (currency) o percentuale (percent). : permette di eseguire il parsing di una stringa contenente un



numero e convertirlo in un oggetto di tipo Number. Tra gli attributi abbiamo: value, per indicare la stringa da sottoporre a parsing; type, per indicare se la stringa dell’attributo value è un numero, valuta o percentuale; var, per indicare il nome della variabile dove memorizzare il risultato della conversione (sarà di tipo Number). La pagina JSP del Listato 23.20 permette di mostrare il form dei dati di una persona dove le etichette delle relative caselle di testo e del pulsante di submit sono localizzate a seconda che l’utente sia italiano oppure americano. La stessa discriminante avviene per il tag legend che mostra la scritta Info --> seguita dalla formattazione della data e dell’orario corrente. Listato 23.20 File JSTL_PersonFormUEL.jsp. ...
View more...

Comments

Copyright ©2017 KUPDF Inc.
SUPPORT KUPDF