Per “binding dinamico” (letteralmente, “bind” significa “legare”) si intende il meccanismo per cui non è il compilatore, ma la JVM ad avere l’ultima parola su quale metodo invocare in corrispondenza di ciascuna chiamata a metodo.
In effetti, questo meccanismo è una diretta conseguenza del polimorfismo e dell’overriding.
Ovvero, ciascun riferimento può puntare ad oggetti di tipo effettivo diverso (polimorfismo) e ciascuno di questi tipi effettivi può prevedere una versione diversa dello stesso metodo (overriding).
Inoltre, il compilatore non può prevedere di che tipo effettivo sarà una variabile nel corso dell’esecuzione del programma (tecnicamente, questo problema è indecidibile).
Quindi, in Java il binding (collegare ciascuna chiamata ad un metodo vero e proprio) avviene in due fasi:
Chiaramente, il late binding non è necessario per quei metodi che non ammettono overriding: i metodi privati, statici o final.
Per questi metodi si parla di “binding statico”, perché la scelta del metodo da eseguire viene fatta già dal compilatore.
Esaminiamo prima l’early binding, che si divide a sua volta in due fasi:
Le prossime slide approfondiscono ciascuna di queste fasi.
Consideriamo una chiamata generica
x.f(a_1, …, a_n)
Si ricorda che per “firma” di un metodo si intende il suo nome e l’elenco dei tipi dei suoi parametri formali.
Una generica firma
f(T_1, …, T_n)
è candidata per la chiamata in questione se:
Se nessuna firma risulta candidata per una data chiamata, il compilatore segnala un errore (accompagnato dal messaggio: “cannot find symbol”).
Date due firme con lo stesso nome e numero di argomenti
f(T_1, …, T_n) e f(U_1, …, U_n)
Si dice che la prima firma è più specifica della seconda se, per ogni indice i compreso tra 1 ed n, il tipo T_i è assegnabile al tipo U_i.
ATTENZIONE: notate che questo confronto tra firme non dipende dal tipo dei parametri attuali passati alla chiamata.
E’ facile verificare che essere “più specifico” è una relazione riflessiva, antisimmetrica e transitiva, proprio come la relazione di assegnabilità.
Quindi, essa è una relazione d’ordine sull’insieme delle firme.
Tale ordine è parziale, in quanto alcune firme non sono confrontabili tra loro.
Ad esempio, le firme f(int, double) e f(double, int) non sono confrontabili quanto a specificità.
L’early binding si conclude individuando, tra le firme candidate, una che sia più specifica di tutte le altre.
Per simulare “a mano” questo meccanismo, nei casi complessi può essere conveniente realizzare un diagramma, in cui ci sia un nodo per ciascuna firma candidata ed un arco orientato da un nodo “a” ad un nodo “b” quando la firma “a” è più specifica della firma “b”.
Se nel diagramma c’è un nodo che ha archi uscenti diretti verso tutte le altre firme, quella sarà la firma scelta dal compilatore.
Domanda: E’ possibile che si trovi più di una firma più specifica di tutte le altre? Perché?
Attenzione: in questa discussione sull’overloading sono state tralasciate la programmazione generica e l’autoboxing.
Il late binding è la fase di risoluzione dell’overriding, a carico della JVM.
Questa fase riceve in input la firma scelta dal compilatore durante l’early binding.
Consideriamo nuovamente la chiamata generica
x.f(a_1, …, a_n)
La JVM cerca un metodo da eseguire, con il seguente algoritmo:
Questo procedimento può fallire, cioè non trovare alcun metodo, solo se una classe A dipendeva da una classe B e la classe B è cambiata da quando è stata compilata A.
Dato il seguente programma (tutte le classi appartengono allo stesso pacchetto):
Prima parte Mostra codice
Seconda parte Mostra codice
Indicare l'output del programma.
Se un'istruzione provoca un errore di compilazione, specificarlo e poi continuare l'esercizio ignorando quell'istruzione.
Per ogni chiamata ad un metodo (escluso System.out.println), indicare la lista delle firme candidate.
Esaminiamo le chiamate una per volta
1) System.out.println(alfa.f(3, beta));
Delle tre firme candidate, la seconda è più specifica della prima, ma non è confrontabile con la terza.
Quindi, nessuna firma è più specifica di tutte le altre.
Il risultato è un errore di compilazione.
ATTENZIONE: ricordate che la scelta della firma più specifica non dipende dal tipo dei parametri attuali della chiamata.
Esaminiamo la seconda chiamata:
2) System.out.println(alfa.f(3.0, beta))
Delle due firme candidate, la seconda è più specifica della prima.
Quindi, l’early binding si conclude con la selezione della firma f(double, B).
Per il late binding, cerchiamo il metodo da eseguire a partire dalla classe effettiva di alfa: B.
Nella classe B, troviamo un metodo visibile con quella firma.
Quindi, l’output di questa chiamata è
B1
Esaminiamo la terza chiamata:
3) System.out.println(beta.f(3.0, alfa))
Essendoci una sola firma candidata, l’early binding si conclude con la selezione della firma f(double, A).
Per il late binding, cerchiamo il metodo da eseguire a partire dalla classe effettiva di beta: B.
Nella classe B, non c’è alcun metodo con la firma scelta.
Passiamo alla classe A, in cui troviamo un metodo con la firma scelta.
Quindi, l’output di questa chiamata è
A1
Esaminiamo l’ultima chiamata:
4) System.out.println(gamma.f(3, gamma))
Le due firme candidate non sono confrontabili.
Quindi, l’early binding si conclude con un errore di compilazione.
Dato il seguente programma (tutte le classi appartengono allo stesso pacchetto):
Prima parte Mostra codice
Seconda parte Mostra codice
Indicare l'output del programma.
Se un'istruzione provoca un errore di compilazione, specificarlo e poi continuare l'esercizio ignorando quell'istruzione.
Per ogni chiamata ad un metodo (escluso System.out.println), indicare la lista delle firme candidate.
4. Risoluzione dell'overloading e dell'overriding
5. Controllo di uguaglianza tra oggetti
6. Classi interne, locali ed anonime
7. Iteratori, teoria e pratica
8. Clonazione di oggetti. Confronto tra oggetti.
9. Elementi di programmazione di interfacce grafiche
10. Il paradigma Model-View-Controller. Il pattern Strategy
11. I pattern Composite e Decorator
12. I pattern Template Method e Factory Method
13. Classi e metodi parametrici
14. La libreria Java Collection Framework: le interfacce Iterable, ...
15. La libreria Java Collection Framework: la classe HashSet e le l...
16. Parametri di tipo con limiti
17. L'implementazione della programmazione generica: la cancellazio...
18. La riflessione
19. Introduzione al multi-threading
22. Classi enumerate