Полиморфизм
Полиморфизм (polymorphism) — это понятие из объектно-ориентированного программирования, которое позволяет разным сущностям выполнять одни и те же действия. При этом неважно, как эти сущности устроены внутри и чем они различаются.
С греческого языка слово «полиморфизм» означает «многообразие». Термин используют и в других сферах. Скажем, полиморфизм в биологии — способность какого-то вида существовать в разных формах.
Пример полиформности в природе — пчелы: есть рабочие пчелы, матка, трутни. Они разные, но все могут летать независимо от того, что это за пчела. По похожему принципу работает и полиморфизм в ООП.
Освойте профессию
«Fullstack-разработчик на Python»
Профессия / 12 месяцев
Fullstack-разработчик на Python
Создавайте веб-проекты самостоятельно
Fullstack-разработчик на Python
Fullstack-разработчики могут в одиночку сделать IT-проект от архитектуры до интерфейса. Их навыки востребованы у работодателей, особенно в стартапах. Научитесь программировать на Python и JavaScript и создавайте сервисы с нуля.
Например, есть две разных сущности: картинка и видео. И тем, и другим можно поделиться: отправить в личное сообщение другому человеку. Программист может сделать два разных метода — один для картинки, другой для видео. А может воспользоваться полиморфизмом и создать один метод «Отправить» для обеих сущностей.
Такой метод будет называться полиморфным. Плюс этого подхода — разработчик пишет меньше кода и не повторяется.
Чтобы лучше понять, что такое полиморфизм и как он работает, советуем прочитать статью про ООП. Полиморфизм — один из четырех основных принципов этого способа программирования
Для чего нужен полиморфизм
- Облегчает написание кода. Не нужно придумывать десять разных методов: отправить одно, другое, третье. Есть один метод, который можно применять к разным сущностям и не задумываться.
- Позволяет масштабировать решения. Если понадобится отправлять не только видео и картинки, но и текст, это можно будет сделать той же командой.
- Делает код более читаемым. Разработчику не нужно разбираться, что делает десяток методов с похожими названиями. Есть один метод, и по его названию все понятно.
- Помогает точно понимать, чего ждать от разных методов, то есть делает код более предсказуемым. Не может быть такого, что метод «Отправить» вдруг окажется методом не для картинки, а для видео.
С понятием должен быть знаком любой разработчик. Множество языков программирования используют полиморфизм: C, C++, Java, Python и другие. Не все эти языки чисто объектно-ориентированные: некоторые устроены так, что с ними можно работать в разных парадигмах программирования. Так что столкнуться с полиморфизмом может каждый.
Станьте Fullstack-разработчик на Python и найдите стабильную работу
на удаленке
Полиморфизм как принцип ООП
В объектно-ориентированном программировании четыре основных принципа: инкапсуляция, абстракция, наследование и полиморфизм. Это связанные между собой вещи — без одного не работало бы и другое.
Инкапсуляция — это создание сущностей как «вещей в себе». Классы должны работать независимо друг от друга: если какая-то сущность, например, удалится, это не повлияет на принцип работы остальных.
Абстракция — это принцип, когда какие-то общие вещи сводятся до набора абстрактных признаков. То есть мы имеем не абсолютно разные классы «картинка», «видео», «текст», а абстрактный класс «контент».
Наследование — это возможность делать на основе одних сущностей другие. Обычно в качестве «родителя» используются абстрактные сущности, а от них наследуются уже более конкретные. То есть если родитель — «контент», то дети — «картинка», «видео», «текст». Это все подвиды контента и его наследники.
Чтобы реализовать полиморфизм, нужны как минимум абстракция и наследование. Они помогают сделать абстрактный класс, в нем — абстрактный «общий» метод, а потом унаследовать разные реализации этого метода. В итоге название одно, а механика разная в зависимости от подтипа. Сейчас разберемся подробнее.
Что такое полиморфный метод и как его создают
Полиформный — это многообразный: формы различаются, а действие одно и то же. Тот же самый метод «Отправить» из примера выше может быть реализован по-разному для видео и картинки. Но вызывается он одинаково для всех видов контента и выполняет одну и ту же задачу. Вот как это работает.
Сначала программист создает общий класс. Например, «контент». В нем он описывает вещи, общие для всего контента: свойства и методы. Свойства — это признаки, то, что есть у контента: количество лайков, возможность комментирования и так далее. А методы — это действия, то есть команды: контент можно лайкнуть, открыть на отдельной вкладке, репостнуть или отправить в личное сообщение.
У общего класса — абстрактные, общие методы. Контент можно отправить, но как — пока непонятно. Зато уже можно описать, как будет выглядеть эта команда: как она называется, что в нее нужно передать, какое сообщение выдать после этого. Это своего рода каркас будущего конкретного действия.
Затем разработчик создает производные классы. Это наследники общего класса: они более конкретные, у них могут быть дополнительные свойства и методы. Например, видео можно поставить на паузу, а картинку — нет. А еще у них сохраняются абстрактные свойства и методы родителя: их переопределяют, чтобы они тоже работали конкретнее.
У производных классов — свои реализации общих методов. Например, в классе «контент» есть метод «отправить». Он же есть и в производных классах. В классе «картинка» может быть свой код для отправки, а в классе «видео» — свой. Они могут быть похожи или различаться. Но название у них окажется одинаковым, и делать они будут одно и то же.
Можно создавать объекты производных классов и пользоваться их методами. Абстрактные классы существуют как бы в вакууме: нельзя создать объект, который будет принадлежать такому классу. Среди реальных объектов не может быть «просто контента», который не является ни текстом, ни картинкой, ни видео, ни еще чем-то. Соответственно, абстрактные методы никто не будет вызывать. А вот переопределенные методы из производных классов — вполне реальные, ими можно пользоваться в разных ситуациях.
Формы полиморфизма
Существуют разные виды полиморфизма. Вообще-то классификация довольно широкая и начинающему легко в ней запутаться, поэтому мы решили ограничиться только основными формами.
Полиморфизм подтипов. Это полиморфность «по умолчанию»: когда в ООП говорят о полиморфизме, обычно имеют в виду его. Выше мы рассказывали именно про такой тип. Это возможность использовать одни и те же команды, или интерфейсы, для разных сущностей — подтипов.
Параметрический полиморфизм. Его еще называют обобщенным полиморфизмом. В нем для команды не имеет значения, какую сущность ей прислали: для всех возможных классов будет использоваться один код. Такой полиморфизм считается «истинным» и делает код универсальнее, но реализовать его сложнее.
Полиморфизм ad hoc. Этот вид полиморфизма еще называют специализированным. Его иногда противопоставляют параметрическому: идея ad hoc — разный код при одинаковом названии. Часто такой полиморфизм реализуют с помощью перегрузки методов: несколько раз пишут метод с одним и тем же названием, но разным кодом.
Статический и динамический полиморфизм
На более «глубоком», близком к машине уровне полиморфизм можно разделить на две группы — статический и динамический. Разница —в том, когда программа переходит от общего метода к одной из его вариаций.
- Статический — метод переопределяют при компиляции.
- Динамический — при выполнении программы.
Статический полиморфизм реализуют с помощью перегрузки методов, о которой мы рассказывали выше. Динамический — с помощью абстракций. Обычно в объектно-ориентированных языках есть возможность применить оба варианта.
Преимущества полиморфизма
- Код становится аккуратнее: не приходится множить сущности и создавать десяток команд, которые делают одно и то же. Нет «лапши» — бессвязного неструктурированного кода, в котором тяжело разобраться.
- Разработчику удобнее: не нужно каждый раз думать, что делает команда «Отправить» для конкретного вида контента, можно просто написать ее и получить результат.
- Работать с разными сущностями можно одинаково: не обязательно каждый раз узнавать, о каком именно производном классе речь идет на этот раз. Ведь общее полиморфное действие есть для всего.
- Код легче расширять, использовать заново и всячески модифицировать.
Недостатки полиморфизма
- Связь полиморфизма с наследованием порой расценивают как слабое место всей концепции. Если нужен полиформный класс, но для конкретной ситуации не подходит наследование, — реализация может усложниться.
- Не всегда полиморфизм легко реализовать на практике. Поэтому существует довольно много реализаций, которые работают плохо: с багами и необъяснимыми ошибками.
- Полиморфизм может ухудшать производительность кода, делать его более «тяжелым» и медленным. Но тут многое зависит от реализации: скажем, параметрический обычно быстрее, чем ad hoc.
- Новичкам бывает тяжело понять принцип — объяснить полиморфизм простыми словами можно только в связке с другими понятиями ООП. Так что человек на момент изучения уже должен понимать, что такое объектно-ориентированное программирование.
Как начать изучать полиморфизм
Мы советуем начать с основ: сначала разобраться, как работает программирование в целом, потом перейти к принципам ООП и полиморфизму. На всех этапах лучше практиковаться, чтобы закреплять знания, — к тому же на реальных примерах легче понять тот или иной концепт.
Вы можете записаться на наши курсы и начать учиться уже сейчас. Обещаем много реальных задач и интересной практики!
Java Challengers #3: Полиморфизм и наследование
Мы продолжаем перевод серии статей с задачками по Java. Прошлый пост про строки вызвал на удивление бурную дискуссию. Надеемся, что мимо этой статьи вы тоже не пройдете мимо. И да — мы приглашаем теперь на юбилейный десятый поток нашего курса «Разработчик Java».
Согласно легендарному Венкату Субраманиам (Venkat Subramaniam) полиморфизм является самым важным понятием в объектно — ориентированном программировании. Полиморфизм — или способность объекта выполнять специализированные действия на основе его типа — это то, что делает Java — код гибким. Шаблоны проектирования, такие как Команда (Command), Наблюдатель (Observer), Декоратор (Decorator), Стратегия (Strategy), и многие другие, созданные бандой четырех (Gang Of Four), все используют ту или иную форму полиморфизма. Освоение этой концепции значительно улучшит вашу способность продумывать программные решения.
Вы можете взять исходный код для этой статьи и поэксперементировать здесь: https://github.com/rafadelnero/javaworld-challengers
Интерфейсы и наследование в полиморфизме
В этой статье мы сфокусируемся на связи между полиморфизмом и наследованием. Главное иметь в виду, что полиморфизм требует наследования или реализации интерфейса. Вы можете увидеть это на примере ниже с Дюком ( Duke ) и Джагги ( Juggy ):
public abstract class JavaMascot < public abstract void executeAction(); >public class Duke extends JavaMascot < @Override public void executeAction() < System.out.println("Punch!"); >> public class Juggy extends JavaMascot < @Override public void executeAction() < System.out.println("Fly!"); >> public class JavaMascotTest < public static void main(String. args) < JavaMascot dukeMascot = new Duke(); JavaMascot juggyMascot = new Juggy(); dukeMascot.executeAction(); juggyMascot.executeAction(); >>
Вывод этого кода будет таким:
Punch! Fly!
Так как определены конкретные реализации, то будут вызваны методы и Duke и Juggy .
Перегрузка (overloading) метода — это полиморфизм? Многие программисты путают отношение полиморфизма с переопределением методов (overriding) и перегрузкой методов (overloading). Фактически, только переопределение метода — это истинный полиморфизм. Перегрузка использует то же имя метода, но разные параметры. Полиморфизм — это широкий термин, поэтому всегда будут дискуссии на эту тему.
Какова цель полиморфизма
Большим преимуществом и целью использования полиморфизма является уменьшение связанности клиентского класса с реализацией. Вместо того чтобы хардкодить, клиентский класс получает реализацию зависимости для выполнения необходимого действия. Таким образом, клиентский класс знает минимум для выполнения своих действий, что является примером слабого связывания.
Чтобы лучше понять цель полиморфизма, взгляните на SweetCreator :
public abstract class SweetProducer < public abstract void produceSweet(); >public class CakeProducer extends SweetProducer < @Override public void produceSweet() < System.out.println("Cake produced"); >> public class ChocolateProducer extends SweetProducer < @Override public void produceSweet() < System.out.println("Chocolate produced"); >> public class CookieProducer extends SweetProducer < @Override public void produceSweet() < System.out.println("Cookie produced"); >> public class SweetCreator < private ListsweetProducer; public SweetCreator(List sweetProducer) < this.sweetProducer = sweetProducer; >public void createSweets() < sweetProducer.forEach(sweet ->sweet.produceSweet()); > > public class SweetCreatorTest < public static void main(String. args) < SweetCreator sweetCreator = new SweetCreator(Arrays.asList( new CakeProducer(), new ChocolateProducer(), new CookieProducer())); sweetCreator.createSweets(); >>
В этом примере вы можете видеть, что класс SweetCreator знает только о классе SweetProducer . Он не знает реализации каждого Sweet . Такое разделение дает нам гибкость для обновления и повторного использования наших классов, а это делает код намного проще в сопровождении. При проектировании кода всегда ищите способы сделать его максимально гибким и удобным. Полиморфизм — это очень мощный способ для использования в этих целях.
Аннотация @Override обязывает программиста использовать такую же сигнатуру метода, которая должна быть переопределена. Если метод не переопределен, будет ошибка компиляции.
Ковариантные возвращаемые типы при переопределении метода
Можно изменить тип возвращаемого значения переопределенного метода если это ковариантный тип. Ковариантный тип в основном является подклассом возвращаемого значения.
public abstract class JavaMascot < abstract JavaMascot getMascot(); >public class Duke extends JavaMascot < @Override Duke getMascot() < return new Duke(); >>
Поскольку Duke является JavaMascot , мы можем изменить тип возвращаемого значения при переопределении.
Полиморфизм в базовых классах Java
Мы постоянно используем полиморфизм в базовых классах Java. Один очень простой пример — создание экземпляра класса ArrayList с объявлением типа как интерфейс List .
List list = new ArrayList<>();
Рассмотрим пример кода, использующий Java Collections API без полиморфизма:
public class ListActionWithoutPolymorphism < // Пример без полиморфизма void executeVectorActions(Vectorvector)* Здесь повтор кода */> void executeArrayListActions(ArrayList arrayList)* Здесь повтор кода */> void executeLinkedListActions(LinkedList linkedList)* Здесь повтор кода */> void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList copyOnWriteArrayList) < /* Здесь повтор кода */>> public class ListActionInvokerWithoutPolymorphism < listAction.executeVectorActions(new Vector<>()); listAction.executeArrayListActions(new ArrayList<>()); listAction.executeLinkedListActions(new LinkedList<>()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList<>()); >
Отвратительный код, не так ли? Представьте себе, что вам нужно его сопровождать! Теперь рассмотрим тот же пример с полиморфизмом:
public static void main(String. polymorphism) < ListAction listAction = new ListAction(); listAction.executeListActions(); >public class ListAction < void executeListActions(Listlist) < // Выполнение действий с различными списками >> public class ListActionInvoker < public static void main(String. masterPolymorphism) < ListAction listAction = new ListAction(); listAction.executeListActions(new Vector<>()); listAction.executeListActions(new ArrayList<>()); listAction.executeListActions(new LinkedList<>()); listAction.executeListActions(new CopyOnWriteArrayList<>()); > >
Преимущество полиморфизма — гибкость и расширяемость. Вместо того чтобы создавать несколько различных методов, мы можем объявить один метод, который получает тип List .
Вызов конкретных методов для полиморфного метода
Можно вызвать конкретные методы при полиморфном вызове метода, это происходит за счет гибкости. Вот пример:
public abstract class MetalGearCharacter < abstract void useWeapon(String weapon); >public class BigBoss extends MetalGearCharacter < @Override void useWeapon(String weapon) < System.out.println("Big Boss is using a " + weapon); >void giveOrderToTheArmy(String orderMessage) < System.out.println(orderMessage); >> public class SolidSnake extends MetalGearCharacter < void useWeapon(String weapon) < System.out.println("Solid Snake is using a " + weapon); >> public class UseSpecificMethod < public static void executeActionWith(MetalGearCharacter metalGearCharacter) < metalGearCharacter.useWeapon("SOCOM"); // Следующая строка не будет работать // metalGearCharacter.giveOrderToTheArmy("Attack!"); if (metalGearCharacter instanceof BigBoss) < ((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!"); >> public static void main(String. specificPolymorphismInvocation) < executeActionWith(new SolidSnake()); executeActionWith(new BigBoss()); >>
Техника, которую мы используем здесь — это приведение типов (casting) или сознательное изменение типа объекта во время выполнения.
Обратите внимание, что вызов определенного метода возможен только при приведении более общего типа к более специфичному типу. Хорошей аналогией было бы сказать явно компилятору: «Эй, я знаю, что я здесь делаю, поэтому я собираюсь привести объект к определенному типу и буду использовать этот метод.»
Ссылаясь на приведенный выше пример, у компилятора есть веская причина не принимать вызов определенных методов: класс, который передаётся должен быть SolidSnake . В этом случае, у компилятора нет никакого способа гарантировать, что каждый подкласс MetalGearCharacter имеет метод giveOrderToTheArmy .
Ключевое слово instanceof
Обратите внимание на зарезервированное слово instanceof . Перед вызовом конкретного метода мы спросили, является ли MetalGearCharacter экземпляром (instanceof) BigBoss . Если это не экземпляр BigBoss , мы получим следующее исключение:
Exception in thread `main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss
Ключевое слово super
Что делать, если мы хотим сослаться на атрибут или метод из родительского класса? В этом случае мы можем использовать ключевое слово super .
Например:
public class JavaMascot < void executeAction() < System.out.println("The Java Mascot is about to execute an action!"); >> public class Duke extends JavaMascot < @Override void executeAction() < super.executeAction(); System.out.println("Duke is going to punch!"); >public static void main(String. superReservedWord) < new Duke().executeAction(); >>
Использование зарезервированного слова super в методе executeAction класса Duke вызывает метод родительского класса. Затем мы выполняем конкретное действие из класса Duke . Вот почему мы можем видеть оба сообщения в выводе:
The Java Mascot is about to execute an action! Duke is going to punch!
Решите задачку по полиморфизму
Давайте проверим, что вы узнали о полиморфизме и наследовании.
В этой задачке Вам дается несколько методов от Matt Groening’s The Simpsons, от вавам требуется разгадать, какой будет вывод для каждого класса. Для начала внимательно проанализируйте следующий код:
public class PolymorphismChallenge < static abstract class Simpson < void talk() < System.out.println("Simpson!"); >protected void prank(String prank) < System.out.println(prank); >> static class Bart extends Simpson < String prank; Bart(String prank) < this.prank = prank; >protected void talk() < System.out.println("Eat my shorts!"); >protected void prank() < super.prank(prank); System.out.println("Knock Homer down"); >> static class Lisa extends Simpson < void talk(String toMe) < System.out.println("I love Sax!"); >> public static void main(String. doYourBest) < new Lisa().talk("Sax :)"); Simpson simpson = new Bart("D'oh"); simpson.talk(); Lisa lisa = new Lisa(); lisa.talk(); ((Bart) simpson).prank(); >>
Как вы думаете? Каким будет результат? Не используйте IDE, чтобы выяснить это! Цель в том, чтобы улучшить ваши навыки анализа кода, поэтому постарайтесь решить самостоятельно.
Выберите ваш ответ (правильный ответ вы сможете найти в конце статьи).
A)
I love Sax!
D’oh
Simpson!
D’oh
B)
Sax 🙂
Eat my shorts!
I love Sax!
D’oh
Knock Homer down
C)
Sax 🙂
D’oh
Simpson!
Knock Homer down
D)
I love Sax!
Eat my shorts!
Simpson!
D’oh
Knock Homer down
Что случилось? Понимание полиморфизма
Для следующего вызова метода:
new Lisa().talk("Sax :)");
вывод будет «I love Sax!». Это потому, что мы передаём строку в метод и у класса Lisa есть такой метод.
Для следующего вызова:
Simpson simpson = new Bart("D'oh"); simpson.talk();
Вывод будет «Eat my shorts!». Это потому, что мы инициализируем тип Simpson с помощью Bart .
Теперь смотрите, это немного сложнее:
Lisa lisa = new Lisa(); lisa.talk();
Здесь мы используем перегрузку метода с наследованием. Мы ничего не передаем методу talk , поэтому вызывается метод talk из Simpson .
В этом случае на выходе будет «Simpson!».
((Bart) simpson).prank();
В этом случае строка prank была передана при создании экземпляра класса Bart через new Bart(«D’oh»); . В этом случае сначала вызывается метод super.prank() , а затем метод prank() из класса Bart . Вывод будет:
"D'oh" "Knock Homer down"
Распространенные ошибки с полиморфизмом
Распространенная ошибка думать, что можно вызвать конкретный метод без использования приведения типа.
Другой ошибкой является неуверенность в том, какой метод будет вызван при полиморфном создании экземпляра класса. Помните, что вызываемый метод является методом созданного экземпляра.
Также помните, что переопределение метода не является перегрузкой метода.
Невозможно переопределить метод, если параметры отличаются. Можно изменить тип возвращаемого значения переопределенного метода, если возвращаемый тип является подклассом.
Что нужно помнить о полиморфизме
- Созданный экземпляр определяет, какой метод будет вызван при использовании полиморфизма.
- Аннотация @Override обязывает программиста использовать переопределенный метод; в противном случае возникнет ошибка компилятора.
- Полиморфизм может использоваться с обычными классами, абстрактными классами и интерфейсами.
- Большинство шаблонов проектирования зависят от той или иной формы полиморфизма.
- Единственный способ вызвать нужный ваш метод в полиморфном подклассе — это использовать приведение типов.
- Можно создать мощную структуру кода, используя полиморфизм.
- Экспериментируйте. Через это, вы сможете овладеть этой мощной концепцией!
Ответ
I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down
Как всегда приветствую ваши комментарии и вопросы. И ждём у Виталия на открытом уроке.
25. Java – Полиморфизм
Полиморфизм – способность объекта принимать множество различных форм. Наиболее распространенное использование полиморфизма в ООП происходит, когда ссылка на родительский класс используется для ссылки на объект дочернего класса. Постараемся разобраться с понятием полиморфизма в Java простыми словами, так сказать для чайников.
Любой объект в Java, который может пройти более одного теста IS-A считается полиморфным. В Java все объекты полиморфны, так как любой объект пройдёт тест IS-A для своего собственного типа и для класса Object.
Важно знать, что получить доступ к объекту можно только через ссылочную переменную. Ссылочная переменная может быть только одного типа. Будучи объявленной, тип ссылочной переменной изменить нельзя.
Ссылочную переменную можно переназначить к другим объектам, которые не объявлены как final. Тип ссылочной переменной определяет методы, которые она может вызвать на объекте.
Ссылочная переменная может обратиться к любому объекту своего объявленного типа или любому подтипу своего объявленного типа. Ссылочную переменную можно объявить как класс или тип интерфейса.
Пример 1
Рассмотрим пример наследования полиморфизм в Java.
public interface Vegetarian<> public class Animal<> public class Deer extends Animal implements Vegetarian<>
Теперь класс Deer (Олень) считается полиморфным, так как он имеет множественное наследование. Следующие утверждения верны для примера выше:
- A Deer IS-A Animal (олень — это животное);
- A Deer IS-A Vegetarian (олень — это вегетарианец);
- A Deer IS-A Deer (олень — это олень);
- A Deer IS-A Object (олень — это объект).
Когда мы применяем факты ссылочной переменной к ссылке на объект Deer (Олень), следующие утверждения верны:
Пример 2
Deer d = new Deer(); Animal a = d; Vegetarian v = d; Object o = d;
Все переменные (d, a, v, o) ссылаются к тому же объекту Deer (Олень).
Виртуальные методы
В этом разделе рассмотрим, как поведение переопределённых методов в Java позволяет воспользоваться преимуществами полиморфизма при оформлении классов.
Мы уже рассмотрели переопредение методов, где дочерний класс может переопределить метод своего «родителя». Переопределённый метод же скрыт в родительском классе и не вызван, пока дочерний класс не использует ключевое слово super во время переопределения метода.
Пример
/* File name : Employee.java */ public class Employee < private String name; private String address; private int number; public Employee(String name, String address, int number) < System.out.println("Собираем данные о работнике"); this.name = name; this.address = address; this.number = number; >public void mailCheck() < System.out.println("Отправляем чек " + this.name + " " + this.address); >public String toString() < return name + " " + address + " " + number; >public String getName() < return name; >public String getAddress() < return address; >public void setAddress(String newAddress) < address = newAddress; >public int getNumber() < return number; >>
Теперь предположим, что мы наследуем класс Employee следующим образом:
/* File name : Salary.java */ public class Salary extends Employee < private double salary; // Годовая заработная плата public Salary(String name, String address, int number, double salary) < super(name, address, number); setSalary(salary); >public void mailCheck() < System.out.println("Внутри mailCheck класса Salary "); System.out.println("Отправляем чек " + getName() + " с зарплатой " + salary); >public double getSalary() < return salary; >public void setSalary(double newSalary) < if(newSalary >= 0.0) < salary = newSalary; >> public double computePay() < System.out.println("Вычисляем заработную плату для " + getName()); return salary/52; >>
Теперь, внимательно изучите программу и попытайтесь предугадать её вывод:
/* File name : VirtualDemo.java */ public class VirtualDemo < public static void main(String [] args) < Salary s = new Salary("Олег Петров", "Минск, Беларусь", 3, 3600.00); Employee e = new Salary("Иван Иванов", "Москва, Россия", 2, 2400.00); System.out.println("Вызываем mailCheck, используя ссылку Salary --"); s.mailCheck(); System.out.println("Вызываем mailCheck, используя ссылку Employee --"); e.mailCheck(); >>
После запуска программы будет выдан такой результат:
Собираем данные о работнике Собираем данные о работнике Вызываем mailCheck, используя ссылку Salary –– Внутри mailCheck класса Salary Отправляем чек Олег Петров с зарплатой 3600.0 Вызываем mailCheck, используя ссылку Employee –– Внутри mailCheck класса Salary Отправляем чек Иван Иванов с зарплатой 2400.0
Итак, мы создали два объекта Salary. Один использует ссылку Salary, то есть s, а другой использует ссылку Employee, то есть e.
Во время вызова s.mailCheck(), компилятор видит mailCheck() в классе Salary во время компиляции, а JVM вызывает mailCheck() в классе Salary при запуске программы.
mailCheck() в e совсем другое, потому что e является ссылкой Employee. Когда компилятор видит e.mailCheck(), компилятор видит метод mailCheck() в классе Employee.
Во время компиляции был использован mailCheck() в Employee, чтобы проверить это утверждение. Однако во время запуска программы JVM вызывает mailCheck() в классе Salary.
Это поведение называется вызовом виртуальных методов, а эти методы называются виртуальными. Переопределённый метод вызывается во время запуска программы вне зависимости от того, какой тип данных был использован в исходном коде во время компиляции.
Оглавление
- 1. Java – Самоучитель для начинающих
- 2. Java – Обзор языка
- 3. Java – Установка и настройка
- 4. Java – Синтаксис
- 5. Java – Классы и объекты
- 6. Java – Конструкторы
- 7. Java – Типы данных и литералы
- 8. Java – Типы переменных
- 9. Java – Модификаторы
- 10. Java – Операторы
- 11. Java – Циклы и операторы цикла
- 11.1. Java – Цикл while
- 11.2. Java – Цикл for
- 11.3. Java – Улучшенный цикл for
- 11.4. Java – Цикл do..while
- 11.5. Java – Оператор break
- 11.6. Java – Оператор continue
- 12. Java – Операторы принятия решений
- 12.1. Java – Оператор if
- 12.2. Java – Оператор if..else
- 12.3. Java – Вложенный оператор if
- 12.4. Java – Оператор switch..case
- 12.5. Java – Условный оператор (? 🙂
- 13. Java – Числа
- 13.1. Java – Методы byteValue(), shortValue(), intValue(), longValue(), floatValue(), doubleValue()
- 13.2. Java – Метод compareTo()
- 13.3. Java – Метод equals()
- 13.4. Java – Метод valueOf()
- 13.5. Java – Метод toString()
- 13.6. Java – Метод parseInt()
- 13.7. Java – Метод Math.abs()
- 13.8. Java – Метод Math.ceil()
- 13.9. Java – Метод Math.floor()
- 13.10. Java – Метод Math.rint()
- 13.11. Java – Метод Math.round()
- 13.12. Java – Метод Math.min()
- 13.13. Java – Метод Math.max()
- 13.14. Java – Метод Math.exp()
- 13.15. Java – Метод Math.log()
- 13.16. Java – Метод Math.pow()
- 13.17. Java – Метод Math.sqrt()
- 13.18. Java – Метод Math.sin()
- 13.19. Java – Метод Math.cos()
- 13.20. Java – Метод Math.tan()
- 13.21. Java – Метод Math.asin()
- 13.22. Java – Метод Math.acos()
- 13.23. Java – Метод Math.atan()
- 13.24. Java – Метод Math.atan2()
- 13.25. Java – Метод Math.toDegrees()
- 13.26. Java – Метод Math.toRadians()
- 13.27. Java – Метод Math.random()
- 14. Java – Символы
- 14.1. Java – Метод Character.isLetter()
- 14.2. Java – Метод Character.isDigit()
- 14.3. Java – Метод Character.isWhitespace()
- 14.4. Java – Метод Character.isUpperCase()
- 14.5. Java – Метод Character.isLowerCase()
- 14.6. Java – Метод Character.toUpperCase()
- 14.7. Java – Метод Character.toLowerCase()
- 14.8. Java – Метод Character.toString()
- 15. Java – Строки
- 15.1. Java – Метод charAt()
- 15.2. Java – Метод compareTo()
- 15.3. Java – Метод compareToIgnoreCase()
- 15.4. Java – Метод concat()
- 15.5. Java – Метод contentEquals()
- 15.6. Java – Метод copyValueOf()
- 15.7. Java – Метод endsWith()
- 15.8. Java – Метод equals()
- 15.9. Java – Метод equalsIgnoreCase()
- 15.10. Java – Метод getBytes()
- 15.11. Java – Метод getChars()
- 15.12. Java – Метод hashCode()
- 15.13. Java – Метод indexOf()
- 15.14. Java – Метод intern()
- 15.15. Java – Метод lastIndexOf()
- 15.16. Java – Метод length()
- 15.17. Java – Метод matches()
- 15.18. Java – Метод regionMatches()
- 15.19. Java – Метод replace()
- 15.20. Java – Метод replaceAll()
- 15.21. Java – Метод replaceFirst()
- 15.22. Java – Метод split()
- 15.23. Java – Метод startsWith()
- 15.24. Java – Метод subSequence()
- 15.25. Java – Метод substring()
- 15.26. Java – Метод toCharArray()
- 15.27. Java – Метод toLowerCase()
- 15.28. Java – Метод toString()
- 15.29. Java – Метод toUpperCase()
- 15.30. Java – Метод trim()
- 15.31. Java – Метод valueOf()
- 15.32. Java – Классы StringBuilder и StringBuffer
- 15.32.1. Java – Метод append()
- 15.32.2. Java – Метод reverse()
- 15.32.3. Java – Метод delete()
- 15.32.4. Java – Метод insert()
- 15.32.5. Java – Метод replace()
- 16. Java – Массивы
- 17. Java – Дата и время
- 18. Java – Регулярные выражения
- 19. Java – Методы
- 20. Java – Потоки ввода/вывода, файлы и каталоги
- 20.1. Java – Класс ByteArrayInputStream
- 20.2. Java – Класс DataInputStream
- 20.3. Java – Класс ByteArrayOutputStream
- 20.4. Java – Класс DataOutputStream
- 20.5. Java – Класс File
- 20.6. Java – Класс FileReader
- 20.7. Java – Класс FileWriter
- 21. Java – Исключения
- 21.1. Java – Встроенные исключения
- 22. Java – Вложенные и внутренние классы
- 23. Java – Наследование
- 24. Java – Переопределение
- 25. Java – Полиморфизм
- 26. Java – Абстракция
- 27. Java – Инкапсуляция
- 28. Java – Интерфейсы
- 29. Java – Пакеты
- 30. Java – Структуры данных
- 30.1. Java – Интерфейс Enumeration
- 30.2. Java – Класс BitSet
- 30.3. Java – Класс Vector
- 30.4. Java – Класс Stack
- 30.5. Java – Класс Dictionary
- 30.6. Java – Класс Hashtable
- 30.7. Java – Класс Properties
- 31. Java – Коллекции
- 31.1. Java – Интерфейс Collection
- 31.2. Java – Интерфейс List
- 31.3. Java – Интерфейс Set
- 31.4. Java – Интерфейс SortedSet
- 31.5. Java – Интерфейс Map
- 31.6. Java – Интерфейс Map.Entry
- 31.7. Java – Интерфейс SortedMap
- 31.8. Java – Класс LinkedList
- 31.9. Java – Класс ArrayList
- 31.10. Java – Класс HashSet
- 31.11. Java – Класс LinkedHashSet
- 31.12. Java – Класс TreeSet
- 31.13. Java – Класс HashMap
- 31.14. Java – Класс TreeMap
- 31.15. Java – Класс WeakHashMap
- 31.16. Java – Класс LinkedHashMap
- 31.17. Java – Класс IdentityHashMap
- 31.18. Java – Алгоритмы Collection
- 31.19. Java – Iterator и ListIterator
- 31.20. Java – Comparator
- 32. Java – Дженерики
- 33. Java – Сериализация
- 34. Java – Сеть
- 34.1. Java – Обработка URL
- 35. Java – Отправка Email
- 36. Java – Многопоточность
- 36.1. Java – Синхронизация потоков
- 36.2. Java – Межпоточная связь
- 36.3. Java – Взаимная блокировка потоков
- 36.4. Java – Управление потоками
- 37. Java – Основы работы с апплетами
- 38. Java – Javadoc
Что такое полиморфизм в Java
Привет! Это статья об одном из принципов ООП — полиморфизм.
Что такое полиморфизм
Определение полиморфизма звучит устрашающе
Полиморфизм — это возможность применения одноименных методов с одинаковыми или различными наборами параметров в одном классе или в группе классов, связанных отношением наследования.
Слово «полиморфизм» может показаться сложным — но это не так. Нужно просто разбить данное определение на части и показать на примерах, что имеется в виду. Поверьте, уже в конце статьи данное определение полиморфизма покажется Вам понятным
Полиморфизм, если перевести, — это значит «много форм». Например, актер в театре может примерять на себя много ролей — или принимать «много форм».
Так же и наш код — благодаря полиморфизму он становится более гибким, чем в языках программирования, которые не используют принципы ООП.
Так о каких формах идет речь? Давайте сначала приведем примеры и покажем, как на практике проявляется полиморфизм, а потом снова вернемся к его определению.
Как проявляется полиморфизм
Дело в том, что если бы в Java не было принципа полиморфизма, компилятор бы интерпретировал это как ошибку:
Как видите, методы на картинке отличаются значениями, которые они принимают:
Однако, поскольку в Java используется принцип полиморфизма, компилятор не будет воспринимать это как ошибку, потому что такие методы будут считаться разными:
Называть методы одинаково — это очень удобно. Например, если у нас есть метод, который ищет корень квадратный из числа, гораздо легче запомнить одно название (например, sqrt()), чем по одному отдельному названию на этот же метод, написанный для каждого типа:
Как видите, мы не должны придумывать отдельное название для каждого метода — а главное их запоминать! Очень удобно.
Теперь Вы можете понять, почему часто этот принцип описывают фразой:
Один интерфейс — много методов
Это предполагает, что мы можем заполнить одно название (один интерфейс), по которому мы сможем обращаться к нескольким методам.
Перегрузка методов
То, что мы показывали выше — несколько методов с одним названием и разными параметрами — называется перегрузкой. Но это был пример перегрузки метода в одном классе. Но бывает еще один случай — переопределение методов родительского класса.
Переопределение методов родителя
Когда мы наследуем какой-либо класс, мы наследуем и все его методы. Но если нам хочется изменить какой-либо из методов, который мы наследуем, мы можем всего-навсего переопределить его. Мы не обязаны, например, создавать отдельный метод с похожим названием для наших нужд, а унаследованный метод будет «мертвым грузом» лежать в нашем классе.
Именно то, что мы можем создать в классе-наследнике класс с таким же названием, как и класс, который мы унаследовали от родителя, и называется переопределением.
Пример
Представим, что у нас есть такая структура:
Вверху иерархии классов стоит класс Animal. Его наследуют три класса — Cat, Dog и Cow.
У класса «Animal» есть метод «голос» (voice). Этот метод выводит на экран сообщение «Голос». Естественно, ни собака, ни кошка не говорят «Голос» Они гавкают и мяукают. Соответственно, Вам нужно задать другой метод для классов Cat, Dog и Cow — чтобы кошка мяукала, собака гавкала, а корова говорила «Муу».
Поэтому, в классах-наследниках мы переопределяем метод voice(), чтобы мы в консоли получали «Мяу», «Гав» и «Муу».
- Обратите внимание: перед методом, который мы переопределяем, пишем «@Override«. Это дает понять компилятору, что мы хотим переопределить метод.
Так что же такое полиморфизм
Тем не менее, полиморфизм — это принцип. Все реальные примеры, которые мы приведодили выше — это только способы реализации полиморфизма.
Давайте снова посмотрим на определение, которое мы давали в начале статьи:
Полиморфизм — возможность применения одноименных методов с одинаковыми или различными наборами параметров в одном классе или в группе классов, связанных отношением наследования.
Выглядит понятнее, правда? Мы показали, как можно:
- создавать «одноименные методы» в одном классе («перегрузка методов»)
- или изменить поведение методов родительского класса («переопределение методов»).
Все это — проявления «повышенной гибкости» объектно-ориентированных языков благодаря полиморфизму.
Надеемся, наша статья была Вам полезна. Записаться на наши курсы по Java можно у нас на сайте.
- ← Перечисления Enum в Java — Часть 1
- Map в Java. Hashmap в Java →