Что такое полиморфизм в Java
Что такое полиморфизм
Определение полиморфизма звучит устрашающе 🙂
Так о каких формах идет речь? Давайте сначала приведем примеры и покажем, как на практике проявляется полиморфизм, а потом снова вернемся к его определению.
Как проявляется полиморфизм
Дело в том, что если бы в Java не было принципа полиморфизма, компилятор бы интерпретировал это как ошибку:
Как видите, методы на картинке отличаются значениями, которые они принимают:
Однако, поскольку в Java используется принцип полиморфизма, компилятор не будет воспринимать это как ошибку, потому что такие методы будут считаться разными:
Теперь Вы можете понять, почему часто этот принцип описывают фразой:
Это предполагает, что мы можем заполнить одно название (один интерфейс), по которому мы сможем обращаться к нескольким методам.
Перегрузка методов
Переопределение методов родителя
Когда мы наследуем какой-либо класс, мы наследуем и все его методы. Но если нам хочется изменить какой-либо из методов, который мы наследуем, мы можем всего-навсего переопределить его. Мы не обязаны, например, создавать отдельный метод с похожим названием для наших нужд, а унаследованный метод будет «мертвым грузом» лежать в нашем классе.
Именно то, что мы можем создать в классе-наследнике класс с таким же названием, как и класс, который мы унаследовали от родителя, и называется переопределением.
Пример
Представим, что у нас есть такая структура:
Поэтому, в классах-наследниках мы переопределяем метод voice(), чтобы мы в консоли получали «Мяу», «Гав» и «Муу».
Так что же такое полиморфизм
Давайте снова посмотрим на определение, которое мы давали в начале статьи:
Выглядит понятнее, правда? Мы показали, как можно:
Надеемся, наша статья была Вам полезна. Записаться на наши курсы по Java можно у нас на сайте.
Полиморфизм и переопределение
— Амиго, ты любишь китов?
— Китов? Не, не слышал.
— Этот как корова, только больше и плавает. Кстати, киты произошли от коров. Ну, или имели общего с ними предка. Не столь важно.

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) А теперь еще на закуску. Вызов оригинального методаИногда тебе хочется не заменить унаследованный метод на свой при переопределении метода, а лишь немного дополнить его.
— Гм. Ничего себе лекция. Мои робо-уши чуть не расплавились. — Да, это не простой материал, он один из самых сложных. Профессор обещал подкинуть ссылок на материалы других авторов, чтобы ты, если все-таки что-то не поймешь, мог устранить этот пробел. Java Challengers #3: Полиморфизм и наследованиеМы продолжаем перевод серии статей с задачками по Java. Прошлый пост про строки вызвал на удивление бурную дискуссию. Надеемся, что мимо этой статьи вы тоже не пройдете мимо. И да — мы приглашаем теперь на юбилейный десятый поток нашего курса «Разработчик Java». Согласно легендарному Венкату Субраманиам (Venkat Subramaniam) полиморфизм является самым важным понятием в объектно — ориентированном программировании. Полиморфизм — или способность объекта выполнять специализированные действия на основе его типа — это то, что делает Java — код гибким. Шаблоны проектирования, такие как Команда (Command), Наблюдатель (Observer), Декоратор (Decorator), Стратегия (Strategy), и многие другие, созданные бандой четырех (Gang Of Four), все используют ту или иную форму полиморфизма. Освоение этой концепции значительно улучшит вашу способность продумывать программные решения. Вы можете взять исходный код для этой статьи и поэксперементировать здесь: https://github.com/rafadelnero/javaworld-challengers Интерфейсы и наследование в полиморфизмеВ этой статье мы сфокусируемся на связи между полиморфизмом и наследованием. Главное иметь в виду, что полиморфизм требует наследования или реализации интерфейса. Вы можете увидеть это на примере ниже с Дюком ( Duke ) и Джагги ( Juggy ): Вывод этого кода будет таким: Перегрузка (overloading) метода — это полиморфизм? Многие программисты путают отношение полиморфизма с переопределением методов (overriding) и перегрузкой методов (overloading). Фактически, только переопределение метода — это истинный полиморфизм. Перегрузка использует то же имя метода, но разные параметры. Полиморфизм — это широкий термин, поэтому всегда будут дискуссии на эту тему. Какова цель полиморфизмаБольшим преимуществом и целью использования полиморфизма является уменьшение связанности клиентского класса с реализацией. Вместо того чтобы хардкодить, клиентский класс получает реализацию зависимости для выполнения необходимого действия. Таким образом, клиентский класс знает минимум для выполнения своих действий, что является примером слабого связывания. Чтобы лучше понять цель полиморфизма, взгляните на SweetCreator :
Ковариантные возвращаемые типы при переопределении методаМожно изменить тип возвращаемого значения переопределенного метода если это ковариантный тип. Ковариантный тип в основном является подклассом возвращаемого значения. Полиморфизм в базовых классах JavaРассмотрим пример кода, использующий Java Collections API без полиморфизма: Отвратительный код, не так ли? Представьте себе, что вам нужно его сопровождать! Теперь рассмотрим тот же пример с полиморфизмом: Вызов конкретных методов для полиморфного методаМожно вызвать конкретные методы при полиморфном вызове метода, это происходит за счет гибкости. Вот пример: Техника, которую мы используем здесь — это приведение типов (casting) или сознательное изменение типа объекта во время выполнения. Обратите внимание, что вызов определенного метода возможен только при приведении более общего типа к более специфичному типу. Хорошей аналогией было бы сказать явно компилятору: «Эй, я знаю, что я здесь делаю, поэтому я собираюсь привести объект к определенному типу и буду использовать этот метод.» Ключевое слово instanceofКлючевое слово superРешите задачку по полиморфизмуДавайте проверим, что вы узнали о полиморфизме и наследовании. В этой задачке Вам дается несколько методов от Matt Groening’s The Simpsons, от вавам требуется разгадать, какой будет вывод для каждого класса. Для начала внимательно проанализируйте следующий код: Как вы думаете? Каким будет результат? Не используйте IDE, чтобы выяснить это! Цель в том, чтобы улучшить ваши навыки анализа кода, поэтому постарайтесь решить самостоятельно. Выберите ваш ответ (правильный ответ вы сможете найти в конце статьи). A) B) C) D) Что случилось? Понимание полиморфизмаДля следующего вызова метода: вывод будет «I love Sax!». Это потому, что мы передаём строку в метод и у класса Lisa есть такой метод. Для следующего вызова: Теперь смотрите, это немного сложнее: В этом случае на выходе будет «Simpson!». Распространенные ошибки с полиморфизмомРаспространенная ошибка думать, что можно вызвать конкретный метод без использования приведения типа. Другой ошибкой является неуверенность в том, какой метод будет вызван при полиморфном создании экземпляра класса. Помните, что вызываемый метод является методом созданного экземпляра. Также помните, что переопределение метода не является перегрузкой метода. Невозможно переопределить метод, если параметры отличаются. Можно изменить тип возвращаемого значения переопределенного метода, если возвращаемый тип является подклассом. Что нужно помнить о полиморфизмеСозданный экземпляр определяет, какой метод будет вызван при использовании полиморфизма. Аннотация @Override обязывает программиста использовать переопределенный метод; в противном случае возникнет ошибка компилятора. Полиморфизм может использоваться с обычными классами, абстрактными классами и интерфейсами. Большинство шаблонов проектирования зависят от той или иной формы полиморфизма. Единственный способ вызвать нужный ваш метод в полиморфном подклассе — это использовать приведение типов. Можно создать мощную структуру кода, используя полиморфизм. Экспериментируйте. Через это, вы сможете овладеть этой мощной концепцией! ОтветКак всегда приветствую ваши комментарии и вопросы. И ждём у Виталия на открытом уроке. Как полиморфизм реализован внутри JVMПеревод данной статьи подготовлен специально для студентов курса «Разработчик Java». В моей предыдущей статье Everything About Method Overloading vs Method Overriding (“Все о перегрузке и переопределении методов”) были рассмотрены правила и различия перегрузки и переопределения методов. В этой статье мы посмотрим, как обрабатывается перегрузка и переопределение методов внутри JVM. Для примера возьмем классы из предыдущей статьи: родительский Mammal (млекопитающее) и дочерний Human (человек). На вопрос о полиморфизме мы можем посмотреть с двух сторон: с “логической” и “физической”. Давайте сначала рассмотрим логическую сторону вопроса. Логическая точка зренияС логической точки зрения, на этапе компиляции вызываемый метод рассматривается как относящийся к типу ссылки. Но во время выполнения будет вызываться метод объекта, на который указывает ссылка. Это все довольно просто, пока мы остаемся на концептуальном уровне. Но как же JVM обрабатывает это все внутри? Как JVM вычисляет, какой метод должен быть вызван? Также мы знаем, что перегруженные методы (overload) не называются полиморфными и резолвятся во время компиляции. Хотя иногда перегрузку методов называют полиморфизмом времени компиляции или ранним/статическим связыванием. Переопределенные методы (override) резолвятся во время выполнения, так как компилятор не знает, есть ли переопределенные методы в объекте, который присваивается ссылке. Физическая точка зренияКоманда выше покажет две секции байткода. 1. Пул констант. Содержит почти все, что необходимо для выполнения программы. Например, ссылки на методы ( #Methodref ), классы ( #Class ), литералы строк ( #String ). 2. Байткод программы. Выполняемые инструкции байткода. Почему перегрузка методов называется статическим связываниемТаким образом, в случае перегрузки метода, компилятор способен идентифицировать инструкции байткода и адреса методов во время компиляции. Именно поэтому это называют статическим связыванием или полиморфизмом времени компиляции. Почему переопределение методов называется динамическим связываниемДля вызова методов anyMammal.speak() и humanMammal.speak() байткод одинаковый, так как с точки зрения компилятора оба метода вызываются для класса Mammal : Итак, теперь возникает вопрос, если у обоих вызовов одинаковый байткод, как JVM узнает, какой метод вызвать?
JVM использует инструкцию invokevirtual для вызова в Java методов, эквивалентных виртуальным методам C++. В C++ для переопределения метода в другом классе, метод должен быть объявлен как виртуальный (virtual). Но в Java по умолчанию все методы виртуальные (кроме final и static методов), поэтому в дочернем классе мы можем переопределить любой метод. Инструкция invokevirtual принимает указатель на метод, который нужно вызвать ( #4 — индекс в пуле констант). Но ссылка #4 ссылается дальше на другой метод и Class. Все эти ссылки используются совместно для получения ссылки на метод и класс, в котором находится нужный метод. Это также упоминается в спецификации JVM (прим. переводчика: ссылка на JVM spec 2.7):
Это означает, что каждая ссылочная переменная содержит два скрытых указателя: Из приведенных выше рассуждений можно сделать вывод, что ссылка на объект косвенно содержит ссылку/указатель на таблицу, которая содержит все ссылки на методы этого объекта. Java позаимствовала эту концепцию из C ++. Эта таблица известна под различными именами, такими как таблица виртуальных методов (VMT), таблица виртуальных функций (vftable), виртуальная таблица (vtable), таблица диспетчеризации. Мы не можем быть уверены в том, как vtable реализован в Java, потому что это зависит от конкретной JVM. Но мы можем ожидать, что стратегия будет примерно такая же, как и в C ++, где vtable — это структура, похожая на массив, которая содержит имена методов и их ссылки. Всякий раз, когда JVM пытается выполнить виртуальный метод, она запрашивает его адрес в vtable. Для каждого класса существует только одна vtable, это означает, что таблица уникальна и одинакова для всех объектов класса, аналогично объекту Class. Объекты Class подробнее рассмотрены в статьях Why an outer Java class can’t be static и Why Java is Purely Object-Oriented Language Or Why Not. Инструкция invokevirtual заставляет JVM обрабатывать значение в ссылке на метод # 4 не как адрес, а как имя метода, которое нужно искать в vtable для текущего объекта. Полиморфизм в Java – подробно с примерамиПолиморфизм в Java – это концепция ООП, в которой одно имя может иметь много форм. Например, у вас есть смартфон для общения. Режим связи, который вы выбираете, может быть любым. Это может быть звонок, текстовое сообщение, графическое сообщение, почта и т. д. Итак, общая цель – общение, но у них другой подход. Это называется полиморфизмом. Объяснение полиморфизмаУ нас есть один родительский класс, «Счет» с функцией пополнения и снятия. Учетная запись имеет 2 дочерних класса. Операции ввода и вывода одинаковы для Сберегательного и Чекового счетов. Таким образом, унаследованные методы из класса Account будут работать. Изменение требований к программному обеспечению В спецификации требований произошли изменения, что так часто встречается в индустрии программного обеспечения. Вы должны добавить функциональность привилегированного банковского счета с помощью овердрафта.
Итак, метод вывода для привилегированных нужд должен быть реализован заново. Но вы не меняете проверенный кусок кода в Сберегательном и Чековом счете. Это преимущество ООП. Шаг 1) Такой, что при вызове «изъятого» метода для сохранения учетной записи выполняется метод из класса родительской учетной записи. Шаг 2) Но когда вызывается метод «Снять» для привилегированной учетной записи (средство овердрафта), выполняется метод вывода, определенный в привилегированном классе. Это полиморфизм. Переопределение методаПереопределение метода – переопределение метода суперкласса в подклассе. Правила для переопределения метода Разница между перегрузкой и переопределениемПерегрузка метода находится в одном классе, где несколько методов имеют одно и то же имя, но разные подписи. Переопределение метода – это когда один из методов суперкласса переопределяется в подклассе. В этом случае подпись метода остается неизменной. Что такое динамический полиморфизм?Динамический полиморфизм – это механизм, с помощью которого можно определить несколько методов с одинаковыми именами и сигнатурами в суперклассе и подклассе. Вызов переопределенного метода разрешается во время выполнения. Пример динамического полиморфизмаСсылочная переменная суперкласса может ссылаться на объект подкласса. Здесь ссылочная переменная “obj” относится к родительскому классу, но объект, на который она указывает, принадлежит к дочернему классу (как показано на диаграмме). obj.treatPatient() выполнит метод TreatPatient() для подкласса – Surgeon Если для вызова метода используется ссылка на базовый класс, то вызываемый метод определяется JVM в зависимости от объекта, на который указывает ссылка Например, хотя obj является ссылкой на Doctor, он вызывает метод Surgeon, так как он указывает на объект Surgeon. Это определяется во время выполнения и, следовательно, называется динамическим или динамическим полиморфизмом. super ключевое словоЧто, если метод TreatPatient в классе Surgeon хочет выполнить функциональность, определенную в классе Doctor, а затем выполнить свою собственную специфическую функциональность? В этом случае ключевое слово super может использоваться для доступа к методам родительского класса из дочернего класса. Метод TreatPatient в классе Surgeon может быть записан как: Ключевое слово super может использоваться для доступа к любому члену данных или методам суперкласса в подклассе. Шаг 1) Скопируйте следующий код в редактор Шаг 2) Сохранение, компиляция Запустите код. Соблюдайте вывод. Шаг 3) Раскомментируйте строки № 6-9. Сохранить, скомпилировать Запустите код. Соблюдайте вывод. Шаг 4) Раскомментируйте строку # 10. Сохраните и Скомпилируйте код. Шаг 5) Ошибка =? Это потому, что подкласс не может получить доступ к закрытым членам суперкласса. Разница между статическим и динамическим полиморфизмомОшибки, если таковые имеются, устраняются во время компиляции. Поскольку код не выполняется во время компиляции, отсюда и название static. В случае, если ссылочная переменная вызывает переопределенный метод, вызываемый метод определяется объектом, на который указывает ваша ссылочная переменная. Это может быть определено только во время выполнения, когда код находится в процессе выполнения, поэтому имя динамическое. |




















