Metodi del programma

Sono metodi associati al programma, invece che a una classe.

Si fanno metodi di questo tipo quando il metodo è utile solo in un programma.


Il problema

Consideriamo questo problema: stampare una serie di espressioni, separate da una linea di asterischi.

class Linea {
  public static void main(String args[]) {
    int x=12;
    int y=42;

    System.out.println("**********************************");

    System.out.println(x+2);

    System.out.println("**********************************");

    System.out.println(2*x+1);

    System.out.println("**********************************");

    System.out.println(y/2);

    System.out.println("**********************************");

    System.out.println(x+y/2);

    System.out.println("**********************************");
  }
}

Questa istruzione si ripete sempre uguale.


Un metodo?

Potrei definire un metodo che fa la stampa, e poi invocare il metodo quando serve.

Però il metodo va messo in una classe.

Devo creare una classe solo per metterci dentro il metodo:

class Inutile {
  void stampaLinea() {
    System.out.println("**********************************");
  }
}

Il programma

Devo creare un oggetto solo per poter invocare il metodo:

class ProcInutile {
  public static void main(String args[]) {
    int x=12;
    int y=42;

    Inutile i;
    i=new Inutile();

    i.stampaLinea();

    System.out.println(x+2);

    i.stampaLinea();

    System.out.println(2*x+1);

    i.stampaLinea();

    System.out.println(y/2);

    i.stampaLinea();

    System.out.println(x+y/2);

    i.stampaLinea();
  }
}

Soluzione alternativa: definire il metodo dentro il programma.


Metodi del programma

Posso scrivere metodi che sono associati al programma e non a una classe.

class Proc {
  static void stampaLinea() {
    System.out.println("**********************************");
  }

  public static void main(String args[]) {
    int x=12;
    int y=42;

    stampaLinea();

    System.out.println(x+2);

    stampaLinea();

    System.out.println(2*x+1);

    stampaLinea();

    System.out.println(y/2);

    stampaLinea();

    System.out.println(x+y/2);

    stampaLinea();
  }
}

Ci sono delle somiglianze con i metodi delle classi, e delle differenze.


Somiglianze

La dichiarazione è quasi uguale, ma si mette static davanti.

  static TipoValoreRitorno nomeMetodo(argomenti) {
    istruzioni
  }

Per invocare il metodo nel programma (=far eseguire le istruzioni), si mette il nome del metodo con gli argomenti fra parentesi.

    stampaLinea();

    System.out.println(x+y/2);

    stampaLinea();

Differenze

Non c'è un oggetto di invocazione.

Quindi:

Sono tutte e due conseguenze del fatto che questi metodi non hanno un oggetto di invocazione.


Esercizio

Realizzare un metodo che stampa la stringa "Positivo" oppure "Negativo" oppure "Zero" a seconda del segno di un numero passato come argomento.

Realizzare anche un programma di prova.


Intestazione

Il metodo ha:

valore di ritorno
nessuno, ossia void
nome
per esempio, stampaSegno
argomenti
un intero

L'intestazione del metodo è di conseguenza:

  static void stampaSegno(int n)

Corpo del metodo

Devo stampare una stringa diversa a seconda se il numero è positivo, negativo, oppure zero.

Un modo possibile (ce ne sono altri):

  static void stampaSegno(int n) {
    if(n==0)
      System.out.println("Zero");
    else if(n>0)
      System.out.println("Positivo");
    else
      System.out.println("Negativo");
  }

Un programma di prova

Questo è un possibile programma che usa il metodo:

class Segno {
  static void stampaSegno(int n) {
    if(n==0)
      System.out.println("Zero");
    else if(n>0)
      System.out.println("Positivo");
    else
      System.out.println("Negativo");
  }

  public static void main(String args[]) {
    int a=2;

    stampaSegno(a);
    stampaSegno(-9);
    stampaSegno(2*a-4);
  }
}

Posso invocare il metodo passando il risultato di una qualsiasi espressione, per esempio:


Esercizio

Stampare questa figura:

*
**
***
****
*****
******
*******
********
*********
**********

Definire un metodo che stampa una linea di n
(per stampare un singolo asterisco,
fare System.out.print("*");)


Il metodo

Il metodo ha:

tipo valore di ritorno
void
nome
per esempio, asterischi
argomenti
un intero

Per l'intestazione, è come un metodo di una classe:

  static void asterischi(int n) {
    ...
  }

L'unica differenza, nell'intestazione, è la parola static


Corpo del metodo

In n viene messo il numero di asterischi da stampare.

Dopo aver stampato, vado a capo.

  static void asterischi(int n) {
    int a;

    for(a=0; a<n; a++)
      System.out.print("*");

    System.out.println();
  }

Soluzione completa

Se invoco il metodo come asterischi(x), viene stampata una linea di n asterischi.

Questo va fatto per x=1, 2, 3, ..., 10

class Aster {
  static void asterischi(int n) {
    int a;

    for(a=0; a<n; a++)
      System.out.print("*");

    System.out.println();
  }

  public static void main(String args[]) {
    int x;

    for(x=1; x<=10; x++)
      asterischi(x);
  }
}

Fasi di esecuzione di un metodo

Quando si invoca un metodo void:

  1. si valutano i parametri attuali
  2. si crea la zona di memoria per il metodo, in cui si sono parametri formali e parametri attuali
  3. si copiano i valori dei parametri attuali in quelli formali
  4. si esegue il corpo del metodo

Nel corpo del metodo le variabili del programma non si possono usare.

I parametri attuali sono valori, che possono anche risultare dalla valutazione di espressioni:

  asterischi(12);
  asterischi(a*2-b);
  asterischi(2+(int) p.distance(q));

Le variabili locali

Le variabili del metodo e del programma possono anche avere lo stesso nome.

Sono comunque due variabili diverse.

class Stesso {
  static void asterischi(int n) {
    int x;

    for(x=0; x<n; x++)
      System.out.print("*");
    System.out.println();
  }

  public static void main(String args[]) {
    int x;

    for(x=1; x<=10; x++)
      asterischi(x);
  }
}

Invocazione di metodo, in memoria

Prima di invocare il metodo:

Le variabili del metodo non esistono


Zona di memoria associata al metodo

Ogni volta che viene invocato un metodo, si crea una zona di memoria per tutte le sue variabili (variabili locali e parametri formali).

Prima di eseguire le istruzioni, c'è la copiatura dei parametri attuali in quelli formali.


Memoria, quando si esegue il corpo

Quando si eseguono le istruzioni, nei parametri attuali sono stati messi i valori dei parametri attuali.


Attenzione ai nomi!

Il valore di x va in n: questo dipende dell'invocazione!

invocazione:
asterischi(x)
definizione:
static ... (int n)

Quindi, il valore di x viene copiato dentro n

È come se venisse fatto n=x;


Durante l'esecuzione

Ogni metodo può accedere solo alle sue variabili:


Fine del metodo

La zona di memoria di asterischi viene rimossa, e si torna allo stato originario.

Attenzione! Quando si invoca nuovamente il metodo, le sue variabili vengono create di nuovo.

Da una invocazione all'altra, i valori delle variabili locali vengono persi.


Modifica delle variabili

Cosa stampa questo programma?

class Trab1 {
  static void prova(int x) {
    x=0;
  }

  public static void main(String args[]) {
    int x;

    x=12;

    prova(x);

    System.out.println(x);
  }
}

Risposta

Si deve fare la figura.

Durante l'esecuzione del programma, esiste solo la zona di memoria del programma:


Invocazione del metodo

Viene creata la zona di memoria del metodo:


Parametri

Il valore di x del main viene copiato nella x di prova

Solo ora si esegue il corpo del metodo.


Modifica

Se x=0 compare nel metodo prova, viene messo 0 nella x di prova

Notare: la x di main non può venire modificata: è inaccessibile dal metodo prova


Ritorno

Viene cancellata la zona di prova

Viene quindi stampato 12


Attenzione all'ordine

Cosa stampa questo programma?

class Ordine {
  static void prova(int x, int y) {
    System.out.println(x);
  }

  public static void main(String args[]) {
    int x=10;
    int y=20;

    prova(y,x);
  }
}

Nomi delle variabili

I nomi delle variabili sono irrilevanti.

Se invece del nome x uso il nome abcd il programma fa lo stesso.

Il passaggio avviene sulla base della posizione:

Nella x di prova viene messo il valore della y di main

Viene stampato 20


Suggerimento

Se non è chiaro:

usate nomi di variabili diverse, se possibile, per evitare confusione

Se invece il meccanismo è chiaro, potete anche usare gli stessi nomi.


Quindi, x=0; nel metodo non ha effetto?

Altro suggerimento:

non modificate i parametri formali, per evitare confusione

I parametri attuali

Il programma trasmette dei valori al metodo (es. 3, 12, -2, ecc.)

Il metodo li riceve: per poterli usare, vengono messi in alcune variabili


Esempio

  int a=12, b=-2;

  nomemetodo(12*2, a-b/2, a+b);

Vengono valutate le tre espressioni.

I valori calcolati sono 24, 13, 10

L'invocazione del metodo equivale a:

  nomemetodo(24, 13, 10);

La variabile non può venire modificata!

Per lo stesso motivo:

  int x=12;

  prova(x);

È equivalente a:

  int x=12;

  prova(12);

Perchè il valore di x dovrebbe cambiare.


Concetto fondamentale

I parametri attuali sono valori.

Quando si invoca il metodo, le espressioni fra parentesi vengono valutate.

Il fatto che il valore 12 derivi dal fatto che in x c'era questo valore viene dimenticato:

prova(x) diventa come prova(12), che non ha nessuna relazione con x

i parametri attuali sono valori: il programma "trasmette" dei valori ai metodi; la provenienza dei valori (costanti, valori di variabili, valori di espressioni) è irrilevante

Quando faccio prova(x) è il valore contenuto in x che viene inviato. Il fatto che provenga da x non ha importanza.


Valore di ritorno

La sequenza effettiva è:

  1. si calcolano i valori dei parametri attuali
  2. si crea la zona di memoria del metodo
  3. si copiano i parametri attuali nei parametri formali
  4. si esegue il corpo del metodo
  5. il valore di ritorno viene usato come se fosse scritto al posto della invocazione

I passi vengono fatti in questa sequenza.


Esercizio

Cosa stampa questo programma?

class Ritorno {
  static int prova(int x) {
    x=x+10;

    return x+2;
  }

  public static void main(String args[]) {
    int x=12;

    x=prova(x);

    System.out.println(x);
  }
}

Risposta

Viene stampato 24

Perchè?


Motivazione

Inizio del metodo:


Alla fine del metodo

Viene ritornato il valore di x+2

Per x si intende la x di prova, che è 22


Valore di ritorno

Dato che il metodo era stato invocato con x=prova(x), e il valore di ritorno è 24, questo equivale a x=24

Il valore di ritorno viene messo in x


Vero e falso

falso:
si stampa 24 perchè il metodo prova aumenta di 10 il valore della variabile x e poi ancora di due
vero:
il valore di ritorno del metodo prova è 24, e questo numero viene messo in x

Se non fate modifiche ai parametri formali, non avrete questi problemi:

// programma piu' chiaro

class Chiaro {
  static int prova(int x) {
    int altronome;

    altronome=x+10;

    return altronome+2;
  }

  public static void main(String args[]) {
    int x=12;

    x=prova(x);

    System.out.println(x);
  }
}

Spezzare gli assegnamenti

  x=prova(x);

Se non vi risulta chiaro, usate una variabile in più:

  int y;
  y=prova(x);
  x=y;

Da questo si capisce la sequenza: prima invocazione, poi assegnamento.


Esercizio sui metodi

Date due variabili intere a e b, stampare i valori delle espressioni a-b, a-b*b, -a/b, a/b

Stampare solo i valori di queste espressioni che risultano positivi (maggiori o uguali a zero).


Soluzione senza metodo

Valuto ogni espressione e vedo se è positiva:

class SenzaMetodo {
  public static void main(String args[]) {
    int a=12, b=32;

    if(a-b >= 0)
      System.out.println(a-b);
    else
      System.out.println("Espressione negativa");

    if(a-b*b >= 0)
      System.out.println(a-b*b);
    else
      System.out.println("Espressione negativa");

    if(-a/b >= 0)
      System.out.println(-a/b);
    else
      System.out.println("Espressione negativa");

    if(a/b >= 0)
      System.out.println(a/b);
    else
      System.out.println("Espressione negativa");
  }
}

Farlo usando un metodo.


Quale metodo?

Quale è l'operazione che si ripete?

Stampare un valore se positivo.

``calcolare l'espressione'' non è l'operazione che si ripete, dato che ogni volta si tratta di una operazione diversa.

Si può riscrivere il programma:

class Ripetizione {
  public static void main(String args[]) {
    int a=12, b=32;
    int f;

    f=a-b;
    if(f >= 0)
      System.out.println(f);
    else
      System.out.println("Espressione negativa");

    f=a-b*b;
    if(f >= 0)
      System.out.println(f);
    else
      System.out.println("Espressione negativa");

    f=-a/b;
    if(f >= 0)
      System.out.println(f);
    else
      System.out.println("Espressione negativa");

    f=a/b;
    if(f >= 0)
      System.out.println(f);
    else
      System.out.println("Espressione negativa");
  }
}

È chiaro che la valutazione di una espressione va fatta nel programma, dato che sono quattro espressioni diverse.

Però la verifica se positivo e la stampa si ripetono sempre uguali.


Il programma

Il metodo prende un valore intero, e non restituisce nulla.

class ConMetodo {
  static void stampaSePositivo(int val) {
    if(val>=0)
      System.out.println(val);
    else
      System.out.println("Espressione negativa");
  }

  public static void main(String args[]) {
    int a=12, b=32;

    stampaSePositivo(a-b);
    stampaSePositivo(a-b*b);
    stampaSePositivo(-a/b);
    stampaSePositivo(a/b);
  }
}

Cosa succede quando si invoca stampaSePositivo(a-b*b)?

Equivale a fare: val=a-b*b e poi eseguire il corpo del metodo.

Si intende che dove val è la variabile del metodo mentre a e b sono variabili del programma.


Esercizio

Scrivere un metodo che calcola il fattoriale di un numero intero.

Scrivere poi il programma che stampa i fattoriali dei valori da 1 a 4, il fattoriale di 6 e poi verifica se il fattoriale di 5 è maggiore di 50 oppure no.


Il metodo per il fattoriale

Ha valore di ritorno intero.

Ha come argomento un intero.

  static int fattoriale(int n)

Corpo del metodo

Calcolo il fattoriale del numero che sta in n,
e ritorno il valore calcolato.

  static int fattoriale(int n) {
    int a;
    int f;

    f=1;
    for(a=1; a<=n; a++)
      f=f*a;

    return f;
  }

Programma

Basta a questo punto invocare il metodo tutte le volte che serve.

  public static void main(String args[]) {
    int i;

    for(i=1; i<=4; i++)
      System.out.println(fattoriale(i));

    System.out.println(fattoriale(6));

    if(fattoriale(5)>50)
      System.out.println("Il fattoriale di 5 e' maggiore di 50");
    else
      System.out.println("Il fattoriale di 5 e' minore di 50");
  }

Provare senza metodo: andava ripetuto lo stesso codice almeno tre volte.


Altra versione

Anche in questo modo funziona:

  static int fattoriale(int n) {
    int f=1;

    for(; n>=1; n--)
      f=f*n;

    return f;
  }

Molto meno chiaro.


Altro esercizio

Scrivere un metodo che calcola il valore assoluto di un numero intero.

Scrivere un programma che legge da tastiera un intero, e stampa la radice del valore assoluto.


Intestazione

Ha come parametro un intero, e ritorna un intero.

  static int valoreAssoluto(int)

Corpo del metodo

Se il numero è positivo, ritorno il suo valore.

Se è negativo, ritorno l'opposto del suo valore.

  static int valoreAssoluto(int n) {
    if(n>=0)
      return n;
    else
      return -n;
  }

Programma

programma di prova
serve per verificare se il metodo funziona
programma vero
quello in cui si usa il metodo

Nel programma di prova, è bene fare più invocazioni, per verificare se il metodo funziona.

Nel programma vero potrebbe anche esserci solo una invocazione.


Esempio di programma di prova

class ValAssoluto {
  static int valoreAssoluto(int n) {
    if(n>=0)
      return n;
    else
      return -n;
  }

  public static void main(String args[]) {
    int x;

    for(x=-10; x<=10; x++) {
      System.out.println(valoreAssoluto(x));
    }
  }
}

Programma di uso

Il programma che voglio fare è quello che legge un intero da tastiera, fa la radice del valore assoluto, e stampa.

import javax.swing.*;

class Radice {
  static int valoreAssoluto(int n) {
    if(n>=0)
      return n;
    else
      return -n;
  }

  public static void main(String args[]) {
    int x;

    String s;
    s=JOptionPane.showInputDialog("Dammi un intero");

    x=Integer.parseInt(s);

    System.out.println(Math.sqrt(valoreAssoluto(x)));

    System.exit(0);
  }
}

Esercizio

Scrivere un metodo che riceve due interi e restituisce la somma dei loro fattoriali.

Usare il metodo fattoriale.

Si può invocare un metodo anche all'interno di un altro metodo.


Primo: il solito metodo fattoriale.

  static int fattoriale(int n) {
    int a;
    int f;

    f=1;
    for(a=1; a<=n; a++)
      f=f*a;

    return f;
  }

Secondo: l'intestazione del nuovo metodo

Prende due interi e torna un intero.

  static int sommaFatt(int, int) 

Terzo: corpo del metodo

Devo calcolare i fattoriali, e sommarli.

  static int sommaFatt(int x, int y) {
    int f1, f2;

    f1=fattoriale(x);
    f2=fattoriale(y);

    return f1+f2;
  }

Un programma completo

class SommaFatt {
  static int fattoriale(int n) {
    int a;
    int f;

    f=1;
    for(a=1; a<=n; a++)
      f=f*a;

    return f;
  }

  static int sommaFatt(int x, int y) {
    int f1, f2;

    f1=fattoriale(x);
    f2=fattoriale(y);

    return f1+f2;
  }

  public static void main(String args[]) {
    int i;

    System.out.println(sommaFatt(1,4)/2);
  }
}