Durante
la realizzazione di programmi complessi, capita che
si presenti più volte lo stesso problema, magari
con minime variazioni. Invece di replicare la soluzione,
riscrivendo più volte un medesimo frammento di
codice, magari con variazioni mimime, è preferibile
fermarsi un attimo, isolare il problema, trovare un
minimo comun denominatore ed infine creare un modulo
parametrizzato riutilizzabile. Tale modulo prende in
Java il nome di metodo, ed è la prima forma di
organizzazione del codice che studieremo.
Un
metodo è un frammento di codice che può
essere richiamato diverse volte all'interno dello stesso
programma. Tale chiamata si presenta come una sorta
di "estensione" del linguaggio, definita dallo
stesso utente. Nel dichiarare un metodo, possiamo avvalerci
di un gruppo di parametri; inoltre un metodo può
restituire un valore, in modo simile a quanto avviene
con le funizoni matematiche.
I
principali vantaggi derivanti dall'uso dei metodi sono
due:
- rendere
il codice più compatto (e di conseguenza più
leggibile)
- definire
porzioni di codice riutilizzabili in diversi punti
del programma principale
Prima
di vedere come sia possibile creare metodi in Java,
è importante chiarire le idee a chi fino ad ora
ha avuto esperienza con altri linguaggi di programmazione.
Metodi,
Procedure e Funzioni
Molti
altri linguaggi di programmazione, primo tra tutti il
Pascal, definiscono due costrutti: la Funzione e la
Procedura. La funzione è molto simile al metodo
com'è stato definito nel precedente paragrafo:
un frammento di codice parametrizzato, accessibile dal
resto del programma, che restituisce un valore. La procedura
è sempre un frammento di codice parametrizzato,
che al contrario non restituisce alcun valore. Nel linguaggio
C, antenato di Java, le procedure non esistono: al loro
posto è possibile dichiarare delle speciali funzioni
che restituiscono un valore vuoto (void). Il metodo
è un'evoluzione della funzione del C: il cambiamento
di nome è dovuto al fatto che Java è un
linguaggio orientato agli oggetti, e nel mondo della
programmazione ad oggetti i metodi, pur avendo tutte
le caratteristiche delle funzioni, hanno delle proprietà
aggiuntive che vedremo in seguito. Ciò non di
meno, in questi primi esempi vedremo un uso dei metodi
simile in tutto e per tutto a quello delle funzioni
del C o del Pascal.
Dichiarazione
di Metodo
La
struttura standard di un metodo senza parametri è
la seguente:
static
tipo nome() {
istruzione1;
istruzione2;
....
instruzioneN;
return
returnVal;
}
La
sequenza static tipo nome() viene detta firma del metodo
(dall'inglese signature): più avanti analizzeremo
quali varianti è possibile inserire nella firma
di un metodo: per il momento si noti l'uso della parola
chiave "static", il cui scopo diverrà
chiaro quando inizieremo a parlare di programmazione
orientata agli oggetti.
Come
tipo possiamo specificare uno qualunque dei tipi che
abbiamo visto fino ad ora (int, long, short, byte, char,
boolean, float e double), oppure il tipo speciale void,
che si usa quando il metodo non restituisce nessun valore.
Tutti i metodi con tipo diverso da void devono terminare
con l'istruzione 'return', seguita da un valore dello
stesso tipo presente nella firma del metodo.
Gli
esempi seguenti renderanno più chiara la sintassi
delle dichiarazioni di metodo: per ora si noti semplicemente
che la parte di codice fino ad ora definita semplicemente
come "main" altro non è che un metodo
speciale: si tratta infatti del metodo che viene chiamato
automaticamente dalla Java Virtual Machine al momento
dell'esecuzione del programma.
Visibilità
delle variabili: variabili locali
Una
cosa a cui occorre fare grande attenzione durante la
realizzazione di un metodo è la visibilità
delle variabili, quello che nei testi in inglese viene
normalmente definito "scope". Quando dichiariamo
una variabile all'interno di un metodo, essa risulta
visibile solamente all'interno del metodo stesso: per
questa ragione le variabili dichiarate all'interno di
un metodo vengono definite variabili locali. Vediamo
un esempio:
public
class Esempio1 {
static void metodo1() {
int a = 10;
System.out.println(a);
}
static void metodo2() {
a = a++; //ERRORE!
System.out.println(a);
}
}
Il
primo dei due metodi e' corretto: esso dichiara la variabile
intera a, la inizializza a 10 e la stampa su schermo.
Nel secondo metodo c'e' invece un errore: si cerca di
utilizzare la variabile a che in questo metodo non è
stata definita. Se correggiamo il secondo metodo nel
modo seguente:
static
void metodo2() {
int a = 10;
a++;
System.out.println(a);
}
otteniamo
un metodo corretto, che dichiara una nuova variabile
a (diversa, si tenga presente, dalla omonima variabile
a presente nel metodo1), la inizializza a 10, la incrementa
a 11 e la stampa.
A
volte vorremmo che una variabile risulti visibile da
più di un metodo. Esiste un modo per definire
variabili con questa proprieta? Ovviamente si, come
vedremo meglio nel prossimo paragrafo.
Variabili
globali
Una
variabile globale è una variabile definita al
di fuori di un metodo, in modo tale che risulti visibile
a tutti i metodi del programma. La sintassi generale
di una variabile globale è:
static
tipo nomeVariabile;
Si
noti di nuovo l'uso della parola "static",
il cui ruolo verrà spiegato in dettaglio più
avanti: per il resto si tratta di una normale dichiarazione
di variabile. Anche in questo caso è possibile
dichiarare variabili inizializzate:
static
tipo nomeVariabile = valoreIniziale;
in
questo caso la variabile viene inizializzata con il
valore iniziale quando il programma viene eseguito.
Pertanto è possibile riproporre una variazione
sul programma precedente che mostri l'uso di una variabile
globale:
public
class Esempio2 {
static int a = 10;
static void metodo1() {
System.out.println(a);
}
static void metodo2() {
a = a++;
System.out.println(a);
}
}
Cosa
succede quando all'interno di un metodo si dichiara
una variabile locale con lo stesso nome di una variabile
globale? Accade che, all'interno del metodo, la variabile
locale ha precedenza di accesso rispetto a quella globale,
che comunque mantiene inalterato il suo valore. Si osservi
un frammento di codice come il seguente:
public
class Esempio3 {
static int a = 10;
static void metodo1() {
System.out.println(a);
}
static void metodo2() {
int a = 11; // questa a e' DIVERSA
dalla a globale
System.out.println(a);
}
}
In
questo caso abbiamo una variabile globale di nome a,
che viene utilizzata all'interno del metodo1. Nel metodo2,
viene dichiarata una nuova variabile a, visibile solo
all'interno del metodo, che tuttavia non sovrascrive
la sua omonima globale. Dall'interno del metodo2 e'
ancora possibile accedere alla variabile globale a,
ricorrendo alla parola riservata "this". Vediamo
un esempio:
public
class Esempio4 {
static int a = 10;
static void metodo2() {
int a = 11; // questa variabile
a e' DIVERSA dalla a globale
System.out.println(a); // stampa
la variabile locale
System.out.println(this.a);
// stampa la variabile globale
}
}
Inutile
sottolineare che simili situazioni tendono a creare
confusione ed ambiguità: il programmatore deve
sempre cercare di adottare la soluzione più semplice,
ed evitare il più possibile di riutilizzare il
nome di una variabile globale in ambito locale.
Struttura
generale di un programma con metodi e variabili globali
E'
giunto il momento di riassumere le nozioni introdotte
fino ad ora, e di integrarle con quanto appreso in precedenza.
I metodi e le variabili globali vanno definiti all'interno
della definizione di classe, sullo stesso livello della
sezione main, che, come già detto, è un
metodo a sua volta. Possiamo allora estendere la struttura
base di un programma Java seguendo questo schema:
public
class NomeProgramma {
static tipo variabile1;
static tipo variabile2;
...
static tipo metodo1() {
istruzione1;
...
istruzioneN;
return returnVal;
}
static tipo metodo2() {
istruzione1;
...
istruzioneN;
return returnVal;
}
...
public
static void main(String argv[]) {
}
}
Un
dettaglio importante per chi ha una certa esperienza
del linguaggio Pascal: contrariamente a quest'ultimo,
Java non permette di dichiarare metodi all'interno di
altri metodi, ossia metodi nidificati.
Chiamata
di metodo
Fino
ad ora ci siamo occupati di definire la sintassi di
un metodo. Ma dopo averli definiti, come si possono
utilizzare? Un metodo è stato definito come costrutto
di programmazione riutilizzabile: con questo si intende
dire che è possibile richiamare un metodo in
qualunque altra parte del programma, cedendo in tal
modo il flusso di controllo ad esso. Esso diventa una
specie di "nuova istruzione", che ci permette
in un certo senso di personalizzare il linguaggio. Vediamo
un esempio
public
class ProgrammaConMetodo {
static int a = 0;
static void incrementa_e_stampa_a() {
a++;
System.out.println(a);
}
public static void main(String argv[]) {
for ( int i = 0 ; i < 10 ; i++ )
incrementa_e_stampa_a(); // chiamata di metodo
}
}
Si
noti che il flusso di controllo del programma prende
sempre il via dal metodo main: pertanto è da
quel punto che dobbiamo cercare di capire come funzionano
le cose.
Il
main contiene unicamente un ciclo for, che esegue per
dieci volte una chiamata al metodo incrementa_e_stampa_a().
Tale metodo incrementa il valore della variabile globale
a (posto a 0 all'inizio dell'esecuzione) e quindi ne
stampa il valore a schermo. Una volta finito il metodo,
il flusso di controllo ritorna al metodo main, che riprende
l'esecuzione da dove era stata interrotta prima della
chiamata: in questo caso esegue il passo successivo
del ciclo for.
Una
volta compilato ed eseguito questo programma, otterremo
il seguente output:
1
2
3
4
5
6
7
8
9
10
Si
noti che il programma precedente è del tutto
equivalente al successivo:
public
class ProgrammaSenzaMetodo {
public static void main(String argv[]) {
int a = 0;
for ( int i = 0 ; i < 10
; i++ ){
a++;
System.out.println(a);
}
}
}
In
un caso come questo si può dubitare del fatto
che la versione con chiamata a metodo sia più
leggibile di quella senza. D'altra parte, l'utilità
dei metodi aumenta a mano a mano che crescono le dimensioni
del programma principale: vedremo in seguito situazioni
in cui rimpiazzare 5-10 righe di codice con una chiamata
di metodo renda il la struttura del programma di gran
lunga più semplice e leggibile.
Conclusioni
Questo
mese abbiamo studiato i concetti di metodo e variabile
globale, e i problemi di visibilità connessi
al loro utilizzo. Il mese prossimo studieremo un uso
più avanzato dei metodi: uso di parametri, valori
di ritorno, ricorsione e faremo una breve panoramica
sui principi della programmazione strutturata
Risorse
Scarica
qui i sorgenti presentati
nell'articolo
|