MokaByte 67 - 8bre 2002 
Corso introduttivo su Java
VII parte: i metodi
di
Andrea Gini
Durante la realizzazione di un programma complesso si arriva al momento in cui il codice diventa cosė complesso da richiedere una forma efficiente di organizzazione. In altri casi, capita che in un programma si ripresenti pių volte il medesimo problema, con variazioni minime. in casi come questi, Java offre il costrutto dei metodi, che andremo ad affrontare in questo articolo

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

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it