☕ Учебник по Java: cтатический и динамический полиморфизм
В статье разберемся с одной из ключевых концепций объектно-ориентированного программирования — полиморфизмом — и посмотрим, как она реализована в Java.
Что такое Полиморфизм?
Слово «полиморфизм» произошло от двух греческих слов: «Poly» означающего «многочисленный» и «Morph» — форма. Если перевести дословно – бесчисленное множество форм. Живые примеры этого явления можно встретить в земной коре, среди животных и рептилий, а также в других областях науки. Но речь сегодня не об этом, а об основной парадигме ООП, применяющейся во многих языках программирования.
Что такое полиморфизм в программировании?
Полиморфизм в ООП подразумевает собой особенность, при которой одно и то же действие выполняется разными способами. Или, проще говоря, такая концепция позволяет выполнять несколько реализаций одной и той же сущности.
Чаще всего она возникает при наличии другой парадигмы ООП — наследования, т. е. когда у нас существует множество классов, связанных друг с другом. Наследование позволяет одному классу приобретать свойства и атрибуты другого, а полиморфизм использует унаследованные свойства для выполнения различных задач.
Примеры из жизни
Чтобы лучше понять принцип парадигмы, попробуем объяснить это с помощью нескольких простых примеров.
Возьмем нас с вами или если совсем обобщенно — человека. Он, в зависимости от различных обстоятельств, может вести себя по-разному. Например, женщина может быть и матерью, и дочерью, и сестрой, и другом одновременно и в разных ситуациях ее действия будут радикально отличаться.
А еще в организме человека есть разные органы и у каждого из них есть своя функция: сердце отвечает за кровоток, легкие за дыхание, мозг за когнитивную деятельность. То есть, у нас есть стандартная функция, работающая по-разному в зависимости от органа тела. Теперь перейдем конкретно к Java.
Полиморфизм в Java
Что мы должны понимать про принцип действия рассматриваемой концепции? Ну во-первых, все то, что содержит или обрабатывает значения различных типов, когда идет компиляция или выполняется программа — является полиморфным, например:
- любая переменная, которой присваивается значение другого типа;
- любой объект, обладающий свойствами, изменяющими тип данных присвоенного ему значения;
- любая функция, принимающая аргументы различных типов.
Рассмотрим небольшой пример по реализации полиморфизма на Java:
У суперкласса под названием Forms
есть метод shapearea()
и классы-потомки Triang
и Circ
. Каждый из них имеет свой способ подсчета площади. С помощью рассматриваемой парадигмы потомки пользуются методом родителя shapearea()
, чтобы найти формулу для расчета своей площади.
class Forms { public void shapearea() { System.out.println("Формулы:"); } } class Triang extends Forms{ public void shapearea() { System.out.println("Sтреугольника: ½ * основания * высоту"); } } class Circ extends Forms{ public void shapearea() { System.out.println("Sкруга: 3,14 * радиус * радиус"); } } class Main { public static void main(String[] args) { Forms xForms = new Forms); // создание объекта Forms Forms xTriang= new Triang(); // создание объекта Triang Forms xCirc = new Circ(); // создание объекта Circ xForms.shapearea(); xTriang.shapearea(); xForms.shapearea(); xCirc.shapearea(); } }
Вывод:
Формула: площадь треугольника: ½ * основания * высоту Формула: площадь круга: 3,14 * радиус * радиус
Типы полиморфизма
Два варианта реализации полиморфизма в Java:
- Overloading – перегрузка метода
- Overriding – переопределение метода
При перегрузке метода мы в одном классе создаем нескольких методов с одинаковым названием, но разным функционалом, например:
class Forms{ public void shapearea()) { System.out.println("Площади фигур:"); } public void shapearea(int r) { System.out.println("Sкруга = "+3.14*r*r); } public void shapearea(double b, double h) { System.out.println("Sтреугольника ="+0.5*b*h); } public void shapearea(int l, int b) { System.out.println("Sпрямоугольника ="+l*b); } } class Main { public static void main(String[] args) { Forms xForms = new Forms(); xForms.shapearea(); xForms.shapearea(3); xForms.shapearea(2.5, 2.0); xForms.shapearea(4,7); } }
Вывод:
Площади фигур: Sкруга = 28.26 Sтреугольника = 5 Sпрямоугольника = 28
При переопределении название метода дочернего класса мы ставим такое же, как и уже объявленного метода родительского класса, например:
class Vehicles{ // определение метода void drive(){ System.out.println("Управлять транспортным средством");} } //создаем дочерний класс class Cars extends Vehicles{ //определяем одноименный метод void drive(){ System.out.println("Управлять автомобилем"); } public static void main(String args[]){ Cars model= new Cars(); //создание нового объекта model.drive(); //вызов переопределенного метода } }
Выведет:
Управлять автомобилем
Статический и динамический полиморфизм
При статическом варианте развития событий метод можно вызывать при компиляции кода, происходит это при помощи рассмотренной нами ранее концепции — перегрузки методов. Говоря проще, у них будет одинаковое название, но разная функциональная начинка (тип возвращаемых данных, свойств и т. д.). В таком случае Java позволяет пользователю свободно определять функциям идентичные названия, если они будут отличны по типу и параметрам, например:
public class Add { void sum(int x, int y) { int z = x + y; System.out.println(“Сумма двух чисел: ”+z); } void sum(int x, int y, int i) { int z = x + y + i; System.out.println(“Сумма трех чисел: ”+z); } public static void main(String[] args) { Add myAdd = new Add(); myAdd.sum(5, 11); myAdd.sum(1, 8, 11); } }
Вывод программы:
Сумма двух чисел: 16 Сумма трех чисел: 20
При динамическом полиморфизме вызвать переопределеный метод можно при выполнении программы. Скопировать метод родительского класса и обозначить его в дочернем можно с помощью ссылочной переменной родительского класса, при этом вызванный метод будет определяться по объекту, на который она ссылается. Такую операцию еще называют Upcasting, например:
Создадим один родительский класс Звери
и три подкласса: травоядные
, плотоядные
и всеядные
. В этом случае классы-потомки расширили родителя и переопределили его метод eat()
.
class Beast { void eat() { System.out.println("Животные питаются:"); } } class herbivorous extends Beast { void eat() { System.out.println("Травоядные едят растения"); } } class omnivorous extends Beast { void eat() { System.out.println("Всеядные едят растения и мясо"); } } class carnivorous extends Beast { void eat() { System.out.println("Хищники едят только мясо"); } } class main { public static void main(String args[]) { Beast X = new Beast(); Beast herb = new herbivorous(); Beast omni = new omnivorous(); Beast carn = new carnivorous(); X.eat(); herb.eat(); omni.eat(); carn.eat(); } }
Вывод:
Животные питаются: Травоядные едят растения Всеядные едят растения и мясо Хищники едят мясо
Преимущества и недостатки полиморфизма
- Предоставляет возможность повторного использования кода. Реализованные классы можно многократно использовать повторно. Кроме того, это экономит много времени разработчику, ведь при этом появляется возможность поменять что-то в программе, не трогая исходный код.
- Одна переменная может использоваться для хранения нескольких значений данных. Значение переменной в дочернем классе, наследуемой от родительского, может быть изменено без изменения родительской переменной.
- Легче отлаживать код, когда его не так много.
Помимо преимуществ, у рассматриваемой парадигмы есть еще и несколько недостатков:
- Полиморфизм не так просто реализовать на деле.
- Такой подход снижает читаемость кода.
- Может вызывать серьезные проблемы с производительностью в режиме реального времени.
Мы надеемся, что у вас сложилось правильное общее представление о полиморфизме в Java и о том, как его использовать. С более подробной информации по теме можно ознакомиться на официальном сайте Oracle.