funzioni

parti di codice riutilizzabili che svolgono compiti specifici

Partiamo subito da un caso specifico: poniamo di voler costruire un programma per calcolare la media o il massimo di un elenco di valori contenuti in una casella di testo, il risultato sarebbe un programma come quello qui sotto.


public class MediaMaggiorePlain extends Application{

    TextField valori = new TextField();
    Label risposta = new Label();
    
    @Override
    void start(Stage finestra){
        Label e_elenco = new Label("elenco: ");
        Label e_risposta = new Label("risposta: ");
        Button p_media = new Button("media");
        Button p_massimo = new Button("massimo");
        
        GridPane griglia = new GridPane();
        griglia.add(e_elenco, 0, 0);
        griglia.add(valori, 1, 0, 2, 1);
        griglia.add(p_media, 1, 1);
        griglia.add(p_massimo, 2, 1);
        griglia.add(e_risposta, 0, 2);
        griglia.add(risposta, 1, 2);
        
        Scene scena = new Scene(griglia);
        finestra.setScene(scena);
        finestra.setTitle("media & massimo");
        finestra.show();
        
        p_media.setOnAction(evento->calcolaMedia());
        p_massimo.setOnAction(evento->trovaMassimo());
    }
    
    void trovaMassimo(){
        String testi[] = valori.getText().split(" ");
        int numeri[] = new int[testi.length];
        int massimo;
        for (int i = 0; i < testi.length; i++) {
           numeri[i] = Integer.parseInt(testi[i]);
        }
        
        massimo = numeri[0];
        for (int i = 1; i < numeri.length; i++) {
            if(numeri[i]>massimo){
                massimo = numeri[i];
            }
        }
        risposta.setText(""+massimo);
    }
    
    void calcolaMedia(){
        String testi[] = valori.getText().split(" ");
        int numeri[] = new int[testi.length];
        int somma=0,media;
        for (int i = 0; i < testi.length; i++) {
            numeri[i] = Integer.parseInt(testi[i]);
        }
        
        for (int i = 0; i < numeri.length; i++) {
            somma += numeri[i];
        }
        media = somma/numeri.length;
        risposta.setText(""+media);
    }

    public static void main(String args[]){
        launch(args);
    }   
}

Una parte del codice è evidenziata per far vedere che ci sono due segmenti che si ripetono praticamente identici, questo fatto comporta una serie di problemi:

Fortunatamente in tutti i linguaggi attuali c'è una soluzione: scrivere una funzione (che nei linguaggi ad oggetti si chiama "metodo") che fa il lavoro comune. Questa funzione farà un lavoro ben determinato: leggerà i dati dalla casella valori e avrà come risultato un vettore di interi, in pratica:


int[] leggiDati(){
    String testi[] = valori.getText().split(" ");
    int numeri[] = new int[testi.length];
    for (int i = 0; i < testi.length; i++) {
        numeri[i] = Integer.parseInt(testi[i]);
    }
    return numeri;
}

Proviamo a vedere cosa c'è scritto nel pezzo di codice precedente. Dalla prima riga, che è la dichiarazione della funzione, si possono ricavare alcune informazioni: stiamo dichiarando una funzione (un tipo seguito da un nome e da una parentesi tonda), la funzione si chiama "leggiDati" (il nome prima della tonda aperta) e da come risultato un vetore di interi (il tipo prima del nome).

Tutto il resto dovrebbe risultare familiare tranne l'ultima riga: return è una istruzione che indica cosa va restituito a chi chiama (sarebbe a dire usa) la funzione.

Usare una funzione non è molto difficile, è esattamente come usare il metodo getText() di un Button, niente di più, quel che cambia è che possiamo direttamente scrivere il nome della funzione perché si trova nella nostra classe (non serve scrivere il nome di un oggetto seguito da ".")


void trovaMassimo(){
    int numeri[] = leggiDati();
    int massimo;
       
    massimo = numeri[0];
    for (int i = 1; i < numeri.length; i++) {
        if(numeri[i]>massimo){
            massimo = numeri[i];
        }
    }
    risposta.setText(""+massimo);
}

Passaggio di parametri

La funzione vista in precedenza calcola un risultato ma non ha nessun valore in input su cui lavorare: è possibile però fornirglieli utilizzando il passaggio di parametri.

Il passaggio dei parametri (che sono delle variabili) si fa indicando tra le parentesi tonde che seguono il nome della funzione una sequenza di dichiarazioni, proviamo a vedere ad esempio una funzione che calcola il totale di una spesa:


// definizione
double calcolaPrezzoTotale(int quantita, double prezzoUnitario) {
    double totale;

    totale = quantita * prezzoUnitario;

    return totale;
}

// uso (chiamata)
double t;
int q = 5;
double pu = 3.2;
t = calcolaPrezzoTotale(q , pu);

I parametri presenti nella definizione si chiamano parametri formali mentre quelli nella chiamata vengono detti parametri attuali.

In questo caso i parametri sono due: quantita di tipo int e prezzoUnitario di tipo double. L'ordine dei parametri è importante così come il tipo: quando chiamo la funzione i parametri forniti (parametri attuali) devono combaciare con quelli attesi (parametri formali) sia nel tipo che nell'ordine.

In Java il passaggio dei parametri (dalla funzione che chiama a quella che viene chiamata) avviene per valore: questo vuol dire che nel parametro formale viene copiato il valore passato come parametro attuale.


static void incrementaNumero(int i){
    i++;
}
public static void main(String args[]){
    int x=1;
    System.out.println(x); // stampa "1"
    incrementaNumero(x);
    System.out.println(x); // stampa sempre "1"
}

In caso i parametri siano di tipo riferimento quello che viene passato è una copia dell'indirizzo di memoria del parametro attuale: questo significa che se la funzione apporta delle modifiche al parametro formale queste queste saranno in pratica applicate anche al parametro attuale, sarà molto più chiaro con un esempio:


static void incrementaElementi(int m[]){
    for(int i=0; i<m.length; i++){
        m[i]++;
    }
}
public static void main(String args[]){
    int [] v = new int[2];
    v[0] = 4;
    v[1] = 2;
    int s;

    s = v[0]+v[1];
    System.out.println(s); // stampa "6"
    incrementaElementi(v);
    s = v[0]+v[1];
    System.out.println(s); // stampa "8"
}

Valore restituito

Ogni funzione può o meno restituire un valore a chi l'ha chiamata: il tipo del valore restituito viene dichiarato prima del nome della funzione e in caso che non ci sia un valore da restituire questo viene indicato con la parola chiave void al posto del tipo.

All'interno della funzione la parola chiave return serve per terminare la funzione e restituire a chi l'ha chiamata il valore dell'espressione che la segue (es: return a*7;). Ovviamente il valore restituito deve essere del tipo dichiarato prima del nome della funzione, in caso di una funzione void il return non è necessario (ma se si vuole usarlo non deve essere eseguito da nessuna espressione).

Una funzione (o metodo) è un blocco di codice che può ricevere dei parametri in ingresso ed eventualmente restituire un singolo valore in uscita.

Adesso possiamo riassumere: una dichiarazione di funzione è un tipo (oppure void) seguito da un nome (stesse regole dei nomi di variabili) e da una lista di parametri tra parentesi tonde (anche se non ce ne è nessuno va bene) e un blocco di codice racchiuso tra graffe.

Come funziona

Usiamo l'esempio qui sotto per definire con precisione come funzionano le cose: il programma qui sotto inizierà la sua esecuzione dalla fuzione chiamata main (alla riga 7).

La riga uno contiene la dichiarazione della funzione: il suo nome è somma, restituirà un numero intero e ha due parametri formali: n e q che assumeranno un valore quando la funzione verrà chiamata (cioè utilizzata). La funzione potrebbe non ritornare nessun valore e in quel caso il tipo ritornato sarebbe void.

Le righe 2 e 3 servono a calcolare la somma mentre la 4 è quella che dice che la funzione deve terminare e restituire a chi l'ha chiamata (ad esempio alla riga 9) il valore contenuto nella variabile s. return è seguita da una espressione (anche una semplice variabile è una espressione) che deve essere dello stesso tipo ritornato dalla funzione (nel caso di funzioni void non avrà alcuna espressione), quando si esegue un return la funzione termina.

Il programma inizia la sua esecuzione alla riga 7, la 8 è una semplice dichiarazione di variabile mentre la 9 è quella che ci interessa: quando il programma arriva qui sospende la funzione main e passa a somma copiando i parametri attuali "3" e "9" in quelli formali "n" e "q" della funzione. A questo punto viene eseguita la riga 3, s = "3 + 9" e poi alla 4 la funzione termina restituendo 12.

Quindi l'esecuzione torna alla riga 10 dove avevamo sospeso, il valore restituito dalla funzione somma viene copiato nella variabile a che quindi a questo punto conterrà 12.

avvia programma
pausa

funzioni e variabili

codice

 1int somma(int n, int q){
 2  int s;
 3  s = n + q;
 4  return s;
 5}
 6  
 7void main()
 8  int a;
 9  int b;
10  a = somma(3, 9);
11  b = a*2;
12}

Continua esempio

Tornando all'esempio iniziale potrebbe succedere che l'elenco dei numeri si trovi in una casella oppure in un'altra, in questo caso la nostra funzione leggiDati() non va più bene perché legge sempre dalla stessa casella: potrebbe far comodo modificare la funzione in modo che prenda come input un testo e restituisca un vettore di numeri (esattamente come prima). Chiamiamo questa nuova funzione daStringaAVettore.


int[] daStringaAVettore(String testo){ 
    String testi[] = testo.split(" ");
    int numeri[] = new int[testi.length];
    for (int i = 0; i < testi.length; i++) {
        numeri[i] = Integer.parseInt(testi[i]);
    }
    return numeri;
}

In questo caso per usare la funzione sarà necessario fare un passaggio in più perché la funzione vuole un dato in input (un testo) quindi leggerò un testo e lo passerò come parametro:

String testoCasella = valori.getText();
int numeri[] = daStringaAVettore(testoCasella);

Nel nostro caso:

int[]
(la parte prima del nome) la funzione restituirà (a chi l'ha chiamata) un vettore di interi
daStringaAVettore
il nome della funzione (viene usato ad esempio per chiamarla)
String testo
la funzione richiede un parametro formale di tipo String che all'interno del corpo della funzione sarà riferito con la variabile testo

Riguardo public int calcolaNumero(int a, int b) quali delle seguenti affermazioni sono vere?

restituisce una stringa ha due parametri ha un parametro intero si chiama calcolaNumero

public class Incrementa {
    static int incrementa(int x){ 
        x++;
        return x;
    }
    public static void main(String[] args) {
        int a = 10;
        int b = incrementa(a);
        System.out.println(a+", "+b);
    }
}

cosa comparirà come risultato del programma sopra?

11, 11no, la variabile a non viene modificata dalla funzione perché x contiene una copia del valore di a, non è la stessa variabile 10, 11 giusto, x viene incrementata ma è una copia di a che quindi non viene mai modificata nel programma