Привет! Сегодня мы заканчиваем серию лекций о принципах ООП. На этом занятии поговорим о полиморфизме. Коротко: полиморфизм это возможность работать с объектами разных типов через один общий тип, при этом каждый объект сохраняет свое поведение. Обычно он достигается через наследование: один метод может принимать родительский тип (например Cat) и работать с любым его наследником (Lion, Tiger, Cheetah), не заставляя писать отдельный метод под каждый вид. Полиморфизм это возможность работать с несколькими типами так, будто это один и тот же тип. При этом поведение объектов будет разным в зависимости от того, к какому типу они принадлежат.

Кратко

  • Полиморфизм это возможность работать с объектами разных типов через общий тип, сохраняя при этом их собственное поведение.
  • Обычно достигается через наследование: класс-родитель (например Cat) и наследники (Lion, Tiger, Cheetah).
  • Один метод может принимать общий тип (healCat(Cat cat)) и работать с любым наследником, не плодя отдельные методы.
  • При вызове переопределенного метода (run()) выполняется реализация конкретного класса, а не родителя.
  • Главное преимущество: гибкость и меньше кода; единый интерфейс для многих классов.
Давай рассмотрим это утверждение подробнее.Полиморфизм в Java: работа с объектами разных типов как с одним

Что такое полиморфизм

Начнем с первой части: «возможность работать с несколькими типами так, как будто это один и тот же тип». Как разные типы могут при этом быть одним и тем же? Звучит немного странно :/ На самом деле все просто. К примеру, такая ситуация возникает при обычном использовании наследования. Посмотрим, как это работает. Допустим, у нас есть простой родительский класс Cat с единственным методом run(), то есть «бежать»:

public class Cat {

   public void run() {
       System.out.println("Бег!");
   }
}
А теперь создадим три класса, которые наследуются от Cat: Lion, Tiger и Cheetah, обозначающие льва, тигра и гепарда.

public class Lion extends Cat {

   @Override
   public void run() {
       System.out.println("Лев бежит со скоростью 80 км/ч");
   }
}

public class Tiger extends Cat {

   @Override
   public void run() {
       System.out.println("Тигр бежит со скоростью 60 км/ч");
   }
}

public class Cheetah extends Cat {

   @Override
   public void run() {
       System.out.println("Гепард бежит со скоростью до 120 км/ч");
   }
}
Итак, у нас есть 3 класса. Давай смоделируем ситуацию, при которой мы сможем работать с ними так, как будто это один и тот же класс.

Один метод для всех типов

Представим, что кто-то из наших котов заболел, и ему нужна помощь доктора Айболита. Попробуем создать класс Aibolit, который будет способен лечить и львов, и тигров, и гепардов.

public class Aibolit {

   public void healLion(Lion lion) {

       System.out.println("Лев здоров!");
   }

   public void healTiger(Tiger tiger) {

       System.out.println("Тигр здоров!");
   }

   public void healCheetah(Cheetah cheetah) {

       System.out.println("Гепард здоров!");
   }
}
Казалось бы, проблема решена: класс написан и готов к работе. Но что мы будем делать, если захотим расширить нашу программу? Сейчас у нас всего 3 вида: львы, тигры, и гепарды. Но в мире существует больше 40 видов кошек. Представь, что будет, если мы добавим в программу отдельные классы для манулов, ягуаров, мейн-кунов, домашних кошек и всех остальных. Без полиморфизма для каждого вида кошек пришлось бы писать отдельный методСама программа, конечно, будет функционировать, но вот в класс Aibolit придется постоянно добавлять новые методы для лечения каждого вида кошек, и в итоге он разрастется до невиданных размеров. Здесь и проявляется свойство полиморфизма, «возможность работать с несколькими типами так, как будто это один и тот же тип». Нам не нужно создавать бесчисленное количество методов, которые будут делать одно и то же, лечить кошку. Достаточно будет одного метода для всех случаев сразу:

public class Aibolit {

   public void healCat(Cat cat) {

       System.out.println("Пациент здоров!");
   }
}
В метод healCat() мы можем передавать и объекты Lion, и Tiger и Cheetah, они все являются Cat:

public class Main {

   public static void main(String[] args) {

       Aibolit aibolit = new Aibolit();

       Lion simba = new Lion();
       Tiger sherekhan = new Tiger();
       Cheetah chester = new Cheetah();

       aibolit.healCat(simba);
       aibolit.healCat(sherekhan);
       aibolit.healCat(chester);
   }
}
Вывод в консоль:

Пациент здоров!
Пациент здоров!
Пациент здоров!
Вот так наш класс Айболит может работать с разными типами, как будто это один и тот же тип. Наглядно разницу двух подходов можно свести в таблицу:
ПодходКласс AibolitЧто будет при 40 видах кошек
Без полиморфизмаотдельный метод на каждый вид (healLion, healTiger, healCheetah и т.д.)40 почти одинаковых методов
С полиморфизмомодин метод healCat(Cat cat)один метод на все виды

Разное поведение у разных типов

Теперь давай разберемся со второй частью: «при этом поведение объектов будет разным в зависимости от того, к какому типу они принадлежат». Здесь тоже все просто. В природе все кошки бегают по-разному. Как минимум, у них различается скорость бега. Среди наших трех питомцев гепард самый быстрый, а тигр и лев бегают медленнее. То есть у них отличается поведение. Полиморфизм не только дает нам возможность использовать разные типы как один. Он при этом еще позволяет не забывать об их отличиях и сохраняет специфическое для каждого из них поведение. Это можно понять на таком примере. Допустим, после успешного выздоровления наши коты решили на радостях немного побегать. Добавим это в наш класс Aibolit:

public class Aibolit {

   public void healCat(Cat cat) {

       System.out.println("Пациент здоров!");
       cat.run();
   }
}
Попробуем выполнить тот же код для лечения трех зверей:

public static void main(String[] args) {

   Aibolit aibolit = new Aibolit();

   Lion simba = new Lion();
   Tiger sherekhan = new Tiger();
   Cheetah chester = new Cheetah();

   aibolit.healCat(simba);
   aibolit.healCat(sherekhan);
   aibolit.healCat(chester);
}
И вот как будет выглядеть результат:

Пациент здоров!
Лев бежит со скоростью 80 км/ч
Пациент здоров!
Тигр бежит со скоростью 60 км/ч
Пациент здоров!
Гепард бежит со скоростью до 120 км/ч
Здесь мы наглядно видим, что специфическое поведение наших объектов сохранилось, хотя мы передали всех троих зверей в метод, «обобщив» каждого из них до Cat. Благодаря полиморфизму Java прекрасно помнит, что это не просто три каких-то кота, а именно лев, тигр и гепард, которые бегают по-разному. В этом заключается главное преимущество использования полиморфизма, гибкость. Когда нам нужно создать какой-то общий для многих типов функционал, львы, тигры и гепарды превращаются просто в «котов». Все животные разные, но в некоторых ситуациях кот есть кот, без разницы к какому виду он относится:) Вот тебе видеоподтверждение.
Когда же это «обобщение» не требуется, и нам наоборот нужно, чтобы поведение у видов отличалось, каждый тип ведет себя по-своему. Благодаря полиморфизму, ты создаешь единый интерфейс (набор методов) для широкого набора классов. За счет этого снижается сложность программ. Если бы мы даже расширили программу до 40 видов кошек, у нас все равно сохранился бы максимально простой интерфейс, один метод run() для всех 40 кошек. Подробнее о полиморфизме можно прочитать в документации Oracle (Polymorphism).

Читайте также

Вопросы и ответы

Что такое полиморфизм в Java?

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

Как полиморфизм связан с наследованием?

Наследники (например Lion, Tiger) являются подтипами родителя (Cat), поэтому их можно передавать туда, где ожидается родительский тип. Это и дает работу с разными типами как с одним.

Зачем нужен полиморфизм?

Он убирает дублирование: вместо отдельного метода на каждый тип пишут один метод для общего типа. Код становится гибче и проще поддерживается.

Что происходит при вызове переопределенного метода?

Выполняется реализация того класса, к которому реально принадлежит объект, а не родителя. Например, cat.run() у объекта Lion выведет поведение льва.

Чем полиморфизм отличается от простого наследования?

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