Dichiarazione di metodo (ripasso)

Sintassi:
 
<modif> <tipo> <nome> (<par formali>){
    <corpo>
}

Esempio (dalla Classe java.lang.Math)

public static double sqrt (double a){
    double b;
    <si calcola la radice quadrata di a in b>
    return b;
}
dove:
  • <modif>: Modificatori. Descrivono proprietà del metodo come visibilità (da chi può essere usato), modificabilità, appartenenza ad una classe o alle istanze.  Si riassumeranno in seguito: per ora useremo solo static (metodi di classe).
  • <tipo>: Il tipo del metodo. E' il tipo del valore restituito dal metodo, oppure void.
  • <nome>: Il nome del metodo. E' un identificatore qualunque, che individua il metodo all'interno della classe.
  • <par formali>: I parametri formali. Una lista (anche vuota) di coppie <tipo> <parametro> separate da virgole, dove <tipo> è un tipo di dato e <parametro> è una variabile (identificatore)
  • <corpo>: Il corpo del metodo. Un blocco che contiene istruzioni fra cui una o più istruzioni return (secondo le regole che vedremo dopo).



Firma di un metodo e overloading

Java consente di chiamare più metodi con lo stesso nome (overloading, sovraccarico),  purché abbiano una diversa firma (signature).
La firma di un metodo
 
<modif> <tipo> <nome> (<tipo1> <p1>,...,<tipon> <pn>){
...
}

è la sequenza (<nome>, <tipo1>, ..., <tipon>).
Attenzione: la firma non comprende né il tipo del metodo, né i nomi dei parametri formali.




Esempio di overloading

Nella classe java.lang.Math ci sono diversi metodi min che restituiscono il minore tra due numeri:
  • public static int min (int a, int b)
  • public static double min (double a, double b)
Si noti che
  • hanno come firma (min, int, int) e (min, double, double)
  • è naturale chiamarli con lo stesso nome
  • anche se ogni int è un double, non possiamo avere solo il secondo, perchè il minore di due interi sarebbe un numero decimale!



Significato di invocazione di metodo

Quando l'interprete incontra una chiamata tipo nome(exp1, ..., expn) esegue i seguenti passi:
  • valuta le espressioni exp1,...,expn, ottenendo dei valori val1,..., valn, e ne determina i tipi (siano questi tipo1,...,tipon)
  • cerca una dichiarazione di un metodo con firma

  •   nome(tipo1,...,tipon)
Supponiamo che trovi
<tipo> nome(tipo1 par1, ... , tipon parn){
  <istruzioni>
}
  • (Passaggio dei parametri)  vengono dichiarate le variabili par1,...,parn, e vengono inizializzate con i parametri attuali (nel nostro caso i valori val1,...,valn)

  •  
  • vengono eseguite le <istruzioni> fino al primo return, oppure fino alla fine se non c'è nessun return
  • se viene eseguito un return seguito da una espressione, quest'ultima viene valutata ed il suo valore viene restituito al metodo chiamante



Passaggio di parametri

Il passaggio dei parametri è il meccanismo che lega i parametri attuali di una specifica invocazione di un metodo ai parametri formali della corrispondente dichiarazione. In Java, se abbiamo la dichiarazione
 
<tipo> nome (<tipo1> par1, ... , <tipon> parn){
    <istruzioni>
}

e la chiamata

    nome(exp1, ..., expn)

l'effetto del passaggio di parametri è equivalente all'esecuzione del seguente blocco di istruzioni:
 
 

{
<tipo1> par1 = exp1;
   ... ;
<tipon> parn = expn;
<istruzioni>
}

Quindi:
 

numero e tipi dei parametri attuali devono coincidere con numero e tipi dei parametri formali

Questo è garantito dalla scelta del metodo sulla base della firma; se non c'è un metodo con la firma richiesta dall'invocazione, il compilatore segnalerà un errore (cannot resolve symbol...).





Passaggio di parametri per valore

Se un parametro attuale è di tipo elementare, le modifiche sul corrispondente parametro formale non si ripercuotono all'esterno del metodo: si tratta di passaggio di parametri per valore.
 
class Parametri1 {

  public static void main (String [ ] args) {
   int number = 30;
    nonAnnulla(number);
    System.out.println("number vale: " + number);
          // Stampa 30
  }
  static void nonAnnulla(int i) {
     i = 0;
  }
}

Vediamo perché:
 
Istruzione eseguita
Stato
int number = 30;      // assegnamento
number 
30
 
nonAnnulla(number); 
       // passaggio parametro: equivale a
       // int i = number;
number 
30
30
 
i = 0;     // assegnamento a variabile locale
number 
30
0
 
System.out.println ("number vale: "+number)
number 
30
 


 




Passaggio di parametri per riferimento

Se un parametro attuale è un riferimento ad un oggetto, allora le modifiche sul corrispondente parametro formale  si ripercuotono all'esterno del metodo: si tratta di passaggio di parametri per riferimento
 
class Parametri2 {

  public static void main (String[] args) {
    int[] arNumber = new int [5];
    arNumber[0] = 30;
    annulla(arNumber);
    System.out.println("arNumber[0] vale:"+arNumber[0]);
          // Stampa 0
  }

  static void annulla(int[] arNum) {
     arNum[0] = 0;
  }
}

Vediamo perché:
 
Istruzione eseguita
Stato
int arNumber[] = new int[5]
arNumber[0] = 30
annulla(arNumber);
       // passaggio parametro: equivale a
       // int [] arNum = arNumber;
arNum[0] = 0
System.out.println
    ("arNumber[0] vale:" + arNumber[0])




Parametri del metodo main

Visto che main è un metodo, cosa sono i parametri formali nella sua dichiarazione?
 
public static void main (String [] args) {
        <istruzioni>
    }

Il metodo main ha un solo parametro formale, args, di tipo array di String.
Il main di una classe NomeClasse viene invocato col comando
    > java NomeClasse
Per passare degli argomenti al main, si può invocare con
    > java NomeClasse  argomento1  ... argomentok
In questo caso, durante l'esecuzione del main avremo

args[0] = argomento1
...
args[k-1] = argomentok
args.length = k




Parametri di main: esempio


Esempio: un programma che stampa il numero di caratteri dell'argomento su cui viene invocato.
 
public class Count{

  public static void main (String [ ] args){ 
       if (args.length >= 1)
            countChars(args[0]);
       else
            System.err.println("Usage: Count <string>");
    }

   static void countChars(String name){ 
        System.out.println(name.length());
    }
}

Invocando:         java Count pippo
Viene stampato:    5



Nota per i conoscitori del C:

Si noti che a differenza dell'array argv della procedura main di un programma C, args[0] contiene il primo argomento, e non il nome del programma. Questo perché il nome del programma coincide con il nome della classe. Inoltre non esiste argc, perché è ricavabile come args.length.




Il comando return

Ricordiamo che il tipo di un metodo è il tipo del dato restituito dal metodo, oppure void.

Se il tipo è diverso da void, il metodo si chiama tipizzato e deve restituire un valore del tipo indicato: conseguentemente per ogni possibile ramo di esecuzione del corpo deve comparire un comando return  seguito da un'espressione del tipo del metodo.
    
 

static String happy(){
 if (true) 
    return "I'm happy!!";
> javac EsempioReturn.java
EsempioReturn.java:9: missing return statement
    static String Happy(){
                         ^
static String happy(){
 if (true) 
    return "I'm happy!!";
 else return "I'm not happy!";
 
Se il tipo è void, il metodo si chiama  non tipizzato e non restituisce niente. I metodi non tipizzati normalmente non contengono alcun comando return. Possono comunque contenere dei comandi return, purché non siano seguiti da espressioni. In tal caso l'esecuzione del metodo termina quando si incontra un return oppure quando si sono eseguite tutte le istruzioni.



return e variabili riferimento

Il tipo di un metodo può essere una classe. In questo caso non viene restituito un valore, ma il riferimento ad un oggetto.

E' possibile che venga restituito null: se una variabile di tipo riferimento vale nullsignifica che non riferisce alcun oggetto.
 
 

public class Ora  {
   private int ore;
   public Ora(int h) {ore = h;}
   public void stampa(){
        System.out.println("Sono le " + ore);
   }
}

 
public class TestOra  {
   public static void main(String args[]) {
   ConsoleReader console = new ConsoleReader(System.in);
   System.out.print("Inserisci l'ora: ");
   int h = console.readInt();
   Ora o = crea(h);
   if (o!= null)
      o.stampa();
   else
      System.out.print("Ora errata");
      //se o==null, la chiamata o.stampa() fallirebbe
   }
   public static Ora crea(int ora){
    if (0 <= ora && ora< 24)
        return new Ora(ora);
    else
        return null;
  }
}