Динамическая диспетчеризация методов

Хотя приведенные в предыдущем разделе примеры демонстрируют механизм переопределения методов, они не показывают всех возможностей. Действительно, если бы переопределение методов служило лишь для удобства работы с пространством имен, оно представляло бы только определенный теоретический интерес, но имело бы очень небольшое практическое значение. Однако это не так. Переопределение методов служит основой для одной из наиболее мощных концепций Java — динамической диспетчеризации методов. Динамическая диспетчеризация методов — механизм, посредством которого разрешение обращения к переопределенному методу осуществляется во время выполнения, а не во время компиляции.

Динамическая диспетчеризация методов важна потому, что именно с ее помощью Java реализует полиморфизм времени выполнения.

Рассмотрение этой концепции начнем с повторной формулировки одного важного принципа: ссылочная переменная суперкласса может ссылаться на объект подкласса. Система Java использует этот факт для разрешения обращений к переопределенным методам во время выполнения. Вот как это происходит. Когда вызов переопределенного метода реализуется посредством ссылки на суперкласс, Java выбирает нужную версию этого метода в зависимости от типа объекта ссылки в момент вызова. Таким образом, этот выбор осуществляется во время выполнения. При ссылке на различные типы объектов программа будет обращаться к различным версиям переопределенного метода. Иначе говоря, выбор для выполнения версии переопределенного метода осуществляется в зависимости от типа объекта ссылки (а не от типа ссылочной переменной). Следовательно, если суперкласс содержит метод, переопределяемый подклассом, то при наличии ссылки на различные типы объектов через ссылочную переменную суперкласса программа будет выполнять различные версии метода.

В следующем примере иллюстрируется динамическая диспетчеризация методов.

// Динамическая диспетчеризация методов
class А {
void callme() {
System.out.println("Внутри метода callme класса A");
}
}
class В extends A {
// переопределение метода callme()
void callme() {
System.out.println("Внутри метода callme класса В");
}
}
class С extends A {
// переопределение метода callme()
void callme() {
System.out.println("Внутри метода callme класса С");
}
}
class Dispatch {
public static void main(String args []) {
A a = new A(); // объект типа А
В b = new В ();// объект типа В
С с = new С (); // объект типа С
А r; // получение ссылки типа А
r = а; //r ссылается на объект А r.callme(); // вызов версии метода callme, определенной в А
r = b; // r ссылается на объект В
r.callme(); // вызов версии метода callme, определенной в В
r = с; //r ссылается на объект С
r.callme(); // вызов версии метода callme, определенной в С
}
}

Эта программа генерирует следующий вывод:

Внутри метода callme класса А Внутри метода callme класса В Внутри метода callme класса С

Эта программа создает один суперкласс А и два его подкласса: В и С. Подклассы В и С переопределяют метод callme (), объявленные в классе А. Внутри метода main () программа объявляет объекты типов А, В и С. Программа объявляет также ссылку типа А по имени г. Затем программа по очереди присваивает переменной г ссылку на каждый тип объекта и использует эту ссылку для вызова метода callme (). Как видно из вывода, выполняемая версия метода callme () определяется по типу объекта ссылки во время выполнения. Если бы выбор осуществлялся по типу ссылочной переменной, г, вывод отражал бы три обращения к методу callme () класса А.

На заметку! Те читатели, которые знакомы с С+ + или С#, должны заметить, что переопределенные методы в Java подобны виртуальным функциям в этих языках.