Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D La Corte in Rete
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Marco Faella » 4.Risoluzione dell'overloading e dell'overriding


Binding dinamico

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).

Le fasi del binding

Quindi, in Java il binding (collegare ciascuna chiamata ad un metodo vero e proprio) avviene in due fasi:

  • Early binding, in cui il compilatore risolve l’overloading (scegliendo la firma più appropriata alla chiamata);
  • Late binding, in cui la JVM risolve l’overriding (scegliendo il metodo vero e proprio).

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:

  • Individuazione delle firme candidate;
  • Scelta della firma più specifica tra quelle candidate.

Le prossime slide approfondiscono ciascuna di queste fasi.

Individuazione delle firme candidate

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:

  • Si trova nella classe dichiarata di x o in una sua superclasse.
  • E’ visibile dal punto della chiamata, rispetto alle regole di visibilità Java.
  • E’ compatibile con la chiamata; ovvero, per ogni indice i compreso tra 1 ed n, il tipo (dichiarato) del parametro attuale a_i è assegnabile al tipo T_i.

Se nessuna firma risulta candidata per una data chiamata, il compilatore segnala un errore (accompagnato dal messaggio: “cannot find symbol”).

Confrontare le firme candidate

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à.

Scelta della firma più specifica

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.

Late binding

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:

  • si parte dalla classe effettiva di x;
  • si cerca un metodo che abbia la firma identica a quella scelta dall’early binding;
  • inolte, il metodo deve essere visibile dal punto della chiamata;
  • se non lo si trova, si passa alla superclasse;
  • così via, fino ad arrivare ad Object.

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.

Esercizio 1 (esame 8/9/2009)

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.

Svolgimento dell’esercizio 1

Esaminiamo le chiamate una per volta

1) System.out.println(alfa.f(3, beta));

  • alfa è di tipo dichiarato A, quindi le firme candidate vanno cercate nella classe A (o tutt’al più in Object);
  • i due parametri attuali della chiamata sono di tipo (dichiarato) int e B, rispettivamente;
  • la firma f(double, A) è candidata, in quanto visibile e compatibile
    • essa è compatibile perché int è assegnabile a double (conversione implicita) e B è assegnabile ad A (sottotipo);
  • la firma f(double, B) è candidata, in quanto visibile e compatibile;
  • la firma f(int, Object) è candidata, in quanto visibile e compatibile;
  • non vi sono altre firme candidate.

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.

Svolgimento dell’esercizio 1 (segue)

Esaminiamo la seconda chiamata:

2) System.out.println(alfa.f(3.0, beta))

  • alfa è di tipo dichiarato A, quindi le firme candidate vanno cercate nella classe A (o tutt’al più in Object);
  • i due parametri attuali della chiamata sono di tipo (dichiarato) double e B, rispettivamente;
  • la firma f(double, A) è candidata, in quanto visibile e compatibile;
  • la firma f(double, B) è candidata, in quanto visibile e compatibile;
  • la firma f(int, Object) non è candidata, in quanto non compatibile;
  • non vi sono altre firme candidate.

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

Svolgimento dell’esercizio 1 (segue)

Esaminiamo la terza chiamata:

3) System.out.println(beta.f(3.0, alfa))

  • beta è di tipo dichiarato B, quindi le firme candidate vanno cercate in B, in A e in Object;
  • i due parametri attuali della chiamata sono di tipo (dichiarato) double ed A, rispettivamente;
  • la firma f(double, B) non è candidata, in quanto non compatibile (secondo argomento);
  • la firma f(float, Object) non è candidata, in quanto non compatibile (primo argomento);
  • la firma f(int, Object) non è candidata, in quanto non compatibile (primo argomento);
  • la firma f(double, A) è candidata, in quanto visibile e compatibile.

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

Svolgimento dell’esercizio 1 (segue)

Esaminiamo l’ultima chiamata:

4) System.out.println(gamma.f(3, gamma))

  • gamma è di tipo dichiarato C, quindi le firme candidate vanno cercate in C, in A e in Object;
  • i due parametri attuali della chiamata sono di tipo (dichiarato) int e C, rispettivamente;
  • la firma f(int, Object) è candidata, in quanto visibile e compatibile;
  • la firma f(double, A) è candidata, in quanto visibile e compatibile;
  • la firma f(double, B) non è candidata, in quanto non compatibile (secondo argomento).

Le due firme candidate non sono confrontabili.
Quindi, l’early binding si conclude con un errore di compilazione.

Esercizio 2 (esame 27/11/2009)

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.

  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion

Fatal error: Call to undefined function federicaDebug() in /usr/local/apache/htdocs/html/footer.php on line 93