Entrambi. Stai un po 'confondendo le nozioni di disponibilità metodo / campo per tipo e late binding. Sono separati, anche se correlati.
... actual object at runtime determines which method ...
Potrei riaffermarlo come "il cui" metodo invece di "quale" metodo per essere più specifico. Il nome del metodo + firma che verrà chiamato viene determinato al momento della compilazione e risolto, ma la particolare implementazione (ad es. Base o qualche override) di quel metodo viene determinata in fase di esecuzione. Questo è talvolta chiamato anche legame tardivo.
(Oltre al nome + firma, il metodo che verrà chiamato sarà anche direttamente correlato al metodo della classe che lo ha originariamente introdotto, ad esempio sarà il metodo stesso o un override di quello in una sottoclasse. )
Prendi la situazione in cui abbiamo:
class A {
void foo ();
}
class B extends A { ... }
class C extends B { ... }
class D extends A { ... }
...
class U {
void method ( A a ) {
a.foo ();
}
}
...
U u = new U();
u.method ( new A () );
u.method ( new B () );
u.method ( new C () );
u.method ( new D () );
Sappiamo che method
chiamerà un metodo esistente denominato foo
senza parametri, introdotto da class A
. Tuttavia, può chiamare A
's foo
, B
' s foo
, C
's foo
, o D
' s foo
. Qui il runtime della lingua (Macchina virtuale) che esegue method
determina il cui foo
da chiamare utilizzando l'associazione tardiva.
Il problema di accesso è una determinazione del tempo di compilazione effettuata utilizzando l'analisi statica e le regole del sistema di tipi (della particolare lingua coinvolta). Usando i tipi in fase di compilazione, le lingue possono prevenire determinati stati di programmi illegali (con un errore di compilazione nel tempo invece di un errore di runtime), come non c'è foo
da chiamare.
Dovresti anche notare che ci sono fondamentalmente diversi tipi di cast. Gli aggiornamenti (casting da B
a A
, o C
a B
o C
a A
) sono sicuri e possono essere convalidati in fase di compilazione. I cast down (ad es. Da A
a B
, o A
a D
) non sono sempre necessariamente analizzabili nel tempo di compilazione, e quindi sono progettati nella lingua per eseguire un controllo di runtime al punto di cast (prima delle cose andare oltre e utilizzare il valore casted).
Di conseguenza, è possibile ottenere un'eccezione di cast illegale, in fase di runtime. Avendo definito la lingua in modo che il compilatore e il runtime cospirino per controllare i cast illegali, non devono controllare il metodo non trovato in runtime, indipendentemente dalla classe reale dell'oggetto: questo perché sa che il cast ha funzionato, quindi il metodo esiste (nonostante la riflessione, anche il versioning delle DLL complica le cose, ma c'è un controllo del tempo di caricamento aggiuntivo che integra il controllo del tempo di compilazione e entrambi insieme sostituiscono la necessità di un controllo runtime per i metodi esistenti o meno.)
Anche se nel tuo particolare esempio con Flyer
e Bird
, un'analisi statica avrebbe potuto eliminare il controllo del cast runtime in ((Bird) f)
, in generale questo non è sempre possibile. Il tuo esempio continua a chiamare un metodo (solo) disponibile in Bird
.
Si noti che, per definizione delle istruzioni del codice byte, questo richiamo del metodo ( getNativeOf()
) può essere eseguito solo (cioè può essere raggiunto solo dal flusso del controllo) se il cast del runtime ha esito positivo, in quanto un fallimento del cast lanciare un'eccezione modificando il normale flusso di controllo e impedendo il raggiungimento dell'invocazione del metodo.
(Inoltre, il compilatore non genererà quanto segue, un programma di codice di byte appositamente progettato che tenta di accedere a getNativeOf()
da un oggetto Flyer
senza un cast di runtime protettivo necessario su Bird
fallirà un tempo di caricamento controllo di verifica! Sebbene ciò avvenga dopo la compilazione, è anche una forma di analisi statica, in altre parole, viene eseguito una sola volta in fase di esecuzione (noto come tempo di caricamento) anziché ogni volta che viene utilizzato il codice.)
Dopo il cast, il compilatore genererà l'invocazione del metodo assumendo che il cast abbia successo (sapendo che se siamo a questo punto, il cast deve aver avuto successo) e come tale conosce al momento della compilazione il nome + signature (e introducendo la classe ) di quale metodo chiamare. Il metodo effettivo risultante chiamato utilizza ancora l'associazione tardiva in fase di esecuzione per trovare la cui implementazione o sovrascrittura di tale metodo è appropriata per il tipo reale dell'oggetto (nonostante l'ottimizzazione, ma qualsiasi ottimizzazione deve fornire gli stessi risultati se si utilizza l'associazione tardiva).