JavaRush /Курсы /JAVA 25 SELF /Понятие полиморфизма, зачем он нужен

Понятие полиморфизма, зачем он нужен

JAVA 25 SELF
18 уровень , 0 лекция
Открыта

1. Введение

Полиморфизм — это одна из трёх китовых концепций объектно-ориентированного программирования (наряду с наследованием и инкапсуляцией). Если дословно, то слово происходит от греческого "poly" (много) и "morph" (форма). В программировании это означает: один интерфейс — много реализаций.

Определение

Полиморфизм — это способность объектов разных классов реагировать на одинаковые сообщения (вызовы методов) по-разному.

То есть, если у вас есть метод makeSound(), то вы можете вызвать его у любого животного, но кошка замяукает, собака залает, а корова замычит. Для программиста — это просто вызов animal.makeSound(), а вот что произойдёт на самом деле — зависит от того, какой именно объект стоит за этой переменной.

Аналогия из жизни

Представьте, что у вас дома есть пульт от телевизора, и этим же пультом можно управлять колонками, проектором и даже кофемашиной. Вы нажимаете кнопку «Включить» — turnOn(), и каждый прибор реагирует по-своему. Главное — у всех есть «кнопка» включения, но реализация разная.

Пример на Java

class Animal {
    void makeSound() {
        System.out.println("Какой-то звук...");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Гав!");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Мяу!");
    }
}

class Cow extends Animal {
    @Override
    void makeSound() {
        System.out.println("Мууу!");
    }
}

Теперь мы можем сделать так:

Animal animal1 = new Dog();
Animal animal2 = new Cat();
Animal animal3 = new Cow();

animal1.makeSound(); // Гав!
animal2.makeSound(); // Мяу!
animal3.makeSound(); // Мууу!

Обратите внимание: все переменные имеют тип Animal, но результат вызова зависит от фактического типа объекта.

2. Виды полиморфизма

В Java (и в большинстве ООП-языков) различают два основных вида полиморфизма:

Компиляторный (статический) полиморфизм — перегрузка методов (overloading)

Это когда в одном классе есть несколько методов с одинаковым именем, но разными параметрами. Компилятор сам решает, какой метод вызвать, исходя из переданных аргументов.

Пример (заглянем вперёд — подробнее в следующей лекции):

class Printer {
    void print(int x) {
        System.out.println("Число: " + x);
    }

    void print(String s) {
        System.out.println("Строка: " + s);
    }
}

Исполнительный (динамический) полиморфизм — переопределение методов (overriding)

Это когда метод определяется в базовом классе, а затем переопределяется в подклассах. Какой именно метод будет вызван — решается во время выполнения программы (runtime), в зависимости от фактического типа объекта.

Пример — см. выше с животными.

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

Полиморфизм — это не просто красивое слово для собеседования. Это инструмент, который делает ваш код гибким, расширяемым и удобным для поддержки.

Универсальность кода

Вы можете писать код, который работает с объектами базового типа, не заботясь о деталях их реализации. Например, если у вас есть список животных, вы можете пройтись по нему и вызвать у каждого makeSound(), не думая о том, кошка это или собака.

Animal[] animals = { new Dog(), new Cat(), new Cow() };

for (Animal animal : animals) {
    animal.makeSound(); // Каждый раз вызовется "правильный" метод
}

Лёгкость расширения

Если завтра к вам придёт начальник и скажет: «А давай добавим попугая!», вы просто пишете новый класс Parrot extends Animal и добавляете его в массив. Весь остальной код останется прежним. Это — открытость для расширения и закрытость для изменений (принцип OCP из SOLID).

Упрощение архитектуры

Вы можете строить сложные системы, где отдельные части взаимодействуют друг с другом через абстракции (базовые классы или интерфейсы), не заботясь о конкретных реализациях. Это экономит время, нервы и кофе.

4. Ключевые понятия: ссылочный и фактический тип

Ссылочный тип переменной
Когда вы пишете Animal animal = new Dog();, переменная animal имеет ссылочный тип Animal, то есть компилятор «думает», что это животное, и разрешает только те методы, которые объявлены в классе Animal.

Фактический (реальный) тип объекта
Но в памяти у вас реально лежит объект типа Dog. Именно он определяет, какой метод будет вызван при обращении к makeSound().

Иллюстрация

Animal animal = new Dog();
animal.makeSound(); // Вызовет Dog.makeSound(), а не Animal.makeSound()

Важно! Через ссылку базового типа (Animal) вы не сможете вызвать методы, которые есть только у Dog, если они не объявлены в базовом классе.

Позднее (динамическое) связывание
Это магия, которая происходит во время выполнения: когда вы вызываете метод через ссылку базового типа, JVM смотрит на фактический тип объекта и вызывает «правильную» реализацию. Это и есть полиморфизм в действии.

5. Практический пример: полиморфизм в приложении

Давайте продолжим развитие нашего учебного приложения. Допустим, мы пишем простую симуляцию зоопарка. У нас есть базовый класс Animal и несколько его потомков. Мы хотим, чтобы все животные могли «издавать звук», но не хотим каждый раз писать отдельный код для каждого типа животного.

Шаг 1: Базовый класс и потомки

class Animal {
    void makeSound() {
        System.out.println("Какой-то звук...");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Гав!");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Мяу!");
    }
}

Шаг 2: Массив животных

Animal[] zoo = { new Dog(), new Cat(), new Animal() };

Шаг 3: Перебор и вызов метода

for (Animal animal : zoo) {
    animal.makeSound();
}

Результат выполнения:

Гав!
Мяу!
Какой-то звук...

Обратите внимание: мы не знаем заранее, кто именно находится в массиве — но программа сама разбирается и вызывает нужный метод.

6. Схематическое изображение полиморфизма


.        Animal (makeSound)
           /        \
        Dog        Cat
     (makeSound) (makeSound)

Animal animal = new Dog();
animal.makeSound(); // --> Dog.makeSound()

Animal animal = new Cat();
animal.makeSound(); // --> Cat.makeSound()

7. Ещё пример: полиморфизм в реальной задаче

Допустим, вы пишете программу для управления сотрудниками компании. У вас есть базовый класс Employee и два потомка: Manager и Developer. Все сотрудники могут работать — work(), но делают это по-разному.

class Employee {
    void work() {
        System.out.println("Сотрудник работает.");
    }
}

class Manager extends Employee {
    @Override
    void work() {
        System.out.println("Менеджер проводит совещание.");
    }
}

class Developer extends Employee {
    @Override
    void work() {
        System.out.println("Разработчик пишет код.");
    }
}

Теперь вы можете сделать так:

Employee[] staff = { new Manager(), new Developer(), new Employee() };

for (Employee emp : staff) {
    emp.work();
}

Результат:

Менеджер проводит совещание.
Разработчик пишет код.
Сотрудник работает.

8. Когда полиморфизм НЕ работает

Полиморфизм работает только для методов, объявленных в базовом классе. Если в подклассе есть свой уникальный метод, через ссылку базового типа вы его не увидите.

class Dog extends Animal {
    void fetchStick() {
        System.out.println("Собака приносит палку!");
    }
}

Animal animal = new Dog();
// animal.fetchStick(); // Ошибка компиляции! Такой метод не виден через Animal

Чтобы вызвать специфичный метод, нужно привести переменную к нужному типу:

if (animal instanceof Dog) {
    ((Dog) animal).fetchStick();
}

Но это уже другая история — главное: через полиморфизм доступны только методы, объявленные в базовом классе.

9. Типичные ошибки при работе с полиморфизмом

Ошибка № 1: Ожидание, что через ссылку базового типа будут доступны все методы подкласса. На самом деле, доступны только те, что объявлены в базовом классе.

Ошибка № 2: Неиспользование аннотации @Override при переопределении метода. Без неё можно случайно написать метод с неправильной сигнатурой, и тогда полиморфизм не сработает (метод базового класса не будет переопределён).

Ошибка № 3: Попытка вызвать специфичный метод подкласса без приведения типа. Компилятор не позволит, потому что не знает, кто именно сидит за ссылкой базового типа.

Ошибка № 4: Путаница между перегрузкой (overloading) и переопределением (overriding). Перегрузка — это несколько методов с одним именем и разными параметрами в одном классе. Переопределение — это изменение поведения метода в подклассе.

1
Задача
JAVA 25 SELF, 18 уровень, 0 лекция
Недоступна
Приключения в мире животных: Урок Полиморфизма!
Приключения в мире животных: Урок Полиморфизма!
1
Задача
JAVA 25 SELF, 18 уровень, 0 лекция
Недоступна
Хор фермы: Полиморфизм с несколькими голосами
Хор фермы: Полиморфизм с несколькими голосами
1
Задача
JAVA 25 SELF, 18 уровень, 0 лекция
Недоступна
Корпоративный лабиринт: Полиморфизм и роль сотрудника
Корпоративный лабиринт: Полиморфизм и роль сотрудника
1
Задача
JAVA 25 SELF, 18 уровень, 0 лекция
Недоступна
Расширение автопарка: Полиморфизм и новые виды транспорта
Расширение автопарка: Полиморфизм и новые виды транспорта
Комментарии (9)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Yaroslav Уровень 38
3 декабря 2025
Справедливости ради, некоторые темы, например эта, объяснены в новом курсе куда лучше. А вот задачи здесь очень упрощенные пока что.
Sergey Lunev Уровень 41
5 декабря 2025
а вы проходили старый курс? Там реально задачи получше? Я не проходил, начал с этого и что-то задачки тут прямо таки слишком слабые, кажется... Уже думаю, может на старый лучше переключиться
Yaroslav Уровень 38
5 декабря 2025
Я прошёл 20 уровней старого, потом решил пройти быстро до новых тем здесь, и продолжить обновлённую версию. Задачи на старом курсе другие. Этот курс - это когда тебе дали карту, гида, обеспечили тёплой палаткой и всем снаряжением, и вы идёте в поход по чётко прописанным условиям задачи, с единственно правильным решением. Иногда задача на 100% совпадает с примером из лекции. На старом курсе: тебе дают рюкзак с минимумом вещей и примерное направление - иди решай, сам думай, пробирайся через заросли, выживай как хочешь. Иногда решение даже не одно, но никто не объясняет как именно ты решаешь, иногда задачи очень трудные, и ты гуглишь что-то, чего не было в лекции, но зато учишься интенсивнее и творчески. На одной задаче с уровнем Hard я просидел несколько часов, а здесь всё решается почти не думая. Вот, как-то так пока. Тут задачи очень однотипные, но зато материалы лекций более уточнённые, с примитивными, но доходчивыми примерами. Я думаю, старую версию писали люди, а эту с применением нейронок. Если слить воедино оба курса, наверное, получилась бы идеальная золотая середина. Поэтому, сейчас кажется, что лучше пройти и то и другое.
Egor Уровень 34
7 января 2026
толку от сложности старых задач? Я прошел пару лет назад весь старый курс(ещё когда Javarush говорил, что у них только курсы по Java будут), но решения задачек по типу легендарного ресторана плюшек не дало, только времени зря потерял, которое можно было потратить на другие технологии, а люди сидят на таких задачках неделями, а на самом сайте вообще по году(что очень странно, когда можно было пойти изучить Spring и т.д.). Новый курс даёт замечательную теорию(с удовольствием беру её для своих лекций), которая намного лучше старого курса, вместе с норм практикой, которая позволяет закрепить материал.
Роман Соловьев Уровень 53
27 ноября 2025
Задачи из этой лекции учат работать аля StackOverflow, просто берешь и копируешь код прям из примеров лекции %)
Ksanders Уровень 32
27 ноября 2025
Че-то уже надоели эти животные со своими звуками, 2 уровень подряд сплошые МУ и МЯУ 😏
Andrey Уровень 1
13 сентября 2025
18
Ihor Уровень 28
11 ноября 2025
Вижу, что почти прошли курс. Как впечатления?
Andrey Уровень 1
5 декабря 2025
Очень интересно было