Elementi di programmazione in Java
{gspeech style=2}
Struttura di un programma in Java
La sintassi generale usata per scrivere un programma in linguaggio Java, anche soltanto in modo procedurale cioè senza l’uso di oggetti istanziati, può essere schematizzato con la seguente sintassi:
public class nomeclasse {
public static void main (String args[]) {
[istruzioni]
}//fine main
// altri eventuali metodi (funzioni o sottoprogrammi)
}// fine class
In un’applicazione console, il metodo main() deve sempre essere presente e dichiara il punto in cui inizia l’esecuzione del programma. Questa formulazione (orientata agli oggetti) rappresenta un passaggio obbligato anche se si volessero eseguire semplici programmi facilmente scrivibili in linguaggi sequenziali (anche se strutturati) come il C o il Pascal.
Prendiamo ad esempio il seguente listato Pascal
program sum;
var
x,y,somma:integer;
begin
write(‘ins. x:’);
readln(x);
write(‘ins. y:’);
readln(y);
somma:=x+y;
writeln(somma);
end.
Esegue delle semplici operazioni di I/O: accetta due interi da tastiera (x e y) e ne restituisce la somma. Riportato in Java potremo scrivere:
import java.io.*;
class sum {
public static void main (String[] args) throws IOException {
int x,y,somma;
InputStreamReader input=new InputStreamReader(System.in);
BufferedReader h= new BufferedReader(input);
System.out.print(“ins.x:”);
x=Integer.parseInt(h.readLine().trim());
System.out.print(“ins.y:”);
y=Integer.parseInt(h.readLine().trim());
somma=x+y;
System.out.println(somma);
}//fine main
} //fine class
In prima battuta non si può certo dire che il linguaggio Java abbia introdotto delle semplificazioni: qualche chiarimento è obbligatorio.
import java.io.*;
ha lo scopo di dichiarare che nella classe vengono usate classi provenienti dal package (libreria) java.io; nel nostro caso, le classi importate sono:
BufferedReader
InputStreamReader
IOException
Si nota poi l’aggiunta del comando throws nella riga:
main (String[] args) throws IOException
che è una richiesta di ‘sollevare’ il programmatore dalla responsabilità di eventuali eccezioni nelle operazioni di I/O.
La complicazione principale è data dal fatto che in Java i dati acquisiti da tastiera, sono considerati per definizione delle stringhe; tramite i comandi:
InputStreamReader input=new InputStreamReader(System.in);
BufferedReader h= new BufferedReader(input);
Viene definito l’oggetto h che effettivamente legge delle stringhe da tastiera, ma con delle stringhe in input è piuttosto difficile eseguire delle operazioni ponderali di somma, per cui l’istruzione:
y=Integer.parseInt(h.readLine().trim());
acquisisce i dati in ingresso e li converte in tipi di dato intero. La conversione avviene tramite la funzione Integer.parseInt(), mentre la funzione trim() toglie eventuali spazi in testa e in coda alla stringa da convertire.
Quello che possiamo dire è che tramite questo modello possiamo tradurre in Java tutti gli algoritmi già elaborati in linguaggio C.
Identificatori
Nella stesura del codice ci si ritrova a dover assegnare dei nomi (alle costanti, alle variabili a dei metodi) detti identificatori, questi nomi possono essere assegnati arbitrariamente, ma bisogna ricordarsi di evitare le seguenti parole riservate:
abstract | boolean | break | byte | case | catch |
char | class | const | continue | default | do |
double | else | extends | false | final | finally |
float | for | goto | if | implements | import |
inatanceof | int | interface | long | native | new |
null | package | private | protected | public | return |
short | static | super | switch | synchronized | this |
throw | throws | transient | true | try | void |
volatile | while |
Un identificatore non può dunque essere una parola riservata, deve incominciare con una lettera (a-z o A-Z) o con _ (underscore) può contenere lettere, cifre, _, a piacere
Variabili
Una variabile è un’associazione fra un identificatore e un valore; è una “scatola” che contiene un valore. In Java ogni variabile è associata ad un tipo di dati. Un tipo è quindi un insieme di valori e di operazioni eseguibili su quei valori. Prima di usare una variabile bisogna, quindi, dichiarare il suo tipo. Di solito questo viene fatto nella parte dichiarativa di un programma. La dichiarazione di una variabile si esprime con la sintassi:
Tipo nomeVariabile
sono dichiarazioni di variabili
int base, altezza; //dichiara le variabili intere base e altezza senza inizializzarle
float peri, area=3.67; //dichiara le variabili float peri e area inizializzando solo quest’ultima
char ch=’A’;//dichiarazione ed inizializzazione di variabile char
boolean a=false,b;//dichiara due boolean, inizializza solo a
int[] T;//dichiara un vettore di interi
String[] s;//dichiara un vettore di stringhe
Come in C per cambiare il contenuto di una variabile si usa un’istruzione di assegnamento:
nomeVariabile=espressione;
Lifetime delle variabili
Per quello che riguarda il tempo di vita e il campo di azione di una variabile, valgono le seguenti regole:
Una variabile locale cessa di esistere all’uscita del blocco in cui è stata dichiarata. Una variabile locale è visibile dal punto in cui viene dichiarata, fino alla fine del suo blocco.
ad esempio nel codice:
{int a=2, b=3;
{int x=a
a=b;
b=x;
}
System.out.println(a);
System.out.println(x);//dà errore :è fuori dal suo campo di azione
}
Costanti
La dichiarazione di costanti assume invece la seguente sintassi:
final int n=5;
final float prz=11.50;
final String messaggio=”ciao”;
L’uso del modificatore final è obbligatorio.
Il modificatore final, trasforma una variabile in una costante, visibile nel blocco della classe.
La tipica applicazione di test ‘ciao a tutti’ verrebbe codificata:
import java.io.*;
class ciao {
public static void main (String[] args) throws IOException {
final String MSG = “Ciao “;
InputStreamReader in=new InputStreamReader(System.in);
BufferedReader h= new BufferedReader(in);
String s;
s=h.readLine();
System.out.println(MSG+s);
}//fine main
}//fine class
Come è possibile riconoscere, Java mantiene la stessa sintassi del linguaggio C; in particolare è obbligatorio rispettare la regola del punto e virgola: cioè, ogni istruzione deve essere seguita (terminata) da un punto e virgola. Come nel caso del C, il linguaggio è case sensitive, ciò vuol dire che la variabile Pippo è considerata differente da una variabile dichiarata pippo.
Tipi di dati (primitivi)
boolean | 1bit | false/true |
byte | 1byte | da -128 a 127 |
short | 2byte | da -32768 a 32767 |
int | 4byte | da -231 a 231-1 |
long | 8byte | da -263 a 263-1 |
char | 2byte | da \u0000 (0) a \uFFFF (65535) |
float | 4byte | ±3.40282347·10+38 a ±1.40239846·10-45 |
double | 8byte | da±1.7976931348623157·10+308 a ±4.94065645841246544·10-324 |
Nonostante non vi sia il tipo primitivo string (ma solo la classe String) Java permette di avere letterali stringhe, che vengono racchiusi fra doppi apici, come “Ciao ” già visto nel programma precedente.
Espressioni
Variabili, letterali e costanti possono essere combinate, usando opportuni operatori e rispettando alcune regole, per formare espressioni analoghe alle espressioni algebriche. Gli operatori hanno differenti priorità, o precedenze, secondo le normali consuetudini dell’algebra ad es. in a*b-c la moltiplicazione viene effettuata prima dell’addizione. Le parentesi permettono di cambiare l’ordine di esecuzione delle operazioni. Dobbiamo come al solito ricordare che se in un’espressione sono coinvolti tipi di dati differenti, il risultato sarà dello stesso tipo del dato che che occupa maggior spazio in memoria. Ad es.
int a=3,c=7;
double b=2.50;
System.out.println(a+b);
System.out.println(c/a);
fornisce in uscita
5.5
2
questo perché in a+b il dato di maggior occupazione in memoria è double , il risultato in tal caso è di tipo double; mentre in c/a il dato di maggior occupazione in memoria è sempre int, il risultato in tal caso è di tipo int. Si raccomanda in tal caso la conversione di cast :
System.out.println((double)c/a);
per evitare la perdita dei decimali.
Operatori aritmetici, logici e relazionali
Gli operatori aritmetici servono appunto ad eseguire operazioni matematiche:
operatore | azione |
– | sottrazione |
+ | addizione |
* | moltiplicazione |
/ | divisione |
% | resto della divisione fra interi |
— | decremento |
++ | incremento |
Gli operatori relazionali, intervengono nelle espressioni logiche che implicano, prevalentemente, dati di tipo numerico.
Ovviamente restituiscono un dato booleano (true/false). Ad es.
class operatori {
public static void main (String[] args) {
int x,y;
boolean p,q;
x=10;
y=10;
p=(x==y);
q=(x!=y);
System.out.println(“p:”+p+”\t”+”q:”+q);
}//fine main
}//fine class
restituisce:
p:true q:false
dato che la condizione logica p è vera, q è falsa.
Gli operatori relazionali sono i seguenti:
operatore | azione |
> | maggiore |
>= | maggiore o uguale |
< | minore |
<= | minore o uguale |
== | uguale a |
!= | diverso da |
Uno degli errori più ricorrenti è quello di usare l’operatore di assegnamento ‘=’ al posto di ‘==’ nelle espressioni condizionali.
Gli operatori logici sono coinvolti nelle associazioni fra le relazioni
operatore | azione |
&& | and |
|| | or |
! | not |
Ad. esempio
class logici {
public static void main (String[] args) {
int x=12;
boolean dueCifre=((x>=10)&&(x<=100));
boolean multiploDi6=((x%6)==0);
boolean dispari=!((x%6)==0);
System.out.println(“dueCifre:”+dueCifre);
System.out.println(“multiploDi6:”+multiploDi6);
System.out.println(“dispari:”+dispari);
}//fine main
}//fine class
restituisce
dueCifre:true
multiploDi6:true
dispari:false
come ci si poteva aspettare.
Funzioni matematiche
Ricordiamo che l’eventuale uso delle funzioni matematiche, implica la restituzione da parte di alcune di queste di numeri in virgola mobile:
operatore | azione |
E | costante numero di Neper=2.717.. |
PI | costante pi greco=3.14.. |
abs(x) | valore assoluto di x |
max(x,y) | massimo fra due numeri |
min(x,y) | minimo fra due numeri |
sin(x),cos(x),tan(x) | funzioni trigonometriche |
asin(x),acos(x),atan(x) | funzioni trigonometriche inverse |
ceil(x) | il più piccolo intero maggiore o uguale a x |
floor(x) | il più grande intero minore o uguale a x |
rint(x) | arrotondamento di un double verso un intero |
round(x) | arrotondamento di un float verso un intero |
exp(x) | e elevato alla x |
pow(x,y) | x elevato alla y |
log(x) | logaritmo in base e di x |
sqrt(x) | radice quadrata di x |
toDegrees(x) | conversione da radianti a gradi |
toRadians | conversione da gradi a radianti |
random() | genera un random di tipo double tra 0.0 e 1.0 |
un esempio del loro uso:
import java.lang.Math;
class potenza {
public static void main (String[] args) {
int x=4,y=2;
double z;
z=Math.pow(x,y);
System.out.println(z);
}//fine main
}//fine class
Strutture di controllo
Le strutture di controllo, specificano l’ordine di esecuzione delle istruzioni di un programma. Così come per gli operatori le strutture di controllo Java possono essere considerate identiche a quelle già viste in linguaggio C.
if-else
L’if-else è la più comune istruzione di selezione. La sua sintassi può essere espressa nel modo seguente,
if(condizione){ Nel caso che la condizione logica sia falsa, non viene intrapresa alcuna azione e il flusso del programma riprende dall’istruzione successiva all’if. Le parentesi graffe sono obbligatorie se deve essere eseguita più di una istruzione (blocco di istruzioni) |
if(condizione){ Quindi, un’istruzione di selezione inizia con la parola chiave if, seguita da un’espressione logica (condizione) fra parentesi tonde (detta condizione dell’if), seguita a sua volta da un blocco di istruzioni e, opzionalmente dalla parola chiave else e da un’altro blocco di istruzioni. |
Un esempio di uso dell’if è:
if (x >= 0) System.out.println(“x e’ positivo”);
Se la condizione dell’ if è valutata a true viene eseguita l’istruzione successiva . Se è presente anche l’else:
if (x >= 0) System.out.príntln(“x e’ positivo”);
else System.out.println(“x e’ negativo”);
l’istruzione dopo l’else viene eseguita se la condizione vale false. Si dice che l’istruzione che appare subito dopo la condizione è il ramo if, mentre quella che appare dopo l’else è il ramo else.
Rimane valido anche il costrutto if-else-if qui esemplificato in un programma che determina il maggiore di 4 caratteri:
import java.io.*;
class ifElseIf {
public static void main (String[] args) throws IOException {
InputStreamReader input=new InputStreamReader(System.in);
BufferedReader h= new BufferedReader(input);
int a, b, c, d, max;
a = Integer.parseInt(h.readLine().trim());
b = Integer.parseInt(h.readLine().trim());
c = Integer.parseInt(h.readLine().trim());
d = Integer.parseInt(h.readLine().trim());
if (a>b && a>c && a>d) max = a;
else if (b>a && b>c && b>d) max = b;
else if (c>a && c>b && c>d) max = c;
else max = d;
System.out.println(max);
}//fine main
} //fine class
switch
Lo stesso schema di flusso dell’istruzione if-else-if viene seguita dal costrutto switch
switch (espressione) {
case valore_a: {istruzioni_a;} break;
case valore_b: {istruzioni_b;} break;
…
case valore_n: {istruzioni_n;} break;
default : {istruzioni_n;} break;
} //fine switch
L’espressione che segue la parola chiave switch (che può anche essere una semplice variabile) viene valutata e il valore ottenuto (che deve essere di tipo byte, short, int , long o char) è confrontato con tutti i letterali , , … che seguono la parola chiave case. Se sono uguali, viene eseguita la sequenza di istruzioni corrispondente; se dopo tali istruzioni vi è la parola chiave break, il controllo passa all’istruzione successiva allo switch. Se nessun letterale è uguale al valore dell’espressione viene eseguita la sequenza di istruzioni che segue la parola chiave default .
while
Capita spesso di voler ripetere più volte le stesse istruzioni. Nei primi linguaggi di programmazione vi erano le istruzioni di salto (goto, jump); nei linguaggi di programmazione moderni le istruzioni di salto sono state sostituite dalle strutture di controllo iterative. Un ciclo while (detto anche ciclo con ripetizione precondizionale) avviene nel modo seguente: la condizione viene valutata e, se il suo valore è true, il corpo viene eseguito; poi la condizione viene valutata nuovamente e il corpo eventualmente eseguito di nuovo, e così via, finché la valutazione della condizione dà il risultato false; a questo punto viene eseguita l’istruzione successiva.
il seguente programma utilizza un ciclo while per generare casualmente un numero compreso fra 1 e 10 (inclusi), per poi stampare a video il numero. Il ciclo continuerà a funzionare finchè la condizione logica del while è vera (true). |
Il ciclo viene eseguito almeno una volta se la variabile testata nella condizione logica è minore di 8, altrimenti, non viene eseguito nemmeno una volta.
class cicloWhile {
public static void main (String[] args) {
int x=0;
while(x<8){
x=(1 + (int) (Math.random() * 10));
System.out.println(x); }
}//fine main
}//fine class
do-while
Detto anche ciclo con ripetizione condizionale :
do{
istruzioni;
}while(condizione);
la sequenza di istruzioni compresa tra il do e il while viene ripetuta tante volte mentre la condizione scritta dopo il while si mantiene vera; in altre parole la ripetizione termina quando la condizione diventa falsa. A differenza dei cicli for e while che verificano la condizione all’inizio del ciclo, il do-while la verifica alla fine, con la conseguenza che il do-while viene eseguito almeno sempre almeno una volta. |
class cicloDoWhile{
public static void main (String[] args) {
int x;
do{
//genera un numero casuale fra 1 e 10 compresi
x=(1 + (int) (Math.random() * 10));
System.out.println(x);
}while(x<8);
}//fine main
}//fine class
in questo caso la variabile può anche non essere inizializzata perché entra nel ciclo almeno una volta e lì viene impostata.
for
Il costrutto for viene usato per eseguire un numero finito di cicli, la sua sintassi è:
for (inizializzazione; condizione;incremento){
istruzioni
}
Dopo la parola chiave for, vi sono tre elementi fra parentesi tonde (detti, da sinistra a destra: l’inizializzazione, la condizione di uscita e l’istruzione di incremento) che formano l’intestazione del for e un’istruzione (detta corpo del for), che può in generale essere composta (in tal caso è obbligatorio usare le parentesi graffe). Il significato dell’istruzione for è il seguente: l’istruzione di inizializzazione assegna un valore iniziale a una variabile, detta variabile di controllo; il corpo del for viene eseguito ripetutamente fìnché vale la condizione di uscita; e a ogni iterazione la variabile di controllo viene incrementata come indicato dall’istruzione di incremento. Ad esempio, la seguente istruzione stampa tutti i numeri da 1 a 10: |
for(i=1;i<=10; i++) System.out.println(i);
Come si nota in questo caso, non vengono usate le parentesi graffe, dato che ad ogni ciclo viene usata una sola istruzione.
{/gspeech}
Commento all'articolo