![]() ![]() |
|
![]() ![]() |
Sintassi della dichiarazione di un metodo:
|
![]() ![]() |
|
![]() ![]() |
Java supporta un meccanismo di overloading
(sovraccarico) che consente di chiamare più
metodi della stessa classe con lo stesso nome,
purché ogni metodo abbia una diversa firma
(signature).
Dato un generico metodo:
la firma corrispondente è data dalla sequenza:
Attenzione: la firma non comprende né il tipo del metodo, né i nomi dei parametri formali. |
![]() ![]() |
|
![]() ![]() |
Alcuni esempi di overloading già visti:
L'oggetto System.out (che rappresenta lo standard output) è un'istanza della classe PrintStream, che definisce i metodi aventi le seguenti firme (oltre a tanti altri):
Analogamente, abbiamo visto:
I metodi appena visti non restituiscono un risultato. In generale, due metodi con lo stesso nome ma firma diversa possono restituire risultati diversi. Ad esempio, nella classe java.lang.Math [locale, Medialab, Sun] ci sono diversi metodi min che restituiscono il minore tra due numeri, tra i quali:
Si noti che:
|
![]() ![]() |
|
![]() ![]() |
I metodi possono essere invocati all'interno di espressioni, come in
double i = Math.sqrt(2) * MyRecursiveMethods.fib(3);oppure come istruzioni (tipicamente sono metodi di tipo void) System.out.println("ciao");
Quando il compilatore incontra una invocazione di un metodo come MyClass.name(exp1,...,expn)esegue i seguenti passi:
Se questa dichiarazione non esiste, la compilazione non ha successo e viene stampato un messaggio di errore. Ad esempio,
|
![]() ![]() |
|
![]() ![]() |
Quando l'interprete incontra una invocazione di un metodo come
MyClass.name(exp1,...,expn)esegue i seguenti passi:
Supponiamo che la dichiarazione di metodo associata dal compilatore a questa invocazione sia:
|
![]() ![]() |
|
![]() ![]() |
Il passaggio dei parametri è il meccanismo
che lega i parametri attuali di una specifica invocazione di un
metodo ai parametri formali della corrispondente
dichiarazione.
Esistono varie modalità di passaggio dei parametri: per valore, per riferimento, per valore/risultato, per costante,... Lo studio di tali modalità e delle differenze tra di loro sarà argomento di corsi successivi. Il linguaggio Java adotta il passaggio di parametri per valore, descritto di seguito. Se abbiamo la chiamata
nome(val1,...,valn) e la corrispondente dichiarazione
il passaggio di parametri seguito dall'esecuzione del corpo del metodo è equivalente all'esecuzione del seguente blocco di istruzioni:
Si noti che il meccanismo con cui il compilatore individua il metodo associato ad una specifica invocazione (basato sulla firma) garantisce che
|
![]() ![]() |
|
![]() ![]() |
Se un parametro è
di tipo elementare (es. int, boolean, double, char, ...), durante il passaggio dei
parametri al parametro formale viene assegnato il
valore risultante dalla valutazione
del parametro attuale. Eventuali modifiche del parametro formale non si ripercuotono all'esterno del metodo.
Vediamo perché:
|
![]() ![]() |
|
![]() ![]() |
Se il tipo di un parametro è un tipo riferimento (un array o una classe, come String, Rectangle, ...), durante il passaggio dei
parametri al parametro formale viene assegnato un
riferimento all'oggetto o array risultante
dalla valutazione del parametro attuale. Eventuali modifiche fatte a tale oggetto nel metodo invocato saranno visibili dal metodo chiamante dopo la fine dell'esecuzione. Abbiamo già incontrato esempi di effetti collaterali dovuti al passaggio di riferimenti (es. con il metodo sort della classe ArrayUtils). Vediamo di capire meglio quale è il motivo con un esempio più semplice.
Vediamo perché:
|
![]() ![]() |
|
![]() ![]() |
Ci sono oggetti il cui stato non può essere
cambiato: questi vengono chiamati oggetti non
modificabili.
Il tipico esempio è dato dalle stringhe: poichè la classe String non mette a disposizione alcun metodo per modificare lo stato di una sua istanza (cioè la sequenza di caratteri in essa contenuta), le stringhe sono non modificabili. Ovviamente possiamo assegnare una nuova stringa a una variabile s di tipo String, ma questo corrisponde a creare un nuovo oggetto e quindi un nuovo riferimento che viene assegnato a s, senza modificare la vecchia stringa. Più in generale, un errore comune consiste nel tentare di modificare lo stato dell'oggetto passato come parametro assegnando un nuovo oggetto al parametro formale: è importante ricordarsi che assegnamenti di questo tipo non hanno effetto all'esterno del metodo.
Vediamo cosa succede:
|
![]() ![]() |
|
![]() ![]() |
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. |
![]() ![]() |
|
![]() ![]() |
Il tipo di un metodo può anche
essere una classe o un tipo array
(es. il metodo substring
della classe String
restituisce una stringa;
i metodi fill_int, fill_String e clone di ArrayUtils restituiscono array di
interi). In questo caso non viene restituito un valore, ma il
riferimento ad un oggetto.
È anche possibile che venga restituita la costante speciale null: se una variabile di tipo riferimento vale null significa che non riferisce alcun oggetto. Durante l'esecuzione di un programma, se viene invocato un metodo d'istanza su di una variabile di tipo riferimento che in quel momento ha come valore la costante null il programma viene interrotto segnalando un'anomalia (lanciando l'eccezione NullPointerException). Quindi è buona norma controllare che una variabile sia diversa da null prima di invocare un metodo su di essa.
|
![]() ![]() |
|
![]() ![]() |
Con le versioni di Java fino alla 1.4,
per definire un metodo con un numero variabile di argomenti
occorre utilizzare un parametro formale di tipo array.
Ad esempio, abbiamo visto il metodo maxIterative della classe ArrayUtils:
Per invocare il metodo maxIterative su di un insieme di numeri, occorre creare un array, inserirci i numeri, e passarlo come parametro attuale nella chiamata del metodo.
|
![]() ![]() |
|
![]() ![]() |
Java 1.5 (o 5.0) ha introdotto nel
linguaggio
i varargs, cioè
metodi con un numero variabile di argomenti in cui l'uso di un
array per il passaggio dei parametri, come visto sopra,
è reso implicito.
Nella definizione di un metodo con varargs il parametro formale di tipo array viene dichiarato, invece che con la normale sintassi <tipo> [] par, con la sintassi:
dove i puntini sospensivi danno l'idea che il parametro attuale corrispondente può essere presente zero o più volte. All'interno del corpo del metodo, il parametro formale par avrà comunque tipo <tipo> []. Attenzione: solo l'ultimo parametro formale di un metodo può avere questa nuova sintassi. Come esempio, il seguente metodo max è la versione con varargs di maxIterative:
Si noti che l'uso di un array in questo metodo può essere reso completamente implicito usando un for generalizzato, come nel metodo seguente della classe VarargsMax:
|
![]() ![]() |
|
![]() ![]() |
La chiamata di un metodo con
varargs può avere un numero variabile di parametri attuali:
tutti i valori dei parametri attuali
in corrispondenza del vararg vengono inseriti
automaticamente in un array, che viene legato al parametro formale
al momento del passaggio dei parametri: si guardino con
attenzione le chiamate del metodo VarargsMax.max nella classe TestVarargsMax. Si noti
(nell'ultima chiamata) che
è anche possibile passare direttamente un array
invece di una lista di valori.
L'output di questo test è il seguente:
|
![]() ![]() |
|
![]() ![]() |
In aggiunta ai classici metodi print e println usati per stampare (e,
come vedremo in seguito, per scrivere su file e su altri stream di
output), Java 5.0 ha introdotto il metodo printf (ispirandosi al linguaggio
C) che permette di specificare in modo semplice il modo in cui valori
numerici e stringhe devono essere rappresentati (formattati) in
output.
Questo permette, ad esempio, di stampare valori double con due sole cifre decimali, oppure di incolonnare correttamente una lista di interi con un numero variabile di cifre, o ancora di allineare alcune stringhe a sinistra. Il primo parametro del metodo printf è una stringa di formato, che indica il posto e il modo in cui vanno visualizzati i parametri che seguono. Infatti, la stringa di formato oltre a contenere caratteri da stampare normalmente contiene anche sequenze speciali di caratteri, chiamate specificatori di formato, che iniziano sempre col carattere % e terminano con una lettera che indica il tipo di formato. Alcuni esempi di tipi di formati tra i più comuni sono:
I restanti parametri passati a printf sono i valori da visualizzare in accordo agli specificatori di formato presenti nella stringa di formato. Esempio TestPrintf (HTML / Java): stampa incolonnata di un array di double con totale. Gli specificatori di formato possono anche contenere altri caratteri speciali detti modificatori di formato che modificano il formato di default. Esempi di modificatori sono:
La classe String possiede un metodo format che è simile a printf, però invece di visualizzare i dati in output, restituisce la stringa corrispondente. |