что такое переопределение метода java
Все о переопределении в Java
У нас на этой неделе практически юбилей — стартует пятая группа «Разработчик Java», а это значит, что мы снова делимся всякими полезностями.
Если вы хотите убедиться, что вы переопределяете правильным образом, взгляните на это руководство, которое описывает различные средства, имеющиеся в вашем распоряжении, и некоторые подводные камни, которых следует остерегаться.
Дочерний класс может переопределить методы экземпляра своего родительского класса. Это называется переопределением метода. Сигнатура (тип возврата, тип параметров, количество параметров и порядок параметров) должна быть такой же, какой была определена в родительском классе. Переопределение метода выполняется для достижения полиморфизма во время выполнения программы.
Что такое полиморфизм?
Полиморфизм позволяет вам определить один интерфейс и иметь для него несколько реализаций. Это один из основных принципов объектно-ориентированного программирования. Слово «полиморфизм» буквально означает «состояние наличия многих форм» или «способность принимать разные формы». При применении к объектно-ориентированным языкам программирования, таким как Java, он описывает способность языка обрабатывать объекты разных типов и классов через единый, однородный интерфейс.
Что такое полиморфизм времени выполнения (или отправка динамического метода?)
Переопределенный метод вызывается в соответствии с объектом, которому принадлежит метод, а не по типу ссылки.
В чём польза полиморфизма времени выполнения?
Статический или динамический полиморфизм?
Private, final и static методы используют статические привязки и связаны компилятором, в то время как виртуальные методы связываются во время выполнения на основе обрабатываемого объекта.
Используйте аннотацию Override, чтобы компилятор мог проверить, что вы действительно переопределяете метод, когда вы на это рассчитываете. Таким образом, если вы совершаете распространенную ошибку, например, опечатку в имени метода или неправильно задаете параметры, вы будете предупреждены о том, что ваш метод фактически не переопределяет в то время, как вы уверены в обратном. Во-вторых, это улучшает читаемость кода, делая переопределение более очевидным.
Кроме того, начиная с Java 1.6 вы можете воспользоваться Override с теми же целями, чтобы отметить, когда метод реализует интерфейс.
Правила динамического полиморфизма
Изменение сигнатуры метода
Если мы используем переопределение, то метод переопределения должен иметь ту же сигнатуру, что и переопределенный метод. Вы можете соответствующим образом изменить сигнатуру метода в своем дочернем классе, то есть количество аргументов, тип и порядок аргументов и тип возвращаемого значения. Но это называется перегрузкой.
Тип возврата метода
Типы возвращаемых данных могут варьироваться в зависимости от методов, которые переопределяют друг друга, если типы возврата являются ссылочными типами. Java поддерживает ковариантные возвращения — специализацию типа возврата к подтипу. Объявление метода d1 с типом возврата R1 заменит возвращаемое значение метода d2 с возвращаемым типом R2 тогда и только тогда, когда выполняются следующие условия:
Ковариантный тип возврата
Возвращение коварианта означает, что при переопределении метода возвращаемый тип переопределяющего метода разрешен как подтип возвращаемого типа переопределенного метода.
Чтобы прояснить это с помощью примера, общим случаем является Object.clone (), который объявляется для возврата типа объекта. Вы можете переопределить это в своем классе следующим образом:
Переопределение статического метода (или) Связывание метода
Связывание статических переменных
Final и private методы
Переопределение уровней доступа
Переопределение с super()
Переопределение с абстракцией
Переопределение с исключениями
Переопределение из внутренних приватных классов
Переопределение и перегрузка
Переопределение методов экземпляра против статических методов
Переопределение методов экземпляра против статических переменных
Конструктор с super()
Переопределение другого и того же пакетов
Правила ребенок-родитель: последствия переопределения
Методы экземпляра предпочтительнее default методов интерфейса.
Программы, содержащие комментарии для иллюстрации использования и последствий при переопределении, могут иметь некоторые CE и RE.
Как всегда будем рады видеть ваши комментарии или вопросы.
BestProg
Содержание
Поиск на других ресурсах:
1. Что означает переопределение метода?
Переопределение метода – это случай, когда подкласс и суперкласс содержат методы, которые имеют одинаковое имя и сигнатуру типов (рисунок 1). Переопределение метода возникает в случаях, когда классы образовывают иерархию наследования.
Рисунок 1. Пример переопределения метода method() суперкласса A в подклассе B
2. Какое отличие между переопределением и перегрузкой метода?
В иерархии наследования методы суперклассов и подклассов могут быть переопределенными и перегруженными.
Между переопределением и перегрузкой метода есть следующее отличие:
3. Правило взаимодействия между методами суперкласса и подкласса, которые имеют одинаковые имена и сигнатуру. Пример
Если в суперклассе и подклассе реализованы методы, которые носят одинаковое имя и сигнатуру типов параметров, то действует следующее правило:
На рисунке 3 схематически изображено правило взаимодействия между одноименными методами суперкласса и подкласса.
Рисунок 3. Демонстрация переопределения метода суперкласса в методе подкласса
Ниже приведен текст программы демонстрирующей переопределение методов, изображенных на рисунке 3.
Результат выполнения программы
Иногда нужно получить доступ из метода подкласса к методу суперкласса, который имеет такое же имя и сигнатуру параметров. В этом случае используется ключевое слово super.
Рисунок 4. Вызов метода суперкласса из метода подкласса с помощью ключевого слова super
Текст программы, которая демонстрирует рисунок 4 следующий
Результат выполнения программы
5. Пример, который демонстрирует перегрузку методов в классах, которые образовывают иерархию наследования
Если классы образовывают иерархию путем наследования, то в этих классах имена методов могут совпадать.
Метод считается перегруженным (не переопределенным) в случае, если:
Таким образом, методы с разными сигнатурами считаются перегруженными а не переопределенными.
Рисунок 5. Перегрузка метода с именем method() в классах, которые образовывают иерархию
Текст программы, которая демонстрирует рисунок 5 следующий
Результат работы программы
6. Что такое динамическая диспетчеризация методов? Пример реализации полиморфизма в Java
Динамическая диспетчеризация методов есть один из наиболее эффективных принципов объектно-ориентированного программирования.
Динамическая диспетчеризация методов – это специальный механизм, который позволяет вызвать переопределенный метод в процессе выполнения программы а не во время компиляции. Динамическая диспетчеризация методов важна при реализации полиморфизма.
Рисунок 6. Демонстрация динамической диспетчеризации методов для трех классов, которые образуют иерархию
В начале кода демонстрации (рисунок 6) объявляется ссылка refA на базовый класс A
Ниже приведен программный код, который демонстрирует динамическую диспетчеризацию методов, изображенную на рисунке 6.
Результат выполнения программы
7. Пример, демонстрирующий способы реализации полиморфизма (динамической диспетчеризации методов)
Полиморфизм может быть реализован в программах двумя способами:
Переопределение методов
1. Что такое переопределение методов?
Если в иерархии классов совпадают имена и сигнатуры типов методов из подкласса и супер класса, то говорят, что метод из подкласса переопределяет метод из супер-класса.
Переопределение методов выполняется только в том случае, если имена и сигнатуры типов обоих методов одинаковы. В противном случае оба методы считаются перегружаемыми.
Когда переопределенный метод вызывается из своего подкласса, он всегда ссылается на свой вариант, определенный в подклассе. А вариант метода, определенный в супер классе, будет скрыт. Из метода someMethod() будет вызван метод того же класса N :
Рассмотрим более конкретный пример, который показывает зачем переопределяются методы.
Результат выполнения кода:
2. Методы подставки
После выхода Java 5 появилась возможность при переопределении методов указывать другой тип возвращаемого значения, в качестве которого можно использовать только типы, находящиеся ниже в иерархии наследования, чем исходный тип. Такие типы еще называются ковариантными.
3. Переопределение и статические методы
Статические методы не могут быть переопределены. Класс наследник может объявлять метод с такой же сигнатурой, что и супер класс, но это не будет переопределением. При вызове переопределенного метода JVM выбирает нужный вариант основываясь на типе объекта. Вызов же статического метода происходит без объекта. Версия вызываемого статического метода всегда определяется на этапе компиляции.
При использовании ссылки для доступа к статическому члену компилятор при выборе метода учитывает тип ссылки, а не тип объекта, ей присвоенного.
Создадим в супер классе и наследнике статические методы с одинаковой сигнатурой:
4. Переопределение методов в классах наследниках
5. Аннотация @Override
Необязательная аннотация @Override используется с методом для указания того, что он переопределен. Если метод переопределен неверно, код не будет компилироваться:
Перегрузка и переопределение методов в Java
Изучите основы перегрузки и переопределения методов в Java.
1. Обзор
Перегрузка и переопределение методов являются ключевыми понятиями языка программирования Java, и как таковые они заслуживают углубленного изучения.
В этой статье мы изучим основы этих концепций и посмотрим, в каких ситуациях они могут быть полезны.
2. Перегрузка метода
Перегрузка методов-это мощный механизм, который позволяет нам определять API-интерфейсы связного класса. Чтобы лучше понять, почему перегрузка методов является такой ценной функцией, давайте рассмотрим простой пример.
Предположим, что мы написали наивный служебный класс, который реализует различные методы умножения двух чисел, трех чисел и так далее.
Проще говоря, мы можем реализовать перегрузку метода двумя различными способами:
2.1. Различное количество аргументов
2.2. Аргументы различных типов
Кроме того, правомерно определить класс Multiplier с обоими типами перегрузки методов:
Чтобы понять, почему – давайте рассмотрим следующий пример:
В этом случае код просто не будет компилироваться из – за неоднозначности вызова метода – компилятор не будет знать, какую реализацию multiply() вызывать.
2.3. Тип Продвижения
Проще говоря, один данный тип неявно повышается до другого, когда нет соответствия между типами аргументов, переданных перегруженному методу, и конкретной реализацией метода.
Чтобы более четко понять, как работает продвижение типов, рассмотрим следующие реализации метода multiply() :
Давайте посмотрим быстрый модульный тест, чтобы продемонстрировать продвижение типа:
И наоборот, если мы вызываем метод с соответствующей реализацией, продвижение типа просто не происходит:
Вот краткое описание правил продвижения типов, которые применяются для перегрузки методов:
2.4. Статическая привязка
Возможность связать конкретный вызов метода с телом метода называется связыванием.
В случае перегрузки метода привязка выполняется статически во время компиляции, поэтому она называется статической привязкой.
Компилятор может эффективно установить привязку во время компиляции, просто проверив сигнатуры методов.
3. Переопределение метода
Переопределение методов позволяет нам предоставлять детальные реализации в подклассах для методов, определенных в базовом классе.
Теперь давайте посмотрим, как использовать переопределение метода, создав простое отношение на основе наследования (“is-a”).
А вот надуманный подкласс:
Здесь ясно видно, что если приложение использует экземпляры класса Vehicle , то оно также может работать с экземплярами Car , поскольку обе реализации метода accelerate () имеют одинаковую сигнатуру и один и тот же тип возвращаемого значения.
Давайте напишем несколько модульных тестов для проверки классов Vehicle и Car :
В нашем случае у нас есть доступ к исходному коду для обоих классов, поэтому мы можем ясно видеть, что вызов метода accelerate() на базовом экземпляре Vehicle и вызов accelerate() на экземпляре Car вернет разные значения для одного и того же аргумента.
Таким образом, следующий тест демонстрирует, что переопределенный метод вызывается для экземпляра Car :
3.1. Взаимозаменяемость Типов
Самая большая проблема с переопределением методов заключается в том, что некоторые конкретные реализации методов в производных классах могут не полностью соответствовать LSP и, следовательно, не сохранять заменяемость типов.
Конечно, допустимо сделать переопределенный метод, чтобы принимать аргументы разных типов и возвращать другой тип, но при полном соблюдении этих правил:
3.2. Динамическая привязка
Учитывая, что переопределение метода может быть реализовано только с наследованием, где существует иерархия базового типа и подтипов, компилятор не может определить во время компиляции, какой метод вызывать, поскольку и базовый класс, и подклассы определяют одни и те же методы.
Как следствие, компилятору необходимо проверить тип объекта, чтобы узнать, какой метод следует вызывать.
Поскольку эта проверка происходит во время выполнения, переопределение метода является типичным примером динамической привязки.
4. Заключение
В этом уроке мы узнали, как реализовать перегрузку методов и переопределение методов, а также рассмотрели некоторые типичные ситуации, в которых они полезны.
Полиморфизм и переопределение
— Амиго, ты любишь китов?
— Китов? Не, не слышал.
— Этот как корова, только больше и плавает. Кстати, киты произошли от коров. Ну, или имели общего с ними предка. Не столь важно.
1) Переопределение метода.
Представь, что ты для игры написал класс «Корова». В нем есть много полей и методов. Объекты этого класса могут делать разные вещи: идти, есть, спать. Еще коровы звонят в колокольчик, когда ходят. Допустим, ты реализовал в классе все до мелочей.
А тут приходит заказчик проекта и говорит, что хочет выпустить новый уровень игры, где все действия происходят в море, а главным героем будет кит.
Ты начал проектировать класс «Кит» и понял, что он лишь немного отличается от класса «Корова». Логика работы обоих классов очень похожа, и ты решил использовать наследование.
Класс «Корова» идеально подходит на роль класса-родителя, там есть все необходимые переменные и методы. Достаточно только добавить киту возможность плавать. Но есть проблема: у твоего кита есть ноги, рога и колокольчик. Ведь эта функциональность реализована внутри класса «Корова». Что тут можно сделать?
К нам на помощь приходит переопределение (замена) методов. Если мы унаследовали метод, который делает не совсем то, что нужно нам в нашем новом классе, мы можем заменить этот метод на другой.
Как же это делается? В нашем классе-потомке мы объявляем такой же метод, как и метод класса родителя, который хотим изменить. Пишем в нем новый код. И все – как будто старого метода в классе-родителе и не было.
Вот как это работает:
В классе Whale переопределен метод printName();
Код | Описание |
---|---|
Ни о каком старом методе мы и не знаем. |
— Честно говоря, ожидаемо.
2) Но это еще не все.
На экран будет выведена надпись Я – белая Я – кит
Код | Описание |
---|---|
На экран будет выведена надпись Я – белая Я – кит |
Главное, не в каком классе написан метод, а какой тип (класс) объекта, у которого этот метод вызван.
— Наследовать и переопределять можно только нестатические методы. Статические методы не наследуются и, следовательно, не переопределяются.
Вот как выглядит класс Whale после применения наследования и переопределения методов:
Код | Описание |
---|---|
Вот как выглядит класс Whale, после применения наследования и переопределения метода. Ни о каком старом методе printName мы и не знаем. |
3) Приведение типов.
Тут есть еще более интересный момент. Т.к. класс при наследовании получает все методы и данные класса родителя, то объект этого класса разрешается сохранять (присваивать) в переменные класса родителя (и родителя родителя, и т.д., вплоть до Object). Пример:
Код | Описание |
---|---|
На экран будет выведена надпись Я – белая | |
На экран будет выведена надпись Я – белая | |
На экран будет выведена надпись Whale@da435a Метод toString() унаследован от класса Object. |
— Очень интересно. А зачем это может понадобиться?
— Это ценное свойство. Позже ты поймешь, что очень, очень ценное.
4) Вызов метода объекта (динамическая диспетчеризация методов).
Вот как это выглядит:
Код | Описание |
---|---|
На экран будет выведена надпись Я – кит. | |
На экран будет выведена надпись Я – кит. |
Обрати внимание, что на то, какой именно метод printName вызовется, от класса Cow или Whale, влияет не тип переменной, а тип – объекта, на который она ссылается.
В переменной типа Cow сохранена ссылка на объект типа Whale, и будет вызван метод printName, описанный в классе Whale.
— Это не просто для понимания.
— Да, это не очень очевидно. Запомни главное правило:
Набор методов, которые можно вызвать у переменной, определяется типом переменной. А какой именно метод/какая реализация вызовется, определяется типом/классом объекта, ссылку на который хранит переменная.
— Ты будешь постоянно сталкиваться с этим, так что скоро поймешь и больше никогда не забудешь.
5) Расширение и сужение типов.
Для ссылочных типов, т.е. классов, приведение типов работает не так, как для примитивных типов. Хотя у ссылочных типов тоже есть расширение и сужение типа. Пример:
Расширение типа | Описание | ||
---|---|---|---|
Классическое расширение типа. Теперь кита обобщили (расширили) до коровы, но у объекта типа Whale можно вызывать только методы, описанные в классе Cow.6) А теперь еще на закуску. Вызов оригинального методаИногда тебе хочется не заменить унаследованный метод на свой при переопределении метода, а лишь немного дополнить его.
— Гм. Ничего себе лекция. Мои робо-уши чуть не расплавились. — Да, это не простой материал, он один из самых сложных. Профессор обещал подкинуть ссылок на материалы других авторов, чтобы ты, если все-таки что-то не поймешь, мог устранить этот пробел.
|