|
Java: Arte e tecnica
Sintassi e struttura del linguaggio
Dario de Judicibus
Descritta la grammatica
base di un linguaggio, iniziamo ad approfondire l’analisi di Java con
l’obiettivo di descriverne le caratteristiche, sia al programmatore esperto sia a
quanti si avvicinano per la prima volta ad un linguaggio di programmazione.
Un’attenzione particolare sarα rivolta a quei lettori che giα conoscono il C++ e
che desiderano passare a Java.
Dopo i due servizi di premessa pubblicati a ottobre e novembre, iniziamo a fare la
conoscenza di Java con una serie di articoli strutturati in modo tale che ciascuno sia
funzionale innanzitutto a quanti non sanno programmare. All’interno saranno quindi
presenti informazioni, tecniche, spunti e trucchi per i pi∙ esperti ed una serie di
riferimenti comparativi per i programmatori C++. Nella prima parte di articoli ci
limiteremo a descrivere le basi del linguaggio e delle classi. Nella seconda svilupperemo
insieme una vera e propria applicazione in Java. Inoltre, sarα dato spazio a classi,
tecniche ed altre informazioni di particolare interesse sviluppate dai lettori. A quanti
non hanno letto i due servizi d’apertura, ricordiamo che essi sono stati dedicati
alla sintassi del linguaggio, cioΦ a quelle istruzioni che costituiscono la struttura
portante del programma. L’acquisizione della grammatica base di un linguaggio non Φ
tuttavia sufficiente a sviluppare applicazioni complesse. Per sfruttare a pieno le
potenzialitα di un linguaggio Φ necessario utilizzare quelle classi e quei metodi che
permettono di accedere alle risorse fisiche del sistema, alle funzioni del sistema
operativo ed ai protocolli di rete. Questo sarα il tema delle puntate successive. Tenete
inoltre presente che Java Φ un linguaggio in continua evoluzione, per cui Φ importante
fare sempre riferimento ai vari aggiornamenti allo standard reperibili dal sito della Sun.
Innanzitutto cos’∞ Java. ╔ un linguaggio fortemente orientato agli oggetti. Questo
vuol dire che il modo di programmare in Java Φ quello di definire classi di oggetti che
interagiscono fra di loro. Potete immaginare un programma Java come una scatola in cui
galleggiano a gravitα zero biglie di tutte le forme e dimensioni. Se immettete
dall’esterno una nuova biglia o comunque urtate una di quelle giα esistenti, darete
origine a tutta una serie di urti e quindi di eventi il cui risultato potrebbe essere ad
esempio rappresentato dall’uscita di un’altra biglia dalla scatola stessa. Un
linguaggio orientato agli oggetti opera proprio cos∞. Avete un certo numero di oggetti
che possono interagire fra loro. La creazione di un nuovo oggetto od un evento che arriva
dall’esterno del programma scatena una serie di reazioni che producono altri oggetti
ed altri eventi. Un evento pu≥ essere, per esempio, la pressione di un pulsante su
un’interfaccia grafica o l’arrivo di un segnale dalla rete. Nei linguaggi
procedurali si procedeva, invece, secondo una logica ad azioni sequenziali o parallele.
Prima faccio questo, poi quello, quindi attivo altre due sequenze che in parallelo
compiono altre azioni, mi metto in attesa finchΘ non hanno finito ed infine attivo una
sequenza conclusiva di azioni. Nel C++, che non Φ un linguaggio orientato agli oggetti
puro, ma una sorta di potente ibrido fra un macro assemblatore ed un linguaggio ad
oggetti, un’applicazione ha comunque una funzione iniziale chiamata main. In Java non
esistono funzioni, ma solo metodi appartenenti ad una ben specifica classe. Il punto
d’ingresso di un’applicazione Φ quindi un metodo, per coerenza ancora chiamato main,
che viene eseguito automaticamente quando si passa all’interprete Java il nome di una
classe in cui esso sia stato definito.
Classe, metodi, oggetti
Veniamo ora alla terminologia usata. Abbiamo parlato di classi, metodi ed oggetti.
Vediamo rapidamente cosa sono. Una classe Φ una definizione di dati e di funzioni che
operano sugli stessi. Ad esempio, una classe Stringa pu≥ essere definita
come un vettore di N caratteri qualunque, un intero che ne indica la lunghezza, ed una
serie di funzioni che operano su questi dati: un metodo per invertire la stringa, uno che
ne ritorna la lunghezza, uno che cerca all’interno della stringa un’altra
stringa data o che conta il numero di volte che essa si presenta, e via dicendo. Da notare
che in genere Φ buona pratica nascondere i dati dell’oggetto permettendone
l’accesso solo attraverso un metodo. Per esempio, se lunghezza Φ
l’intero che contiene la lunghezza della stringa, tale informazione sarα disponibile
utilizzando un metodo lunghezza () che ritorna quel valore, piuttosto che accedere
direttamente al valore stesso. Il motivo Φ che altrimenti si potrebbe correre il rischio
che un programma modifichi alcuni dati dell’oggetto facendone perdere la coerenza.
Per esempio, potrei impostare la lunghezza di "Pippo" a sette, invece che
a cinque, lasciando contemporaneamente inalterata la vera lunghezza della stringa. Un
metodo Φ quindi una funzione interna alla classe che opera sui dati della stessa. Un
oggetto Φ l’istanza di una classe, cioΦ la sua materializzazione in termini di
codice. Se uomo Φ una classe, Carlo ed Andrea sono oggetti. Essi,
infatti, sono entrambi uomini e condividono la stessa struttura dei dati; hanno, per
esempio, entrambi un’etα, ma il valore dei dati pu≥ essere differente. Andrea
ha 15 anni, Carlo 76. Parimenti sia mioNome sia mioCognome possono
essere oggetti della classe Stringa, ma il loro contenuto Φ ovviamente differente. Detto
questo passiamo al linguaggio. Anche se questa parte della trattazione Φ prevalentemente
dedicata ai principianti, le tabelle proposte sono strutturate come un’utile guida di
riferimento rapida anche per gli esperti. Potete fotocopiarle e tenerle accanto al
computer mentre programmate.
I programmi possono essere scritti sia in Unicode sia in un qualunque insieme di
caratteri di tipo ASCII esteso che possa essere convertito in Unicode. Questo sistema Φ
uno schema di codifica dei caratteri basato su due byte che permette di rappresentare fino
a 34.168 caratteri differenti, tra cui quelli appartenenti all’alfabeto greco, russo,
giapponese, ebraico, arabo e via dicendo. Questo, se da un lato permette di scrivere
codice nella propria lingua nativa, dall’altro pu≥ tuttavia creare problemi qualora
si volesse distribuire il sorgente ad altri sviluppatori di nazioni diverse o che lavorano
su differenti piattaforme. Infatti, sebbene qualunque implementazione di Java accetti il
sorgente in Unicode, la maggior parte degli editori disponibili nei vari sistemi operativi
continua a supportare solo un limitato insieme di caratteri generalmente basato sul
vecchio sistema ASCII.
Programmazione e parole chiave
Un linguaggio di programmazione Φ formato da una serie di simboli, identificatori e
parole chiave che vanno assemblati secondo certe regole, che formano la sintassi del
linguaggio. Le parole chiave sono definite dal linguaggio, hanno un
significato ben preciso e non possono essere modificate. In Tabella 1 Φ riportata la
lista di tutte le parole chiave di Java. Non Φ permesso usarle come nomi di variabili od
altri elementi del programma quali metodi, costanti e cos∞ via. Le parole chiave
riportate in corsivo in tabella sono riservate, ma non pi∙ di uso corrente. Come abbiamo
giα accennato, infatti, il linguaggio Java Φ in continua evoluzione, anche se Sun, caso
unico nella storia, ha proposto la candidatura di Java quale standard internazionale. Θ
la prima volta che uno standard Φ stato sottomesso all’ISO da un’azienda
privata piuttosto che da un consorzio o da un’organizzazione internazionale. Gli identificatori,
viceversa, sono definiti dal programmatore e seguono una ben precisa nomenclatura che
dipende solitamente dall’elemento identificato. Un identificatore deve sempre
iniziare con una lettera oppure con i simboli del dollaro o del trattino di
sottolineatura. Il resto del nome pu≥ contenere sia tali caratteri sia numeri. Per
lettera, tuttavia, oltre ai classici caratteri dell’alfabeto anglosassone maiuscoli e
minuscoli, valgono tutti i caratteri Unicode al di sopra del valore 0x00C0. A
parte questo, l’unico altro obbligo nel definire un identificatore Φ quello di non
utilizzare una parola riservata al linguaggio, e cioΦ le parole chiave giα viste pi∙ le
due stringhe booleane true e false. Per convenzione, anche se non Φ obbligatorio, i nomi
delle variabili iniziano con una lettera minuscola, quelli delle classi con una lettera
maiuscola. Java rispetta la cassa dei caratteri come il C ed il C++. Il nome pippo
Φ quindi differente da Pippo. Esistono poi cinque differenti tipi di costanti
esplicite, o literal: gli interi in base 8, 10 o 16, come in C (8, 16, 32 e 64
bit), i valori reali in formato IEE 754 a singola e doppia precisione, i booleani true e false, i
caratteri e le stringhe. Da notare che in Java i booleani non sono di tipo
numerico, per cui non esiste la conversione implicita fra interi e booleani come in C e
C++. I simboli sono di due tipi: operatori e separatori. Ci sono quattro gruppi di
operatori, riportati nelle tabelle dalla 2 alla 9. Gli operatori possono essere unari o
binari. Quelli unari a loro volta possono essere del tipo suffisso, cioΦ posizionati dopo
l’operando, o del tipo prefisso, cioΦ posizionati davanti all’operando. Lo
stesso simbolo pu≥ tuttavia rappresentare operatori differenti. Per esempio, il segno
meno, utilizzato come operatore unario, pu≥ essere posto solo davanti al numero,
utilizzato come operatore binario, va posto tra i due operandi. Analogamente,
l’operatore ++ pu≥ essere posto sia davanti sia dopo l’operando, ma il
significato Φ decisamente differente. Rispetto al C++, esiste un ulteriore operatore di
scorrimento identificato dal simbolo >>> che riempie con zeri a sinistra
il valore man mano che scorre a destra. Esso Φ necessario in quanto tutti gli interi in
Java sono valutati con segno, non esistono, cioΦ, gli interi unsigned come in C e C++. I
separatori sono quei simboli, quali le parentesi, che rappresentano la punteggiatura del
linguaggio. Essi sono riportati nella Tabella 10. Esistono tre tipi di commenti. Quelli
classici, analoghi a quelli usati in C e racchiusi fra /* e */, quelli di
linea, tipici del C++, e che iniziano sempre con // e terminano alla fine della
linea, e quelli per la generazione automatica della documentazione, racchiusi fra il
simbolo /** ed il simbolo */. Un programma Java Φ formato da una o pi∙
definizioni di classi. Ogni classe contiene un certo numero di dati e di metodi. Ogni
metodo Φ una funzione che opera sui dati interni della classe, e quelli passati al metodo
come parametri. L’insieme dei valori che in un certo momento assumono tutti i dati di
un oggetto di una certa classe, sono detti stato dell’oggetto. Al contrario
del C++, dove la dichiarazione di una classe, fatta generalmente in un file con estensione
h, hxx o hpp, era qualcosa di ben distinto dalla definizione della stessa, riportata in un
file di tipo c, cxx o cpp, in Java le dichiarazioni vengono fatte contestualmente
all’implementazione e sono entrambe riportate nello stesso file, la cui estensione Φ
sempre Java. Esso Φ, infatti, un interprete: le istruzioni, benchΘ trascritte in un
formato intermedio, vengono lette ed eseguite una per una cos∞ come sono state scritte,
per cui non possono essere accettate strutture che non possano essere risolte al momento
dell’esecuzione (run-time). Un linguaggio compilato, invece, tramite i passi
di compilazione e risoluzione dei collegamenti (compile e link), pu≥
accettare una sintassi pi∙ complessa che prevede, per esempio, le dichiarazioni
anticipate o collegamenti non risolti fin dall’inizio. Stiamo forse per≥ entrando
troppo in dettagli che non sono essenziali a sviluppare un programma in Java. Ritorneremo
eventualmente in seguito sull’argomento.
Le istruzioni
Le istruzioni in Java sono di tre tipi: dichiarazioni, espressioni e istruzioni
di controllo del flusso. Le dichiarazioni sono utilizzate per definire le proprietα
di una classe, di un metodo, di una variabile, di una costante e cos∞ via. La loro
sintassi dipende da cosa stiamo dichiarando e la vedremo volta per volta quando andremo ad
analizzare in dettaglio i vari elementi del linguaggio. Le espressioni sono una
combinazioni di variabili, costanti esplicite e non, operatori e chiamate a metodi il cui
risultato Φ un valore ben definito. Espressioni sono, per esempio, la chiamata ad un
metodo, come System.in.read(), operazioni su variabili, come index++ oppure operazioni aritmetiche come a*(b-c). Il valore
dell’espressione pu˜ essere assegnato ad una variabile utilizzando uno degli
operatori di assegnazione (Tabella 9). Nel prossimo numero, descriveremo le istruzioni di
controllo del flusso ed inizieremo a vedere i vari tipi di dichiarazioni e la differenza
fra tipi e classi. Per il momento╔auguri.
(ddejudicibus@tecnet.it)
Tabella 1. Parole chiave
Abstract |
Double |
int |
static |
Boolean |
Else |
interface |
super |
Break |
Extends |
long |
switch |
Byte |
Final |
native |
synchronized |
Case |
Finally |
new |
this |
Catch |
Float |
null |
throw |
Char |
For |
package |
throws |
Class |
Goto |
private |
transient |
Const |
If |
protected |
try |
Continue |
Implements |
public |
void |
Default |
Import |
return |
volatile |
Do |
Instanceof |
short |
while |
Tabella 2. Operatori aritmetici unari
Operatore |
Utilizzo |
Descrizione |
+ |
+ n |
Valore
positivo |
- |
- n |
Nega il
valore di n |
++ |
n ++ |
Incrementa
n di 1, ritorna il valore originale |
++ |
++ n |
Incrementa
n di 1, ritorna il valore modificato |
-- |
n -- |
Riduce
n di 1, ritorna il valore originale |
-- |
-- n |
Riduce
n di 1, ritorna il valore modificato |
Tabella 3. Operatori aritmetici binari
Operatore |
Utilizzo |
Descrizione |
+ |
n + m |
Somma n
ed m |
- |
n - m |
Sottrae
m da n |
* |
n * m |
Moltiplica
n per m |
/ |
n / m |
Divide
n per m |
% |
n % m |
Ritorna
il resto di n diviso per m |
Tabella 4. Operatori relazionali
Operatore |
Utilizzo |
La condizione Φ vera se... |
> |
n > m |
N Φ
maggiore di m |
>= |
n >= m |
N Φ
maggiore od uguale ad m |
< |
n < m |
N Φ
minore di m |
<= |
n <= m |
N Φ
minore od uguale ad m |
== |
n == m |
N Φ
uguale ad m |
!= |
n != m |
N Φ
diverso da m |
Tabella 5. Operatori condizionali
Operatore |
Utilizzo |
La condizione Φ vera se... |
&& |
n && m |
Sia n
che m sono veri |
|| |
n || m |
Uno
qualunque dei due operandi Φ vero |
! |
! n |
N Φ
falso |
Tabella 6. Operatori condizionali ternari
Operatore |
Utilizzo |
Significato |
? : |
n ? a : b |
Se n Φ
vero ritorna a, altrimenti ritorna b |
Tabella 7. Operatori di scorrimento
Operatore |
Utilizzo |
Sposta i bit di n... |
>> |
n >> m |
A
destra di m posizioni |
<< |
n << m |
A
sinistra di m posizioni |
>>> |
n >>> m |
A
destra di m posizioni (unsigned) |
Tabella 8. Operatori aritmetici sui Bit
Operatore |
Utilizzo |
Descrizione |
& |
n && m |
N AND m
|
| |
n || m |
N OR m |
^ |
! n |
N XOR m |
~ |
~ n |
NOT n |
Tabella 9. Operatori di asseganzioni
Operatore |
Utilizzo |
Operazione equivalente |
= |
n = m |
Assegna
il valore di m ad n |
+= |
n += m |
N = n +
m |
-= |
n -= m |
N = n
– m |
*= |
n *= m |
N = n *
m |
/= |
n /= m |
N = n /
m |
%= |
n %= m |
N = n %
m |
&= |
n &= m |
N = n
& m |
|= |
n |= m |
N = n |
m |
^= |
n ^= m |
N = n ^
m |
>>= |
n >>= m |
N = n
>> m |
<<= |
n <<= m |
N = n
<< m |
>>>= |
n >>>= m |
N = n
>>> m |
Tabella 10. Separatoroi
Separatore |
Utilizzo |
{ } |
Blocco
di istruzioni |
[ ] |
Vettori
e matrici |
( ) |
Operazioni
aritmetiche, parametri, condizioni |
. |
Composizione
degli identificatori |
; |
Fine
istruzione |
" " |
Stringa
letterale |
, |
Separatore
nelle liste |
|