Es gibt bei Methoden von konkreten Klasse, abstrakte Klassen und Schnittstellen Unterschiede, wo der Aufruf letztendlich landet. Nehmen wir folgende Methode an:
void f( T t ) {
t.m();
}
Fordert die Methode ein Argument vom Typ T und ruft auf dem Parameter t die Methode m() auf, so können wir folgendes festhalten:
· Ist T eine finale Klasse, so wird immer die Methode m() von T aufgerufen, da es keine Unterklassen geben kann, die m() überschreiben.
· Ist T eine nicht-finale Klasse und m() eine finale Methode, wird genau m() aufgerufen, weil kein Unterklasse m() überschreiben kann.
· Ist T eine nicht-finale Klasse und m() keine finale Methode, so könnten Unterklassen von T m() überschreiben und t.m() würde dann dynamisch die überschriebene Methode aufrufen.
· Ist T eine abstrakte Klasse und m() eine abstrakte Methode, so wird in jedem Fall eine Realisierung von m() in einer Unterklasse aufgerufen.
· Ist T eine Schnittstelle, und m() keine Default-Implementierung, so wird in jedem Fall eine Implementierung m() einer implementierenden Klasse aufgerufen.
· Ist T eine Schnittstelle, und m() eine Default-Implementierung, so kann t.m() bei der Default-Implementierung landen, oder bei einer überschriebenen Version einer implementierenden Klasse.