2. Nozioni di base sul linguaggio Java
2.1. Introduzione
Tratteremo Java innanzitutto come un linguaggio di programmazione classico. Affronteremo il tema degli oggetti in un secondo momento.
In un programma si trovano due elementi
- i dati
- le istruzioni che li manipolano
In genere si cerca di separare i dati dalle istruzioni:

2.2. I dati in Java
Java utilizza i seguenti tipi di dati:
- i numeri interi
- i numeri reali
- i caratteri e le stringhe
- i valori booleani
- gli oggetti
2.2.1. I tipi di dati predefiniti
Tipo | Codifica | Dominio |
char | 2 byte | carattere Unicode |
int | 4 byte | [-231, 231-1] |
long | 8 byte | [-263, 263 -1] |
byte | 1 byte | [-27 , 27 -1] |
short | 2 byte | [-215, 215-1] |
float | 4 byte | [3.4 10-38, 3.4 10+38] in valore assoluto |
double | 8 byte | [1.7 10-308 , 1.7 10+308] in valore assoluto |
boolean | 1 bit | vero, falso |
String | riferimento all'oggetto | stringa di caratteri |
Date | riferimento all'oggetto | data |
Character | riferimento oggetto | carattere |
Integer | riferimento oggetto | int |
Long | riferimento all'oggetto | long |
Byte | riferimento oggetto | byte |
Float | riferimento all'oggetto | float |
Double | riferimento all'oggetto | double |
Boolean | riferimento all'oggetto | booleano |
2.2.2. Notazione dei dati letterali
entier | 145, -7, 0xFF (esadecimale) |
reale doppio | 134,789, -45E-18 (-45 × 10⁻¹⁸) |
reale float | 134.789F, -45E-18F (-45 × 10⁻¹⁸) |
caractère | 'A', 'b' |
stringa di caratteri | "oggi" |
booléen | vero, falso |
date | new Date(13,10,1954) (giorno, mese, anno) |
2.2.3. Dichiarazione dei dati
2.2.3.1. Ruolo delle dichiarazioni
Un programma gestisce dati caratterizzati da un nome e da un tipo. Questi dati vengono memorizzati in memoria. Al momento della compilazione del programma, il compilatore assegna a ciascun dato una posizione in memoria caratterizzata da un indirizzo e da una dimensione. Lo fa avvalendosi delle dichiarazioni effettuate dal programmatore.
Inoltre, queste consentono al compilatore di individuare eventuali errori di programmazione. Ad esempio, l’operazione
x=x*2;
sarà segnalata come errata se, ad esempio, x è una stringa di caratteri.
2.2.3.2. Dichiarazione delle costanti
La sintassi per dichiarare una costante è la seguente:
final tipo nome=valore; //definisce la costante nome=valore
es.: final float PI=3.141592F;
Nota
Perché dichiarare le costanti?
- La lettura del programma risulterà più agevole se alla costante viene assegnato un nome significativo:
es.: final float taux_tva = 0.186F;
- La modifica del programma risulterà più semplice qualora la «costante» dovesse cambiare. Pertanto, nel caso precedente, se l’aliquota IVA dovesse passare al 33%, l’unica modifica da apportare sarà quella di modificare l’istruzione che ne definisce il valore:
final float taux_tva=0.33F;
Se nel programma fosse stato utilizzato esplicitamente il valore 0,186, sarebbe stato necessario modificare numerose istruzioni.
2.2.3.3. Dichiarazione delle variabili
Una variabile è identificata da un nome e si riferisce a un tipo di dati. Il nome di una variabile Java è composto da n caratteri: il primo deve essere una lettera, mentre gli altri possono essere lettere o cifre. Java distingue tra maiuscole e minuscole. Pertanto, le variabili FIN e fin sono diverse.
Le variabili possono essere inizializzate al momento della loro dichiarazione. La sintassi per dichiarare una o più variabili è:
dove identificateur_de_type è un tipo predefinito oppure un tipo oggetto definito dal programmatore.
2.2.4. Le conversioni tra numeri e stringhe di caratteri
numero -> stringa | "" + numero |
stringa -> int | Integer.parseInt(stringa) |
stringa -> long | Long.parseLong(stringa) |
stringa -> double | Double.valueOf(stringa).doubleValue() |
stringa -> float | Float.valueOf(stringa).floatValue() |
Ecco un programma che illustra le principali tecniche di conversione tra numeri e stringhe di caratteri. La conversione di una stringa in un numero può fallire se la stringa non rappresenta un numero valido. In tal caso viene generato un errore fatale, denominato “eccezione” in Java. Questo errore può essere gestito tramite la clausola try/catch seguente:
try{
appel de la fonction susceptible de générer l'exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
Se la funzione non genera un'eccezione, si passa all'istruzione successiva; in caso contrario, si passa al corpo della clausola catch e poi all'istruzione successiva. Torneremo in seguito sulla gestione delle eccezioni.
import java.io.*;
public class conv1{
public static void main(String arg[]){
String S;
final int i=10;
final long l=100000;
final float f=(float)45.78;
double d=-14.98;
// numero --> stringa
S=""+i;
affiche(S);
S=""+l;
affiche(S);
S=""+f;
affiche(S);
S=""+d;
affiche(S);
//booleano --> stringa
final boolean b=false;
S=""+new Boolean(b);
affiche(S);
// stringa --> int
int i1;
i1=Integer.parseInt("10");
affiche(""+i1);
try{
i1=Integer.parseInt("10.67");
affiche(""+i1);
} catch (Exception e){
affiche("Erreur "+e);
}
// stringa --> long
long l1;
l1=Long.parseLong("100");
affiche(""+l1);
try{
l1=Long.parseLong("10.675");
affiche(""+l1);
} catch (Exception e){
affiche("Erreur "+e);
}
// stringa --> double
double d1;
d1=Double.valueOf("100.87").doubleValue();
affiche(""+d1);
try{
d1=Double.valueOf("abcd").doubleValue();
affiche(""+d1);
} catch (Exception e){
affiche("Erreur "+e);
}
// stringa --> float
float f1;
f1=Float.valueOf("100.87").floatValue();
affiche(""+f1);
try{
d1=Float.valueOf("abcd").floatValue();
affiche(""+f1);
} catch (Exception e){
affiche("Erreur "+e);
}
}// fine main
public static void affiche(String S){
System.out.println("S="+S);
}
}// fine classe
I risultati ottenuti sono i seguenti:
S=10
S=100000
S=45.78
S=-14.98
S=false
S=10
S=Erreur java.lang.NumberFormatException: 10.67
S=100
S=Erreur java.lang.NumberFormatException: 10.675
S=100.87
S=Erreur java.lang.NumberFormatException: abcd
S=100.87
S=Erreur java.lang.NumberFormatException: abcd
2.2.5. Gli array di dati
Un array Java è un oggetto che consente di raggruppare dati dello stesso tipo sotto un unico identificatore. La sua dichiarazione è la seguente:
Tipo Array[] = new Tipo[n] oppure Tipo[] Array = new Tipo[n]
Entrambe le sintassi sono valide. n è il numero di elementi che l'array può contenere. La sintassi Array[i] indica l'elemento n° i, dove i appartiene all'intervallo [0,n-1]. Qualsiasi riferimento al dato Tableau[i] in cui i non appartiene all'intervallo [0,n-1] provocherà un'eccezione.
Un array bidimensionale può essere dichiarato come segue:
Type Tableau[][] = new Type[n][p] oppure Type[][] Tableau = new Type[n][p]
La sintassi Tableau[i] indica il dato n. i di Tableau, dove i appartiene all'intervallo [0,n-1]. Tableau[i] è a sua volta una tabella: Tableau[i][j] indica il dato n. j della tabella [i], dove j appartiene all'intervallo [0,p-1]. Qualsiasi riferimento a un dato di Tableau con indici errati genera un errore irreversibile.
Ecco un esempio:
public class test1{
public static void main(String arg[]){
float[][] taux=new float[2][2];
taux[1][0]=0.24F;
taux[1][1]=0.33F;
System.out.println(taux[1].length);
System.out.println(taux[1][1]);
}
}
e i risultati dell'esecuzione:
Un array è un oggetto che possiede l'attributo length: si tratta della dimensione dell'array.
2.3. Le istruzioni elementari di Java
Si distingue tra
- le istruzioni elementari eseguite dal computer
- le istruzioni di controllo dello svolgimento del programma.
Le istruzioni elementari risultano evidenti se si considera la struttura di un microcomputer e delle sue periferiche.

-
lettura delle informazioni provenienti dalla tastiera
-
elaborazione delle informazioni
-
visualizzazione di informazioni sullo schermo
-
lettura di informazioni da un file su disco
-
scrittura di informazioni in un file su disco
2.3.1. Scrittura sullo schermo
La sintassi dell'istruzione di scrittura sullo schermo è la seguente:
System.out.println(espressione) oppure System.err.println(espressione)
dove expression è un tipo di dato qualsiasi che possa essere convertito in una stringa di caratteri da visualizzare sullo schermo. Nell’esempio precedente abbiamo visto due istruzioni di scrittura:
System.out scrive in un file di testo che, per impostazione predefinita, è lo schermo. Lo stesso vale per System.err. Questi file recano un numero (ad esempio descripteur) rispettivamente pari a 1 e 2. Anche il flusso di input dalla tastiera (System.in) è considerato un file di testo, con descrittore 0. Sia DOS che Unix supportano il piping dei comandi:
Tutto ciò che commande1 scrive con System.out viene reindirizzato (tramite pipe) verso l'ingresso System.in di commande2. In altre parole, commande2 legge tramite System.in i dati generati da commande2 tramite System.out, che quindi non vengono più visualizzati sullo schermo. Questo sistema è molto diffuso in ambiente Unix. In questa concatenazione, il flusso System.err non viene invece reindirizzato: scrive sullo schermo. Ecco perché viene utilizzato per scrivere i messaggi di errore (da cui il suo nome err): si ha la certezza che, durante un reindirizzamento dei comandi, i messaggi di errore continueranno a essere visualizzati sullo schermo. Si prenderà quindi l’abitudine di scrivere i messaggi di errore sullo schermo con il flusso System.err piuttosto che con il flusso System.out.
2.3.2. Lettura dei dati digitati dalla tastiera
Il flusso di dati proveniente dalla tastiera è indicato dall’oggetto System.in di tipo InputStream. Questo tipo di oggetti consente di leggere i dati carattere per carattere. Spetta poi al programmatore individuare in questo flusso di caratteri le informazioni che gli interessano. Il tipo InputStream non consente di leggere un’intera riga di testo in un’unica operazione. Il tipo BufferedReader lo consente tramite il metodo readLine.
Per poter leggere le righe di testo digitate dalla tastiera, si crea, a partire dal flusso di input System.in di tipo InputStream, un altro flusso di input, questa volta di tipo BufferedReader:
Non spiegheremo qui i dettagli di questa istruzione, che coinvolge il concetto di costruzione di oggetti. La useremo così com’è.
La creazione di un flusso può fallire: in tal caso viene generato un errore fatale, chiamato eccezione in Java. Ogni volta che un metodo è suscettibile di generare un’eccezione, il compilatore Java richiede che essa venga gestita dal programmatore. Pertanto, per creare il flusso di input precedente, sarà necessario in realtà scrivere:
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
System.err.println("Erreur " +e);
System.exit(1);
}
Anche in questo caso, non ci soffermeremo qui a spiegare la gestione delle eccezioni. Una volta creato il flusso IN di cui sopra, è possibile leggere una riga di testo tramite l'istruzione:
La riga digitata sulla tastiera viene memorizzata nella variabile ligne e può quindi essere utilizzata dal programma.
2.3.3. Esempio di operazioni di input-output
Ecco un programma che illustra le operazioni di input-output da tastiera/schermo:
import java.io.*; // necessario per l'utilizzo dei flussi di I/O
public class io1{
public static void main (String[] arg){
// scrittura sul flusso System.out
Object obj=new Object();
System.out.println(""+obj);
System.out.println(obj.getClass().getName());
// scrittura sul flusso System.err
int i=10;
System.err.println("i="+i);
// lettura di una riga immessa dalla tastiera
String ligne;
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
affiche(e);
System.exit(1);
}
System.out.print("Tapez une ligne : ");
try{
ligne=IN.readLine();
System.out.println("ligne="+ligne);
} catch (Exception e){
affiche(e);
System.exit(2);
}
}//fine mano
public static void affiche(Exception e){
System.err.println("Erreur : "+e);
}
}//fine classe
e i risultati dell'esecuzione:
C:\Serge\java\bases\iostream>java io1
java.lang.Object@1ee78b
java.lang.Object
i=10
Tapez une ligne : je suis là
ligne=je suis là
Le istruzioni
hanno lo scopo di dimostrare che qualsiasi oggetto può essere visualizzato. In questa sede non cercheremo di spiegare il significato di ciò che viene visualizzato. Nel blocco è presente anche la visualizzazione del valore di un oggetto:
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
affiche(e);
System.exit(1);
}
La variabile e è un oggetto di tipo Exception che viene visualizzato qui con la chiamata affiche(e). Avevamo già incontrato, senza però menzionarlo, questa visualizzazione del valore di un'eccezione nel programma di conversione visto in precedenza.
2.3.4. Assegnazione del valore di un’espressione a una variabile
Ci interessiamo qui all’operazione variable=expression;
L'espressione può essere di tipo: aritmetico, relazionale, booleano, stringa
2.3.4.1. Interpretazione dell'operazione di assegnazione
L'operazione variable=expression; è essa stessa un'espressione la cui valutazione avviene nel modo seguente:
- Viene valutata la parte destra dell'assegnazione: il risultato è un valore V.
- il valore V viene assegnato alla variabile
- il valore V è anche il valore dell'assegnazione, considerata questa volta come espressione.
È così che l’operazione V1=V2=espressione è valida. A causa della priorità, verrà valutato l'operatore = più a destra. Si ottiene quindi V1=(V2=espressione). L'espressione V2=espressione viene valutata e ha come valore V. La valutazione di questa espressione ha provocato l'assegnazione di V a V2. L'operatore = successivo viene quindi valutato nella forma V1=V. Il valore di questa espressione è ancora V. La sua valutazione provoca l’assegnazione di V a V1. Pertanto, l’operazione V1=V2=espressione è un'espressione la cui valutazione
1: provoca l'assegnazione del valore di expression alle variabili V1 e V2
2: restituisce come risultato il valore di expression.
È possibile generalizzare a un’espressione del tipo: V1=V2=....=Vn=espressione
2.3.4.2. Espressione aritmetica
Gli operatori delle espressioni aritmetiche sono i seguenti:
+: addizione
- : sottrazione
*: moltiplicazione
/ : divisione: il risultato è il quoziente esatto se almeno uno degli operandi è un numero reale. Se entrambi gli operandi sono interi, il risultato è il quoziente intero. Quindi 5/2 -> 2 e 5,0/2 -> 2,5.
% : divisione: il risultato è il resto indipendentemente dalla natura degli operandi, mentre il quoziente è intero. Si tratta quindi dell'operazione modulo.
Esistono diverse funzioni matematiche:
double sqrt(double x) | radice quadrata |
double cos(double x) | Coseno |
double sin(double x) | Sen |
double tan(double x) | Tangente |
double pow(double x, double y) | x elevato a y (x>0) |
double exp(double x) | Esponenziale |
double log(double x) | Logaritmo naturale |
double abs(double x) | valore assoluto |
ecc...
Tutte queste funzioni sono definite in una classe Java chiamata Math. Quando le si utilizza, è necessario anteporre il nome della classe in cui sono definite. Quindi si scriverà:
La definizione della classe Math è la seguente:
public final class java.lang.Math
extends java.lang.Object (I-§1.12)
{
// Campi
public final static double E; §1.10.1
public final static double PI; §1.10.2
// Metodi
public static double abs(double a); §1.10.3
public static float abs(float a); §1.10.4
public static int abs(int a); §1.10.5
public static long abs(long a); §1.10.6
public static double acos(double a); §1.10.7
public static double asin(double a); §1.10.8
public static double atan(double a); §1.10.9
public static double atan2(double a, double b); §1.10.10
public static double ceil(double a); §1.10.11
public static double cos(double a); §1.10.12
public static double exp(double a); §1.10.13
public static double floor(double a); §1.10.14
public static double §1.10.15
IEEEremainder(double f1, double f2);
public static double log(double a); §1.10.16
public static double max(double a, double b); §1.10.17
public static float max(float a, float b); §1.10.18
public static int max(int a, int b); §1.10.19
public static long max(long a, long b); §1.10.20
public static double min(double a, double b); §1.10.21
public static float min(float a, float b); §1.10.22
public static int min(int a, int b); §1.10.23
public static long min(long a, long b); §1.10.24
public static double pow(double a, double b); §1.10.25
public static double random(); §1.10.26
public static double rint(double a); §1.10.27
public static long round(double a); §1.10.28
public static int round(float a); §1.10.29
public static double sin(double a); §1.10.30
public static double sqrt(double a); §1.10.31
public static double tan(double a); §1.10.32
}
2.3.4.3. Priorità nella valutazione delle espressioni aritmetiche
La priorità degli operatori nella valutazione di un'espressione aritmetica è la seguente (dal più prioritario al meno prioritario):
[fonctions], [ ( )], [ *, /, %], [+, -]
Gli operatori appartenenti allo stesso blocco [ ] hanno la stessa priorità.
2.3.4.4. Espressioni relazionali
Gli operatori sono i seguenti: <, <=, ==, !=, >, >=
ordine di priorità
>, >=, <, <=
==, !=
Il risultato di un'espressione relazionale è il valore booleano false se l'espressione è falsa, true altrimenti.
Esempio:
Confronto tra due caratteri
Siano dati due caratteri C1 e C2. È possibile confrontarli utilizzando gli operatori
<, <=, ==, !=, >, >=
Vengono quindi confrontati i loro codici ASCII, che sono numeri. Si ricorda che, secondo l'ordine ASCII, si hanno le seguenti relazioni:
spazio < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. < 'z'
Confronto tra due stringhe di caratteri
Vengono confrontate carattere per carattere. La prima disuguaglianza riscontrata tra due caratteri determina una disuguaglianza dello stesso segno tra le stringhe.
Esempi:
Si considerino le stringhe "Gatto" e "Cane"
Quest'ultima disuguaglianza permette di affermare che "Gatto" < "Cane".

Supponiamo di confrontare le stringhe "Gatto" e "Gattino". C'è uguaglianza per tutto il tempo fino all'esaurimento della stringa "Gatto". In questo caso, la stringa esaurita viene dichiarata la più "piccola". Si ha quindi la relazione
"Gatto" < "Gattino".
Funzioni di confronto tra due stringhe
Qui non è possibile utilizzare gli operatori relazionali <, <=, ==, !=, >, >=. È necessario utilizzare i metodi della classe String:
String chaine1, chaine2;
chaine1=…;
chaine2=…;
int i=chaine1.compareTo(chaine2);
boolean egal=chaine1.equals(chaine2)
Nell'esempio sopra riportato, la variabile i avrà il valore:
0: se le due stringhe sono uguali
1: se la stringa n. 1 è maggiore della stringa n. 2
-1: se la stringa n. 1 è minore della stringa n. 2
La variabile egal assumerà il valore true se le due stringhe sono uguali.
2.3.4.5. Espressioni booleane
Gli operatori sono & (and) ||(or) e ! (not). Il risultato di un'espressione booleana è un valore booleano.
Ordine di priorità ! , &&, ||
esempio:
Gli operatori relazionali hanno la precedenza sugli operatori && e ||.
2.3.4.6. Operazioni bit a bit
Gli operatori
Siano i e j due numeri interi.
i<<n | sposta i di n bit a sinistra. I bit di riempimento sono zeri. |
i>>n | sposta i di n bit a destra. Se i è un numero intero con segno (signed char, int, long), il bit di segno viene mantenuto. |
i & j | effettua l'OR logico bit per bit tra i e j. |
i | j | effettua la OU logica di i e j bit per bit. |
~i | completa i a 1 |
i^j | esegue l'operazione OU EXCLUSIF su i e j |
Sia
operazione | valore |
i<<4 | 0x23F0 |
i>>4 | 0x0123 il bit di segno viene mantenuto. |
k>>4 | 0xFF12 il bit di segno viene mantenuto. |
i&j | 0x1023 |
i|j | 0xF33F |
~i | 0xEDC0 |
2.3.4.7. Combinazione di operatori
a=a+b può essere scritto come a+=b
a=a-b può essere scritto come a-=b
Lo stesso vale per gli operatori /, %, *, <<, >>, &, |, ^
Pertanto, a=a+2; può essere scritto come a+=2;
2.3.4.8. Operatori di incremento e decremento
La notazione variable++ significa variable=variable+1 oppure variable+=1
La notazione variable-- indica variable=variable-1 oppure variable-=1.
2.3.4.9. L'operatore?
L'espressione expr_cond ? expr1:expr2 viene valutata nel modo seguente:
1: viene valutata l'espressione expr_cond. Si tratta di un'espressione condizionale con valore vero o falso
2: se è vera, il valore dell’espressione è quello di expr1. expr2 non viene valutata.
3: se è falsa, avviene il contrario: il valore dell’espressione è quello di expr2. expr1 non viene valutata.
Esempio
i=(j>4 ? j+1:j-1);
assegnerà alla variabile i:
j+1 se j>4, altrimenti j-1
È come scrivere if(j>4) i=j+1; else i=j-1; ma è più conciso.
2.3.4.10. Priorità generale degli operatori
() [] funzione | gd |
! ~ ++ -- | dg |
new (tipo) operatori di conversione | dg |
* / % | gd |
+ - | gd |
<< >> | gd |
< <= > >= instanceof | gd |
== != | gd |
& | gd |
^ | gd |
| | gd |
&& | gd |
|| | gd |
? : | dg |
= += -= ecc. . | dg |
gd: indica che, a parità di priorità, viene osservata la priorità da sinistra a destra. Ciò significa che quando in un'espressione sono presenti operatori con la stessa priorità, viene valutato per primo l'operatore più a sinistra nell'espressione. dg indica una priorità da destra a sinistra.
2.3.4.11. I cambiamenti di tipo
In un'espressione è possibile modificare temporaneamente la codifica di un valore. Questo processo è noto come "cambio di tipo" o, in inglese, "type casting". La sintassi per il cambio di tipo di un valore in un'espressione è la seguente: (tipo) valore. Il valore assume quindi il tipo indicato. Ciò comporta una modifica della codifica del valore.
Esempio:
In questo caso è necessario modificare il tipo di i o j in reale, altrimenti la divisione restituirà un quoziente intero e non reale.
i è un valore codificato in modo esatto su 2 byte
(float) i è lo stesso valore codificato in modo approssimativo come numero reale su 4 byte
Si verifica quindi una transcodifica del valore di i. Questa transcodifica avviene solo per la durata di un calcolo, mentre la variabile i mantiene sempre il proprio tipo int.
2.4. Le istruzioni di controllo dello svolgimento del programma
2.4.1. Arresto
Il metodo exit definito nella classe System consente di interrompere l’esecuzione di un programma.
Azione: interrompe il processo in corso e restituisce il valore status al processo padre
exit provoca la fine del processo in corso e restituisce il controllo al processo chiamante. Il valore di status può essere utilizzato da quest'ultimo. In DOS, questa variabile status viene restituita a DOS nella variabile di sistema ERRORLEVEL, il cui valore può essere verificato in un file batch. In Unix, è la variabile $? che recupera il valore di status se l'interprete di comandi è la Bourne Shell (/bin/sh).
Esempio:
per terminare il programma con un valore di stato pari a 0.
2.4.2. Struttura di selezione semplice
syntaxe : if (condition) {actions_condition_vraie;} else {actions_condition_fausse;}
Note:
- la condizione è racchiusa tra parentesi.
- Ogni azione è terminata da un punto e virgola.
- le parentesi graffe non sono seguite da un punto e virgola.
- le parentesi graffe sono necessarie solo se c'è più di un'azione.
- La clausola else può essere omessa.
- Non c'è il «then».
L'equivalente algoritmico di questa struttura è la struttura if ... then ... else:
esempio
if (x>0) { nx=nx+1;sx=sx+x;} else dx=dx-x;
È possibile annidare le strutture di selezione:
A volte si presenta il seguente problema:
public static void main(void){
int n=5;
if(n>1)
if(n>6)
System.out.println(">6");
else System.out.println("<=6");
}
Nell'esempio precedente, a quale if si riferisce il else? La regola prevede che un codice else si riferisca sempre al codice if più vicino: if(n>6) nell'esempio. Consideriamo un altro esempio:
public static void main(void)
{ int n=0;
if(n>1)
if(n>6) System.out.println(">6");
else; // else dell'if(n>6): niente da fare
else System.out.println("<=1"); // else dell'if(n>1)
}
Qui volevamo inserire un else al posto di if(n>1) e non un else al posto di if(n>6). A causa dell'osservazione precedente, siamo costretti a inserire un "else" per il codice if(n>6), nel quale non è presente alcuna istruzione.
2.4.3. Struttura dei casi
La sintassi è la seguente:
switch(expression) {
case v1:
actions1;
break;
case v2:
actions2;
break;
. .. .. .. .. ..
default: actions_sinon;
}
note
- Il valore dell'espressione di controllo può essere solo un numero intero o un carattere.
- L'espressione di controllo è racchiusa tra parentesi.
- La clausola default può essere assente.
- I valori vi sono valori possibili dell'espressione. Se l'espressione ha come valore vi, vengono eseguite le azioni successive alla clausola case vi.
- L'istruzione break fa uscire dalla struttura case. Se è assente alla fine del blocco di istruzioni con valore vi, l'esecuzione prosegue con le istruzioni del valore vi+1.
Esempio
In algoritmica
selon la valeur de choix
cas 0
arrêt
cas 1
exécuter module M1
cas 2
exécuter module M2
sinon
erreur<--vrai
findescas
In Java
int choix, erreur;
switch(choix){
case 0: System.exit(0);
case 1: M1();break;
case 2: M2();break;
default: erreur=1;
}
2.4.4. Struttura di ripetizione
2.4.4.1. Numero di ripetizioni noto
Sintassi
for (i=id;i<=if;i=i+ip){
actions;
}
Note
- I 3 argomenti di for sono racchiusi tra parentesi.
- I 3 argomenti di for sono separati da punti e virgola.
- Ogni azione di for termina con un punto e virgola.
- La parentesi graffa è necessaria solo se vi è più di un'azione.
- La parentesi graffa non è seguita da un punto e virgola.
L'equivalente algoritmico è la struttura pour:
che può essere tradotta in una struttura tantque:
2.4.4.2. Numero di ripetizioni sconosciuto
In Java esistono numerose strutture per questo caso.
Struttura «while»
while(condition){
actions;
}
Il ciclo continua finché la condizione è vera. Il ciclo potrebbe non essere mai eseguito.
Note:
- la condizione è racchiusa tra parentesi.
- Ogni azione è seguita da un punto e virgola.
- la parentesi graffa è necessaria solo se c'è più di un'azione.
- la parentesi graffa non è seguita da un punto e virgola.
La struttura algoritmica corrispondente è la struttura «finché»:
Struttura «ripeti fino a» (do while)
La sintassi è la seguente:
do{
instructions;
}while(condition);
Il ciclo continua finché la condizione non diventa falsa o finché la condizione rimane vera. In questo caso il ciclo viene eseguito almeno una volta.
Note
- la condizione è racchiusa tra parentesi.
- Ogni azione è seguita da un punto e virgola.
- la parentesi graffa è necessaria solo se c'è più di un'azione.
- La parentesi graffa non è seguita da un punto e virgola.
La struttura algoritmica corrispondente è la struttura «ripeti … fino a»:
Struttura «for» (for)
La sintassi è la seguente:
for(instructions_départ;condition;instructions_fin_boucle){
instructions;
}
Il ciclo continua finché la condizione è vera (valutata prima di ogni iterazione). Le istruzioni Instructions_départ vengono eseguite prima di entrare nel ciclo per la prima volta. Le istruzioni Instructions_fin_boucle vengono eseguite dopo ogni iterazione.
note
- I 3 argomenti di for sono racchiusi tra parentesi.
- I 3 argomenti di for sono separati da punti e virgola.
- Ogni azione del ciclo «for» termina con un punto e virgola.
- La parentesi graffa è necessaria solo se vi è più di un'azione.
- La parentesi graffa non è seguita da un punto e virgola.
- Le diverse istruzioni nei file instructions_depart e instructions_fin_boucle sono separate da virgole.
La struttura algoritmica corrispondente è la seguente:
Esempi
I seguenti programmi calcolano tutti la somma dei primi n numeri interi.
1 for(i=1, somme=0;i<=n;i=i+1)
somme=somme+a[i];
2 for (i=1, somme=0;i<=n;somme=somme+a[i], i=i+1);
3 i=1;somme=0;
while(i<=n)
{ somme+=i; i++; }
4 i=1; somme=0;
do somme+=i++;
while (i<=n);
Istruzioni per la gestione dei cicli
break | esce dal ciclo for, while, do ... while. |
continue | passa all'iterazione successiva nei cicli for, while, do ... while |
2.5. La struttura di un programma Java
Un programma Java che non utilizza classi definite dall'utente né funzioni diverse dalla funzione principale main potrà avere la seguente struttura:
public class test1{
public static void main(String arg[]){
… code du programme
}// main
}// class
La funzione main, detta anche metodo, viene eseguita per prima durante l'esecuzione di un programma Java. Deve obbligatoriamente avere la seguente firma:
public static void main(String arg[]){
oppure
public static void main(String[] arg){
Il nome dell'argomento arg può essere qualsiasi. Si tratta di un array di stringhe che rappresenta gli argomenti della riga di comando. Ne riparleremo più avanti.
Se si utilizzano funzioni che potrebbero generare eccezioni che non si desidera gestire in modo dettagliato, è possibile racchiudere il codice del programma in una clausola try/catch:
public class test1{
public static void main(String arg[]){
try{
… code du programme
} catch (Exception e){
// gestione degli errori
}// try
}// main
}// classe
All'inizio del codice sorgente e prima della definizione della classe, è comune trovare istruzioni di importazione delle classi. Ad esempio:
import java.io.*;
public class test1{
public static void main(String arg[]){
… code du programme
}// main
}// classe
Facciamo un esempio. Consideriamo la seguente istruzione di scrittura:
che scrive java sullo schermo. In questa semplice istruzione ci sono molte cose:
- System è una classe il cui nome completo è java.lang.System
- out è una proprietà di questa classe di tipo java.io.PrintStream, un’altra classe
- println è un metodo della classe java.io.PrintStream.
Non complicheremo inutilmente questa spiegazione, che arriva troppo presto poiché richiede la comprensione del concetto di classe, non ancora trattato. Si può considerare una classe come una risorsa. In questo caso, il compilatore dovrà avere accesso alle due classi java.lang.System e java.io.PrintStream. Le centinaia di classi di Java sono suddivise in archivi chiamati anche pacchetti (package). Le istruzioni import inserite all’inizio del programma servono a indicare al compilatore di quali classi esterne ha bisogno il programma (quelle utilizzate ma non definite nel file sorgente che verrà compilato). Pertanto, nel nostro esempio, il nostro programma necessita delle classi java.lang.System e java.io.PrintStream. Lo si indica con l’istruzione import. All’inizio del programma si potrebbe scrivere:
Poiché un programma Java utilizza comunemente diverse decine di classi esterne, sarebbe complicato scrivere tutte le funzioni import necessarie. Le classi sono state raggruppate in pacchetti ed è quindi possibile importare l’intero pacchetto. Pertanto, per importare i pacchetti java.lang e java.io, si scriverà:
Il pacchetto java.lang contiene tutte le classi di base di Java e viene importato automaticamente dal compilatore. Pertanto, alla fine si scriverà semplicemente:
2.6. Gestione delle eccezioni
Numerose funzioni Java possono generare eccezioni, ovvero errori. Abbiamo già incontrato una funzione di questo tipo, la funzione readLine:
String ligne=null;
try{
ligne=IN.readLine();
System.out.println("ligne="+ligne);
} catch (Exception e){
affiche(e);
System.exit(2);
}// try
Quando una funzione può generare un'eccezione, il compilatore Java impone al programmatore di gestirla al fine di ottenere programmi più resistenti agli errori: occorre sempre evitare il "crash" incontrollato di un'applicazione. In questo caso, la funzione readLine genera un'eccezione se non c'è nulla da leggere, ad esempio perché il flusso di input è stato chiuso. La gestione di un'eccezione avviene secondo lo schema seguente:
try{
appel de la fonction susceptible de générer l'exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
Se la funzione non genera un'eccezione, si passa all'istruzione successiva; in caso contrario, si passa al corpo della clausola catch e poi all'istruzione successiva. Si notino i seguenti punti:
- e è un oggetto derivato dal tipo Exception. È possibile essere più precisi utilizzando tipi quali IOException, SecurityException, ArithmeticException, ecc.: esistono una ventina di tipi di eccezioni. Scrivendo catch (Exception e), si indica che si desidera gestire tutti i tipi di eccezioni. Se il codice della clausola try è suscettibile di generare diversi tipi di eccezioni, si può voler essere più precisi gestendo l’eccezione con più clausole catch:
try{
appel de la fonction susceptible de générer l'exception
} catch (IOException e){
traiter l'exception e
}
} catch (ArrayIndexOutOfBoundsException e){
traiter l'exception e
}
} catch (RunTimeException e){
traiter l'exception e
}
instruction suivante
- È possibile aggiungere alle clausole try/catch una clausola finally:
try{
appel de la fonction susceptible de générer l'exception
} catch (Exception e){
traiter l'exception e
}
finally{
code exécuté après try ou catch
}
instruction suivante
In questo caso, indipendentemente dal fatto che si verifichi o meno un'eccezione, il codice della clausola finally verrà sempre eseguito.
- La classe Exception dispone di un metodo getMessage() che restituisce un messaggio con i dettagli dell’errore verificatosi. Pertanto, se si desidera visualizzarlo, si scriverà:
catch (Exception ex){
System.err.println("L'erreur suivante s'est produite : "+ex.getMessage());
...
}//catch
- La classe Exception dispone di un metodo toString() che restituisce una stringa di caratteri indicante il tipo dell'eccezione e il valore della proprietà Message. Si potrà quindi scrivere:
catch (Exception ex){
System.err.println ("L'erreur suivante s'est produite : "+ex.toString());
...
}//catch
Si può scrivere anche:
In questo caso abbiamo un'operazione string + Exception che verrà automaticamente trasformata dal compilatore in string + Exception.toString() per concatenare due stringhe di caratteri.
L'esempio seguente mostra un'eccezione generata dall'utilizzo di un elemento di array inesistente:
// quadri
// importazioni
import java.io.*;
public class tab1{
public static void main(String[] args){
// dichiarazione e inizializzazione di un array
int[] tab=new int[] {0,1,2,3};
int i;
// visualizzazione di un array con un ciclo for
for (i=0; i<tab.length; i++)
System.out.println("tab[" + i + "]=" + tab[i]);
// generazione di un'eccezione
try{
tab[100]=6;
}catch (Exception e){
System.err.println("L'erreur suivante s'est produite : " + e);
}//try-catch
}//main
}//classe
L'esecuzione del programma fornisce i seguenti risultati:
tab[0]=0
tab[1]=1
tab[2]=2
tab[3]=3
L'erreur suivante s'est produite : java.lang.ArrayIndexOutOfBoundsException
Ecco un altro esempio in cui si gestisce l'eccezione causata dall'assegnazione di una stringa di caratteri a un numero quando la stringa non rappresenta un numero:
// importazioni
import java.io.*;
public class console1{
public static void main(String[] args){
// creazione di un flusso di input
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
}catch(Exception ex){}
// Richiesta del nome
System.out.print("Nom : ");
// lettura della risposta
String nom=null;
try{
nom=IN.readLine();
}catch(Exception ex){}
// viene richiesta l'età
int age=0;
boolean ageOK=false;
while ( ! ageOK){
// domanda
System.out.print("âge : ");
// Lettura e verifica della risposta
try{
age=Integer.parseInt(IN.readLine());
ageOK=true;
}catch(Exception ex) {
System.err.println("Age incorrect, recommencez...");
}//try-catch
}//while
// visualizzazione finale
System.out.println("Vous vous appelez " + nom + " et vous avez " + age + " ans");
}//Main
}//classe
Alcuni risultati dell'esecuzione:
E:\data\serge\MSNET\c#\bases\1>console1
Nom : dupont
âge : xx
Age incorrect, recommencez...
âge : 12
Vous vous appelez dupont et vous avez 12 ans
2.7. Compilazione ed esecuzione di un programma Java
Compilare ed eseguire il seguente programma:
// importazione delle classi
import java.io.*;
// classe di test
public class coucou{
// funzione main
public static void main(String args[]){
// visualizzazione sullo schermo
System.out.println("coucou");
}//main
}//classe
Il file sorgente contenente la classe coucou precedente deve necessariamente chiamarsi coucou.java:
La compilazione e l'esecuzione di un programma Java avvengono in una finestra DOS. I file eseguibili javac.exe (compilatore) e java.exe (interprete) si trovano nella directory bin della directory di installazione di JDK:
E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\bin\java?.exe"
07/02/2002 12:52 24 649 java.exe
07/02/2002 12:52 28 766 javac.exe
Il compilatore javac.exe analizzerà il file sorgente .java e genererà un file compilato .class. Quest'ultimo non è immediatamente eseguibile dal processore. Richiede un interprete Java (java.exe), denominato macchina virtuale o JVM (Java Virtual Machine). A partire dal codice intermedio presente nel file .class, la macchina virtuale genererà istruzioni specifiche per il processore del computer su cui viene eseguita. Esistono macchine virtuali Java per diversi tipi di sistemi operativi (Windows, Unix, Mac OS, ecc.). Un file .class potrà essere eseguito da una qualsiasi di queste macchine virtuali e quindi su qualsiasi sistema operativo. Questa portabilità tra sistemi è uno dei principali punti di forza di Java.
Compiliamo il programma precedente:
E:\data\serge\JAVA\ESSAIS\intro1>"e:\program files\jdk14\bin\javac" coucou.java
E:\data\serge\JAVA\ESSAIS\intro1>dir
10/06/2002 08:42 228 coucou.java
10/06/2002 08:48 403 coucou.class
Eseguiamo il file .class generato:
Si noti che nella richiesta di esecuzione sopra riportata non è stato specificato il suffisso .class del file coucou.class da eseguire. È implicito. Se la directory bin del file JDK si trova all’interno del file PATH della macchina DOS, non sarà necessario specificare il percorso completo dei file eseguibili javac.exe e java.exe. Basterà quindi scrivere semplicemente
2.8. Argomenti del programma principale
La funzione principale main accetta come parametro un array di stringhe: String[]. Questo array contiene gli argomenti della riga di comando utilizzata per avviare l'applicazione. Pertanto, se si avvia il programma P con il comando:
e se la funzione main è dichiarata come segue:
si avrà arg[0]="arg0", arg[1]="arg1" … Ecco un esempio:
import java.io.*;
public class param1{
public static void main(String[] arg){
int i;
System.out.println("Nombre d'arguments="+arg.length);
for (i=0;i<arg.length;i++)
System.out.println("arg["+i+"]="+arg[i]);
}
}
I risultati ottenuti sono i seguenti:
2.9. Passaggio di parametri a una funzione
Gli esempi precedenti hanno mostrato solo programmi Java con una sola funzione, ovvero la funzione principale main. L'esempio seguente illustra come utilizzare le funzioni e come avviene lo scambio di informazioni tra di esse. I parametri di una funzione vengono sempre passati per valore: ciò significa che il valore del parametro effettivo viene copiato nel parametro formale corrispondente.
import java.io.*;
public class param2{
public static void main(String[] arg){
String S="papa";
changeString(S);
System.out.println("Paramètre effectif S="+S);
int age=20;
changeInt(age);
System.out.println("Paramètre effectif age="+age);
}
private static void changeString(String S){
S="maman";
System.out.println("Paramètre formel S="+S);
}
private static void changeInt(int a){
a=30;
System.out.println("Paramètre formel a="+a);
}
}
I risultati ottenuti sono i seguenti:
I valori dei parametri effettivi "papa" e 20 sono stati copiati nei parametri formali S e a. Questi ultimi sono stati poi modificati. I parametri effettivi sono rimasti invece invariati. Si noti in particolare il tipo dei parametri effettivi:
- S è un riferimento all’oggetto c.a.d, ovvero l’indirizzo di un oggetto in memoria
- age è un valore intero
2.10. L'esempio delle imposte
Concluderemo questo capitolo con un esempio che riprenderemo più volte nel presente documento. Ci proponiamo di scrivere un programma che consenta di calcolare l’imposta di un contribuente. Consideriamo il caso semplificato di un contribuente che abbia da dichiarare solo il proprio stipendio:
- si calcola il numero di quote del dipendente nbParts=nbEnfants/2 +1 se non è sposato, nbEnfants/2+2 se è sposato, dove nbEnfants è il numero dei suoi figli.
- se ha almeno tre figli, ha diritto a una mezza quota in più
- si calcola il suo reddito imponibile R = 0,72 * S, dove S è il suo stipendio annuale
- si calcola il suo coefficiente familiare QF = R / nbParts
- si calcola la sua imposta I. Consideriamo la seguente tabella:
12620,0 | 0 | 0 |
13190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24.740 | 0,15 | 2072,5 |
31810 | 0,2 | 3309,5 |
39.970 | 0,25 | 4900 |
48360 | 0,3 | 6898,5 |
55790 | 0,35 | 9316,5 |
92970 | 0,4 | 12106 |
127.860 | 0,45 | 16754,5 |
151250 | 0,50 | 23147,5 |
172.040 | 0,55 | 30710 |
195.000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Ogni riga ha 3 campi. Per calcolare l'imposta I, si cerca la prima riga in cui QF<=campo1. Ad esempio, se QF=23000, si troverà la riga
L'imposta I è quindi pari a 0,15*R - 2072,5*nbParts. Se QF è tale che la relazione QF<=campo1 non è mai soddisfatta, allora vengono utilizzati i coefficienti dell’ultima riga. In questo caso:
il che dà l'imposta I=0,65*R - 49062*nbParts.
Il programma Java corrispondente è il seguente:
import java.io.*;
public class impots{
// ------------ main
public static void main(String arg[]){
// dati
// scaglioni fiscali
double Limites[]={12620, 13190, 15640, 24740, 31810, 39970, 48360,55790, 92970, 127860, 151250, 172040, 195000, 0};
// coefficiente applicato al numero di quote
double Coeffn[]={0, 631, 1290.5, 2072.5, 3309.5, 4900, 6898.5, 9316.5,12106, 16754.5, 23147.5, 30710, 39312, 49062};
// il programma
// creazione del flusso di input da tastiera
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
}
catch(Exception e){
erreur("Création du flux d'entrée", e, 1);
}
// si recupera lo stato civile
boolean OK=false;
String reponse=null;
while(! OK){
try{
System.out.print("Etes-vous marié(e) (O/N) ? ");
reponse=IN.readLine();
reponse=reponse.trim().toLowerCase();
if (! reponse.equals("o") && !reponse.equals("n"))
System.out.println("Réponse incorrecte. Recommencez");
else OK=true;
} catch(Exception e){
erreur("Lecture état marital",e,2);
}
}
boolean Marie = reponse.equals("o");
// numero di figli
OK=false;
int NbEnfants=0;
while(! OK){
try{
System.out.print("Nombre d'enfants : ");
reponse=IN.readLine();
try{
NbEnfants=Integer.parseInt(reponse);
if(NbEnfants>=0) OK=true;
else System.err.println("Réponse incorrecte. Recommencez");
} catch(Exception e){
System.err.println("Réponse incorrecte. Recommencez");
}// try
} catch(Exception e){
erreur("Lecture état marital",e,2);
}// try
}// while
// stipendio
OK=false;
long Salaire=0;
while(! OK){
try{
System.out.print("Salaire annuel : ");
reponse=IN.readLine();
try{
Salaire=Long.parseLong(reponse);
if(Salaire>=0) OK=true;
else System.err.println("Réponse incorrecte. Recommencez");
} catch(Exception e){
System.err.println("Réponse incorrecte. Recommencez");
}// try
} catch(Exception e){
erreur("Lecture Salaire",e,4);
}// try
}// while
// calcolo del numero di quote
double NbParts;
if(Marie) NbParts=(double)NbEnfants/2+2;
else NbParts=(double)NbEnfants/2+1;
if (NbEnfants>=3) NbParts+=0.5;
// reddito imponibile
double Revenu;
Revenu=0.72*Salaire;
// quoziente familiare
double QF;
QF=Revenu/NbParts;
// ricerca della fascia di imposta corrispondente a QF
int i;
int NbTranches=Limites.length;
Limites[NbTranches-1]=QF;
i=0;
while(QF>Limites[i]) i++;
// l'imposta
long impots=(long)(i*0.05*Revenu-Coeffn[i]*NbParts);
// viene visualizzato il risultato
System.out.println("Impôt à payer : " + impots);
}// main
// ------------ errore
private static void erreur(String msg, Exception e, int exitCode){
System.err.println(msg+"("+e+")");
System.exit(exitCode);
}// errore
}// classe
I risultati ottenuti sono i seguenti:
C:\Serge\java\impots\1>java impots
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : 3
Salaire annuel : 200000
Impôt à payer : 16400
C:\Serge\java\impots\1>java impots
Etes-vous marié(e) (O/N) ? n
Nombre d'enfants : 2
Salaire annuel : 200000
Impôt à payer : 33388
C:\Serge\java\impots\1>java impots
Etes-vous marié(e) (O/N) ? w
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? q
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : q
Réponse incorrecte. Recommencez
Nombre d'enfants : 2
Salaire annuel : q
Réponse incorrecte. Recommencez
Salaire annuel : 1
Impôt à payer : 0