![]() ![]() |
|
![]() ![]() |
La scrittura di programmi che risolvono problemi di media o grande complessità è favorita da un approccio modulare:
Esempio: Supponiamo di voler scrivere un programma che legge un numero maggiore di 1 e ne stampa il più piccolo divisore diverso da 1. Anche in questo semplice esempio possiamo individuare almeno tre attività debolmente correlate:
In generale i linguaggi di programmazione mettono a disposizione dei meccanismi di astrazione che favoriscono l'approccio modulare. Nel caso di Java, i due principali meccanismi che ci interessano sono:
Per il momento ci concentreremo sull'astrazione funzionale, basata sui metodi di classe. L'astrazione sui dati e i metodi d'istanza saranno approfonditi nel secondo semestre di LIP e in altri corsi, come Metodologie di Programmazione, e Laboratorio di Strutture Dati. |
![]() ![]() |
|
![]() ![]() |
Un metodo è un blocco di comandi associato ad un nome. I metodi possono essere definiti e invocati:
Per questo motivo, nella definizione di un metodo il codice può far riferimento a dei parametri formali che saranno utilizzati per passare informazioni al metodo al momento dell'invocazione: si tratta di riferimenti simbolici che ad ogni invocazione corrisponderanno a valori diversi. Al momento della chiamata, gli argomenti usati per invocare il metodo divengono i parametri attuali, rispetto ai quali fare il calcolo. Nell'esempio di Math.min, i parametri formali della definizione sono a e b, invece nelle invocazioni che compaiono nel main di UsaMin sono rispettivamente (nella prima invocazione) 100 e il valore di n letto da input e (nella seconda invocazione) ancora il valore di n e 200. Nota: i nomi dei parametri formali non hanno alcuna attinenza con variabili omonime che potrebbero comparire nei programmi che invocano quel metodo. Un metodo può restituire un valore come risultato della chiamata mediante una particolare istruzione chiamata return.
|
![]() ![]() |
|
![]() ![]() |
Ogni metodo deve essere dichiarato
in una classe. Un metodo può essere statico oppure d'istanza. Concettualente, un metodo statico è associato alla classe, mentre un metodo di istanza è associato agli oggetti della classe. Sintassi della dichiarazione di un metodo:
Dato un generico metodo:
diciamo che la sua firma è data dalla sequenza:
Esempio: La firma del metodo visto prima è min(int, int)
|
![]() ![]() |
|
![]() ![]() |
Ricordiamo che il tipo
di un metodo è il tipo del dato restituito dal metodo,
oppure void.
Se il tipo è diverso da void, allora si dice che il metodo è tipizzato e deve forzatamente restituire un valore del tipo indicato. Conseguentemente per ogni possibile ramo di esecuzione del corpo deve comparire un comando return seguito da un'espressione che abbia lo stesso tipo del metodo.
Se invece il tipo è void, allora 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. Attenzione: il comando return ha lo scopo di restituire un valore e quindi non dovrebbe mai alterare il flusso dell'esecuzione. In particolare, per motivarvi a scrivere codice leggibile e che favorisca la verifica di correttezza: |
![]() ![]() |
|
![]() ![]() |
Per invocare un
metodo d'istanza su di un
oggetto si scrive
Per invocare un metodo statico si scrive
In entrambi gli esempi riportati sopra, <lista_parametri_attuali> deve essere una lista di espressioni che concorda in numero e in tipo con la lista dei parametri formali. Attenzione: i nomi dei tipi dei parametri devono essere scritti solo nella definizione (lista dei parametri formali), non ad ogni invocazione (lista dei parametri attuali).
|
![]() ![]() |
|
![]() ![]() |
Durante l'esecuzione di un programma Java,
in ogni istante è in esecuzione un metodo (e uno solo), il metodo corrente. Quando si incontra l'invocazione di un altro metodo, l'esecuzione del metodo corrente viene sospesa fino al completamento del metodo invocato. Per eseguire il metodo invocato, viene allocato in memoria un record di attivazione (o frame). Questo contiene fra l'altro:
In una
sequenza di chiamate di metodi l'ultimo metodo chiamato è il
primo a terminare l'esecuzione.
Questo permette di gestire in modo
efficace i record di attivazione utilizzando una pila (o stack),
una struttura dati che sarà studiata in altri corsi (ad
esempio a LSD). |
![]() ![]() |
|
![]() ![]() |
Vediamo un semplice esempio di
invocazione di metodi con la corrispondente allocazione di frames.
![]() |
![]() ![]() |
|
![]() ![]() |
Quando un metodo termina
l'esecuzione, il controllo passa all'istruzione del metodo
chiamante riferita dall'Indirizzo di ritorno,
eventualmente con il passaggio del risultato. Il frame del
metodo corrente viene disallocato, mentre il metodo
chiamante riprende l'esecuzione.
![]()
|
![]() ![]() |
|
![]() ![]() |
Per rendere il codice più leggibile è consigliabile individuare le funzionalità cruciali e implementarle con metodi opportuni. Riprendiamo l'esempio iniziale e vediamone una possibile soluzione.
Nota: quando scriviamo un metodo non possiamo fare ipotesi sugli argomenti coi quali verrà invocato e quindi dobbiamo gestire tutti i casi (vedi gestione di numeri minori di 2 nel metodo minDiv). Non sempre la scelta è ovvia (ad esempio, come gestireste il caso di divisore 0 nel metodo divide?). Vedremo nel secondo semestre come il meccanismo delle eccezioni possa essere usato anche per questo scopo.
|