Introduzione ai Metodi in Java

Concetti Chiave
  • I metodi in Java sono blocchi di codice che eseguono operazioni specifiche e possono accettare parametri e restituire valori.
  • I metodi possono essere definiti all'interno di classi e sono utilizzati per operare sui dati contenuti nelle variabili di istanza.
  • I metodi possono essere parametrizzati per accettare input e restituire risultati, migliorando la flessibilità e la riusabilità del codice.

Introduzione ai Metodi

Come accennato nelle lezioni precedenti, le classi di solito sono costituite da due elementi: variabili di istanza, cioè campi, e metodi.

Il tema dei metodi è ampio perché Java conferisce loro molta potenza e flessibilità. Di fatto, gran parte delle prossime lezioni è dedicata ai metodi. Tuttavia, vi sono alcuni fondamenti da apprendere subito, così da poter iniziare ad aggiungere metodi alle classi.

Di seguito è riportata la forma generale di un metodo:

tipo nome(elenco-parametri) {
    // corpo del metodo
}

Qui, tipo specifica il tipo di dati restituito dal metodo. Può essere un qualsiasi tipo valido, inclusi tipi di classe creati appositamente.

Se il metodo non restituisce alcun valore, il suo tipo di ritorno deve essere void. Il nome del metodo è indicato da nome. Può essere qualsiasi identificatore legale diverso da quelli già utilizzati da altri elementi all'interno dell'attuale ambito di visibilità.

L'elenco-parametri è una sequenza di coppie tipo-identificatore separate da virgole. I parametri sono essenzialmente variabili che ricevono il valore degli argomenti passati al metodo quando viene chiamato. Se il metodo non possiede parametri, l'elenco dei parametri sarà vuoto.

I metodi il cui tipo di ritorno è diverso da void restituiscono un valore alla routine chiamante tramite la seguente forma dell'istruzione return:

return valore;

Qui, valore è il valore restituito.

Nelle prossime sezioni verrà mostrato come creare vari tipi di metodi, inclusi quelli che accettano parametri e quelli che restituiscono valori.

Esempio

Sebbene sia perfettamente corretto creare una classe che contenga solo dati, ciò accade di rado. La maggior parte delle volte si impiegano metodi per accedere alle variabili di istanza definite dalla classe. In effetti, i metodi definiscono l'interfaccia della maggior parte delle classi. Ciò consente all'implementatore della classe di nascondere la disposizione specifica delle strutture dati interne dietro astrazioni di metodo più pulite. Oltre a definire metodi che forniscono accesso ai dati, è possibile definire anche metodi utilizzati internamente dalla stessa classe.

Ritorniamo alla classe Scatola introdotta nella lezione precedente. In essa sono definite tre variabili di istanza: larghezza, altezza e profondita. Queste variabili contengono i dati che definiscono una scatola. Sebbene sia possibile creare un oggetto della classe Scatola e assegnare valori a queste variabili, non è ancora stato fatto nulla per calcolare il volume della scatola. Inoltre, non è stato definito alcun metodo per accedere ai dati contenuti.

Iniziamo aggiungendo un metodo alla classe Scatola. Può essere sorto il dubbio, osservando i programmi precedenti, che il calcolo del volume di una scatola fosse meglio gestito dalla classe Scatola piuttosto che dalla classe DimostrazioneScatola. Dopotutto, poiché il volume di una scatola dipende dalle sue dimensioni, ha senso che sia la classe Scatola a calcolarlo. Per fare ciò è necessario aggiungere un metodo a Scatola, come mostrato di seguito:

// Questo programma include un metodo all'interno della classe scatola.

class Scatola {
    double larghezza;
    double altezza;
    double profondita;

    // visualizza il volume di una scatola
    void volume() {
        System.out.print("Il volume è ");
        System.out.println(larghezza * altezza * profondita);
    }
}

class DimostrazioneScatola3 {
    public static void main(String[] argomenti) {
        Scatola miaScatola1 = new Scatola();
        Scatola miaScatola2 = new Scatola();

        // assegna valori alle variabili di istanza di miaScatola1
        miaScatola1.larghezza = 10;
        miaScatola1.altezza = 20;
        miaScatola1.profondita = 15;

        /* assegna valori diversi alle variabili di istanza di miaScatola2 */
        miaScatola2.larghezza = 3;
        miaScatola2.altezza = 6;
        miaScatola2.profondita = 9;

        // visualizza il volume della prima scatola
        miaScatola1.volume();

        // visualizza il volume della seconda scatola
        miaScatola2.volume();
    }
}

Questo programma genera il seguente output, che è lo stesso della versione precedente:

Il volume è 3000.0
Il volume è 162.0

Si osservino con attenzione le seguenti due righe di codice:

miaScatola1.volume();
miaScatola2.volume();

La prima riga richiama il metodo volume() su miaScatola1. Vale a dire, chiama volume() in relazione all'oggetto miaScatola1, usando il nome dell'oggetto seguito dall'operatore punto. Pertanto, la chiamata a miaScatola1.volume() visualizza il volume della scatola definita da miaScatola1, mentre la chiamata a miaScatola2.volume() visualizza il volume della scatola definita da miaScatola2. Ogni volta che volume() viene invocato, mostra il volume della scatola specificata.

Se non si ha familiarità con il concetto di chiamata di un metodo, la seguente spiegazione chiarirà la questione. Quando viene eseguito miaScatola1.volume(), il sistema run-time di Java trasferisce il controllo al codice definito all'interno di volume(). Dopo che le istruzioni contenute in volume() sono state eseguite, il controllo ritorna alla routine chiamante ed l'esecuzione riprende con la riga di codice successiva alla chiamata. Nel senso più generale, un metodo costituisce in Java il modo di implementare sotto-procedure o subroutine.

C'è un aspetto molto importante da notare all'interno del metodo volume(): le variabili di istanza larghezza, altezza e profondita sono referenziate direttamente, senza che siano precedute dal nome di un oggetto o dall'operatore punto. Quando un metodo utilizza una variabile di istanza definita dalla sua classe, la referenzia direttamente, senza riferimento esplicito a un oggetto e senza usare l'operatore punto. Ciò è facile da comprendere: un metodo è sempre invocato in relazione a un qualche oggetto della sua classe. Una volta avvenuta tale invocazione, l'oggetto è noto. Di conseguenza, all'interno di un metodo non è necessario specificare nuovamente l'oggetto. Questo significa che larghezza, altezza e profondita dentro volume() fanno implicitamente riferimento alle copie di tali variabili presenti nell'oggetto che invoca volume().

Ricapitolando: quando una variabile di istanza è accessibile da codice che non fa parte della classe in cui la variabile è definita, l'accesso deve avvenire tramite un oggetto, con l'uso dell'operatore punto. Tuttavia, quando una variabile di istanza è accessibile da codice che fa parte della stessa classe della variabile, tale variabile può essere referenziata direttamente. Lo stesso vale per i metodi.

Restituire un Valore

Sebbene l'implementazione di volume() sposti effettivamente il calcolo del volume di una scatola all'interno della classe Scatola, dove è opportuno che stia, non è il modo migliore di procedere.

Per esempio, cosa accadrebbe se un'altra parte del programma volesse conoscere il volume di una scatola, senza tuttavia visualizzarne il valore? Un modo migliore di implementare volume() consiste nel farle calcolare il volume della scatola e restituire il risultato al chiamante. Il seguente esempio, versione migliorata del programma precedente, realizza proprio questo:

// Ora, volume() restituisce il volume di una scatola.

class Scatola {
    double larghezza;
    double altezza;
    double profondita;

    // calcola e restituisce il volume
    double volume() {
        return larghezza * altezza * profondita;
    }
}

class DimostrazioneScatola4 {
    public static void main(String[] argomenti) {
        Scatola miaScatola1 = new Scatola();
        Scatola miaScatola2 = new Scatola();
        double vol;

        // assegna valori alle variabili di istanza di miaScatola1
        miaScatola1.larghezza = 10;
        miaScatola1.altezza = 20;
        miaScatola1.profondita = 15;

        /* assegna valori diversi alle variabili di istanza di miaScatola2 */
        miaScatola2.larghezza = 3;
        miaScatola2.altezza = 6;
        miaScatola2.profondita = 9;

        // ottiene il volume della prima scatola
        vol = miaScatola1.volume();
        System.out.println("Il volume è " + vol);

        // ottiene il volume della seconda scatola
        vol = miaScatola2.volume();
        System.out.println("Il volume è " + vol);
    }
}

Come si può vedere, quando volume() viene chiamato, è posto sul lato destro di un'istruzione di assegnazione. Sul lato sinistro vi è una variabile, in questo caso vol, che riceverà il valore restituito da volume(). Pertanto, dopo l'esecuzione di

vol = miaScatola1.volume();

il valore di miaScatola1.volume() è 3000 e tale valore viene quindi memorizzato in vol.

Vi sono due aspetti fondamentali da comprendere riguardo alla restituzione dei valori:

  • Il tipo di dato restituito da un metodo deve essere compatibile con il tipo di ritorno specificato dal metodo. Ad esempio, se il tipo di ritorno di un metodo è boolean, non è possibile restituire un intero.
  • La variabile che riceve il valore restituito da un metodo (come vol, in questo caso) deve anch'essa essere compatibile con il tipo di ritorno specificato per il metodo.

Un ulteriore punto: il programma precedente può essere scritto in modo leggermente più efficiente perché, in realtà, la variabile vol non è necessaria. La chiamata a volume() può essere usata direttamente nell'istruzione println(), come mostrato qui:

System.out.println("Il volume è " + miaScatola1.volume());

In questo caso, quando viene eseguito println(), miaScatola1.volume() sarà chiamato automaticamente e il suo valore sarà passato a println().

Aggiungere un Metodo che Accetta Parametri

Sebbene alcuni metodi non richiedano parametri, la maggior parte sì. I parametri permettono di generalizzare un metodo; un metodo parametrizzato può operare su una varietà di dati e/o essere usato in numerose situazioni leggermente diverse. Per illustrare il concetto, si consideri un esempio molto semplice. Di seguito è riportato un metodo che restituisce il quadrato del numero 10:

int quadrato() {
    return 10 * 10;
}

Sebbene questo metodo restituisca effettivamente il valore di 10 al quadrato, il suo impiego è molto limitato. Tuttavia, modificando il metodo affinché accetti un parametro, come mostrato di seguito, quadrato() diventa molto più utile:

int quadrato(int i) {
    return i * i;
}

Ora, quadrato() restituirà il quadrato di qualunque valore con cui viene chiamato; si tratta quindi di un metodo generico in grado di calcolare il quadrato di qualsiasi numero intero, non solo di 10.

Ecco un esempio:

int x, y;
x = quadrato(5);  // x vale 25
x = quadrato(9);  // x vale 81
y = 2;
x = quadrato(y);  // x vale 4

Nella prima chiamata a quadrato(), il valore 5 viene passato nel parametro i. Nella seconda chiamata, i riceverà il valore 9. La terza invocazione passa il valore di y, che in questo esempio è 2. Come mostrano questi esempi, quadrato() è in grado di restituire il quadrato di qualsiasi dato gli venga passato.

È importante distinguere i termini parametro e argomento. Un parametro è una variabile definita da un metodo che riceve un valore quando il metodo viene chiamato. Ad esempio, in quadrato(), i è un parametro. Un argomento è un valore passato a un metodo al momento dell'invocazione. Ad esempio, quadrato(100) passa 100 come argomento. All'interno di quadrato(), il parametro i riceve tale valore.

Un metodo parametrizzato può essere utilizzato per migliorare la classe Scatola. Negli esempi precedenti, le dimensioni di ciascuna scatola dovevano essere impostate separatamente tramite una sequenza di istruzioni come:

miaScatola1.larghezza = 10;
miaScatola1.altezza   = 20;
miaScatola1.profondita = 15;

Sebbene questo codice funzioni, presenta due problemi: è macchinoso e soggetto a errori (per esempio, si potrebbe dimenticare di impostare una dimensione); inoltre, nei programmi Java ben progettati, le variabili di istanza dovrebbero essere accessibili solo tramite metodi definiti dalla classe. In futuro, si può modificare il comportamento di un metodo, ma non quello di una variabile di istanza esposta.

Un approccio migliore consiste quindi nel creare un metodo che accetti le dimensioni della scatola come parametri e imposti di conseguenza ciascuna variabile di istanza. Il concetto è implementato dal seguente programma:

// Questo programma utilizza un metodo con parametri.

class Scatola {
    double larghezza;
    double altezza;
    double profondita;

    // calcola e restituisce il volume
    double volume() {
        return larghezza * altezza * profondita;
    }

    // imposta le dimensioni della scatola
    void impostaDim(double l, double a, double p) {
        larghezza  = l;
        altezza    = a;
        profondita = p;
    }
}

class DimostrazioneScatola5 {
    public static void main(String[] argomenti) {
        Scatola miaScatola1 = new Scatola();
        Scatola miaScatola2 = new Scatola();
        double vol;

        // inizializza ogni scatola
        miaScatola1.impostaDim(10, 20, 15);
        miaScatola2.impostaDim(3,  6,  9);

        // ottiene il volume della prima scatola
        vol = miaScatola1.volume();
        System.out.println("Il volume è " + vol);

        // ottiene il volume della seconda scatola
        vol = miaScatola2.volume();
        System.out.println("Il volume è " + vol);
    }
}

Il metodo impostaDim() viene utilizzato per definire le dimensioni di ciascuna scatola. Ad esempio, quando viene eseguito

miaScatola1.impostaDim(10, 20, 15);

10 viene copiato nel parametro l, 20 in a e 15 in p. All'interno di impostaDim(), i valori di l, a e p vengono quindi assegnati rispettivamente a larghezza, altezza e profondita.

Per molti lettori, i concetti presentati nelle sezioni precedenti risulteranno familiari. Se concetti come chiamate di metodo, argomenti e parametri sono nuovi, può essere utile fare qualche esperimento prima di proseguire. Le nozioni di invocazione di un metodo, parametri e valori restituiti sono fondamentali nella programmazione Java.