6 июл. 2015 г.

Полиморфизм. Переопределение методов в классах наследниках.

Когда подкласс определяет метод экземпляра с таким же именем, типом возвращаемого значения и параметрами, что и метод его родительского класса, то данный метод за мещает (overrides) метод родительского класса. Когда этот метод выполняется для экземпляра класса, то вызывается новое определение, а не старое определение родительского класса.

При переопределении метода его сигнатура и тип возвращаемого значения должны полностью сохраняться. Если в подклассе мы изменим тип, количество или порядок следования параметров, то получим новый метод, не переопределяющий метод суперкласса. Если изменим только тип возвращаемого значения, то получим ошибку, которую "заметит" компилятор. Исключением из этого правила является только случай ковариантности возвращаемых типов.

Проверку соответствия сигнатуры переопределяемого метода можно возложить на компилятор, записав перед методом подкласса аннотацию @Override. В этом случае компилятор пошлет на консоль сообщение об ошибке, если сигнатура помеченного метода не будет соответствовать сигнатуре ни одного метода суперкласса с тем же именем.

При переопределении метода права доступа к нему можно только расширить, но не сузить. Открытый метод public должен остаться открытым, защищенный protected может стать public, но не может стать private.

P0001Чтобы все стало более понятно, немного изменим наши классы В и С из прошлого поста. Добавив к ним метод printX(), с точно таким же определением как и в классе А.

P0002

P0003

Классы АВС и А у нас не претерпели ни каких изменений, поэтому я тут их не привожу.
Как видно, аннотация @Override, так же не была использована, так как она не является обязательной. @Override – это дополнительный самоконтроль, чтобы не ошибиться в сигнатуре переопределяемого метода.

После внесенных изменений, метод printX() вызываемый на дочерних экземплярах, уже является методом этих подклассов и возвращает значение поля х этих подклассов.

Остальные методы, работают как и прежде. По большому счету методы printA(), printB() и printC() можно убрать, так как они были призваны выводить значение поля х для соответствующих классов. А эту работу уже выполняет метод printX().

Можно ли теперь внутри подкласса обратиться к переопределенному методу суперкласса? Да, можно, если уточнить имя метода словом super. Чтобы продемонстрировать это изменим методы printC() и printB().

P0004

Вывод программы немного изменился, так как изменился и код подклассов, в которых мы вызываем переопределенные методы суперклассов.

P0005

P0006

Теперь в методах printB() и printC() вызывается переопределенный метод printX() суперкласса, что видно из вывода программы.

Если бы не стояло ключевое слово super, то вызывался бы метод printX() текущего класса.

Как видите это несколько похоже на обращение к замещенным полям.

Несмотря на то, что во многих случаях Java одинаково работает с полями и методами, переопределение методов совсем не похоже на затенение полей. Вы можете сослаться на затененное поле, приведя тип объекта к необходимому родительскому классу, но с помощью этой техники вы не сможете вызвать переопределенный метод экземпляра.

Даже если вы присвоите экземпляр подкласса переменной суперкласса или приведете переменную подкласса к переменной суперкласса, то при вызове замещенного метода, будет вызываться метод подкласса, а не метод суперкласса. Например, в классе АВС на экземпляре bc мы можем вызвать метода printX() и в результате получим X->Класс C, то есть вызовется метод класса С. Все это справедливо для обычных методов. Для переопределенных статических, другая ситуация.

Представляю какая сейчас у вас каша в голове! :) Но ни чего попробуем разобраться на еще одном примере.

И так сперва надо усвоить, что переопределенные статические и обычные методы вызываются по разному. Надо вспомнить, что статические методы правильно вызывать через имя класса, на то они и статические. Хотя конечно их можно вызывать и на экземпляре.

Если ссылка на подкласс хранится в переменной определенной как суперкласс, то на выбор исполнения статического метода не влияет какой объект находится по ссылке. Статические методы всегда вызываются по версии ссылки, а обычные методы вызываются по версии объекта на который указывает ссылка.

Затененные же поля, всегда вызываются по версии ссылки. Так же и методы объявленные как private всегда вызываются по версии ссылки.

Чтобы все встало на свои места приведем пример.

P0007

P0010P0008

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

P0009

Как уже было сказано, переопределенные методы позволяют Java поддерживать полиморфизм времени выполнения. Большое значение полиморфизма для объектно-ориентированного программирования обусловлено следующей причиной: он позволяет общему классу указывать методы, которые станут общими для всех его производных классов, в то же время позволяя подклассам определять конкретные реализации некоторых или всех этих методов. Переопределенные методы — способ реализации аспекта полиморфизма под названием “один интерфейс, множество методов”.

12 комментариев:

  1. Спасибо ! наконец то поставил точку в бесконечной череде вопросов по этому поводу !

    ОтветитьУдалить
  2. Доброго времени суток. Я сноб, потому за ранее извиняюсь. Мне кажется Вы могли бы еще проще написать. Хотя это лучшее определение из тех что мне встречались :)

    ОтветитьУдалить
  3. Может и мог бы :) Но уж лень переделывать или дописывать то, что уже написано...

    ОтветитьУдалить
  4. Хороший труд. Сапасибо. Буду работать над материалом.

    ОтветитьУдалить
  5. Переопределяем статический метод g() класса А?
    Переопределиться только метод f() класса А

    ОтветитьУдалить
  6. Как же режет глаза комментарий :"//переопределяем статический метод..."

    ОтветитьУдалить
    Ответы
    1. Почему? Хочу напомнить что это блог для изучающих Java

      Удалить