JavaRush/Курсы/Java Core/Полиморфизм и переопределение

Полиморфизм и переопределение

Открыта

— Амиго, ты любишь китов?

— Китов? Не, не слышал.

— Этот как корова, только больше и плавает. Кстати, киты произошли от коров. Ну, или имели общего с ними предка. Не столь важно.

Полиморфизм и переопределение - 1

— Так вот. Хочу рассказать тебе об еще одном очень мощном инструменте ООП – это полиморфизм. У него есть особенности.

1) Переопределение метода.

Представь, что ты для игры написал класс «Корова». В нем есть много полей и методов. Объекты этого класса могут делать разные вещи: идти, есть, спать. Еще коровы звонят в колокольчик, когда ходят. Допустим, ты реализовал в классе все до мелочей.

Полиморфизм и переопределение - 2

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

Ты начал проектировать класс «Кит» и понял, что он лишь немного отличается от класса «Корова». Логика работы обоих классов очень похожа, и ты решил использовать наследование.

Класс «Корова» идеально подходит на роль класса-родителя, там есть все необходимые переменные и методы. Достаточно только добавить киту возможность плавать. Но есть проблема: у твоего кита есть ноги, рога и колокольчик. Ведь эта функциональность реализована внутри класса «Корова». Что тут можно сделать?

Полиморфизм и переопределение - 3

К нам на помощь приходит переопределение (замена) методов. Если мы унаследовали метод, который делает не совсем то, что нужно нам в нашем новом классе, мы можем заменить этот метод на другой.

Полиморфизм и переопределение - 4

Как же это делается? В нашем классе-потомке мы объявляем такой же метод, как и метод класса родителя, который хотим изменить. Пишем в нем новый код. И все – как будто старого метода в классе-родителе и не было.

Вот как это работает:

Код Описание
class Cow {
  public void printColor() {
    System.out.println("Я - белая");
  }

  public void printName() {
    System.out.println("Я - корова");
  }
}

class Whale extends Cow {
  public void printName() {
    System.out.println("Я - кит");
  }
}
Тут определены два класса Cow и Whale. Whale унаследован от Cow.








В классе Whale переопределен метод printName();

public static void main(String[] args) {
  Cow cow = new Cow();
  cow.printName();
}
Данный код выведет на экран надпись «Я – корова»
public static void main(String[] args) {
  Whale whale = new Whale();
  whale.printName();
}
Данный код выведет на экран «Я – кит»

После наследования класса Cow и переопределения метода printName, класс Whale фактически содержит такие данные и методы:

Код Описание
class Whale {
  public void printColor() {
    System.out.println("Я - белая");
  }

  public void printName() {
    System.out.println("Я - кит");
  }
}
Ни о каком старом методе мы и не знаем.

— Честно говоря, ожидаемо.

2) Но это еще не все.

— Предположим в классе Cow есть метод printAll, который вызывает два других метода, тогда код будет работать так:

На экран будет выведена надпись Я – белая Я – кит

Код Описание
class Cow {
  public void printAll() {
    printColor();
    printName();
  }

  public void printColor() {
    System.out.println("Я - белая");
  }

  public void printName() {
    System.out.println("Я - корова");
  }
}

class Whale extends Cow {
  public void printName() {
    System.out.println("Я - кит");
  }
}
public static void main(String[] args) {
  Whale whale = new Whale();
  whale.printAll();
}
На экран будет выведена надпись
Я – белая
Я – кит

Обрати внимание, когда вызываются метод printAll() написанный в классе Cow, у объекта типа Whale, то будет использован метод printName класса Whale, а не Cow.

Главное, не в каком классе написан метод, а какой тип (класс) объекта, у которого этот метод вызван.

— Ясно.

— Наследовать и переопределять можно только нестатические методы. Статические методы не наследуются и, следовательно, не переопределяются.

Вот как выглядит класс Whale после применения наследования и переопределения методов:

Код Описание
class Whale {
  public void printAll() {
    printColor();
    printName();
  }

  public void printColor() {
    System.out.println("Я - белая");
  }

  public void printName() {
    System.out.println("Я - кит");
  }
}
Вот как выглядит класс Whale, после применения наследования и переопределения метода. Ни о каком старом методе printName мы и не знаем.

3) Приведение типов.

Тут есть еще более интересный момент. Т.к. класс при наследовании получает все методы и данные класса родителя, то объект этого класса разрешается сохранять (присваивать) в переменные класса родителя (и родителя родителя, и т.д., вплоть до Object). Пример:

Код Описание
public static void main(String[] args) {
  Whale whale = new Whale();
  whale.printColor();
}
На экран будет выведена надпись
Я – белая
public static void main(String[] args) {
  Cow cow = new Whale();
  cow.printColor();
}
На экран будет выведена надпись
Я – белая
public static void main(String[] args) {
  Object o = new Whale();
  System.out.println(o.toString());
}
На экран будет выведена надпись
Whale@da435a
Метод toString() унаследован от класса Object.

— Очень интересно. А зачем это может понадобиться?

— Это ценное свойство. Позже ты поймешь, что очень, очень ценное.

4) Вызов метода объекта (динамическая диспетчеризация методов).

Вот как это выглядит:

Код Описание
public static void main(String[] args) {
  Whale whale = new Whale();
  whale.printName();
}
На экран будет выведена надпись
Я – кит.
public static void main(String[] args) {
  Cow cow = new Whale();
  cow.printName();
}
На экран будет выведена надпись
Я – кит.

Обрати внимание, что на то, какой именно метод printName вызовется, от класса Cow или Whale, влияет не тип переменной, а тип – объекта, на который она ссылается.

В переменной типа Cow сохранена ссылка на объект типа Whale, и будет вызван метод printName, описанный в классе Whale.

— Это не просто для понимания.

— Да, это не очень очевидно. Запомни главное правило:

Набор методов, которые можно вызвать у переменной, определяется типом переменной. А какой именно метод/какая реализация вызовется, определяется типом/классом объекта, ссылку на который хранит переменная.

— Попробую.

— Ты будешь постоянно сталкиваться с этим, так что скоро поймешь и больше никогда не забудешь.

5) Расширение и сужение типов.

Для ссылочных типов, т.е. классов, приведение типов работает не так, как для примитивных типов. Хотя у ссылочных типов тоже есть расширение и сужение типа. Пример:

Расширение типа Описание
Cow cow = new Whale();
Классическое расширение типа. Теперь кита обобщили (расширили) до коровы, но у объекта типа Whale можно вызывать только методы, описанные в классе Cow.

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

Сужение типа Описание
Cow cow = new Whale();
if (cow instanceof Whale) {
  Whale whale = (Whale) cow;
}
Классическое сужение типа с проверкой. Переменная cow типа Cow, хранит ссылку на объект класса Whale.
Мы проверяем, что это так и есть, и затем выполняем операцию преобразования (сужения) типа. Или как ее еще называют – downcast.
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
Ссылочное сужение типа можно провести и без проверки типа объекта.
При этом, если в переменной cow хранился объект не класса Whale, будет сгенерировано исключение – InvalidClassCastException.

6) А теперь еще на закуску. Вызов оригинального метода

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

В этом случае очень хочется исполнить в новом методе свой код и вызвать этот же метод, но базового класса. И такая возможность в Java есть. Делается это так: super.method().

Примеры:

Код Описание
class Cow {
  public void printAll() {
    printColor();
    printName();
  }

  public void printColor() {
    System.out.println("Я – белый");
  }

  public void printName() {
    System.out.println("Я – корова");
  }
}

class Whale extends Cow {
  public void printName() {
    System.out.print("Это неправда: ");
    super.printName();

    System.out.println("Я – кит");
  }
}
public static void main(String[] args){
  Whale whale = new Whale();
  whale.printAll();
}
На экран будет выведена надпись
Я – белый
Это неправда: Я – корова
Я – кит

— Гм. Ничего себе лекция. Мои робо-уши чуть не расплавились.

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

Комментарии (550)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Grigoryvvv
Уровень 28
Expert
9 августа, 07:02
09.08.2026 / 22 уровень
Rei
Уровень 32
22 февраля, 17:40
Уровни джавараша зациклились
C0N5P1RACY QA Automation Engineer
24 января, 17:24
Я это уже видел
Anonymous #3585174
Уровень 33
30 июля 2025, 07:45
like
13 мая 2025, 16:43
по моему тут ошибка написана в лекций в самом начале во втором пункте про наследовательность, сказано мы не можем наследовать статические методы и переопределять их, только что проверил все работает
Андрей
Уровень 28
16 июня 2025, 18:10
Не совсем Статический метод нельзя переопределить, но джава позволяет его "затенить" или скрыть Мутная тема на самом деле =) То есть, в классе наследнике может быть метод с такой же сигнатурой что в классе родителе и все будет работать, но это не будет переопределением Вот, немного нагруженная, но подробная статья обо всем этом Тык
Cryptosin Crypto Magnat
21 июля 2025, 05:51
в Java нельзя переопределить статические методы — но здесь есть важное уточнение: их можно "скрыть" (hide), и это часто воспринимается как "работает" или "переопределяется", хотя технически это не так.,
class Parent {
    static void sayHello() {
        System.out.println("Hello from Parent");
    }
}

class Child extends Parent {
    static void sayHello() {
        System.out.println("Hello from Child");
    }
}

public class Test {
    public static void main(String[] args) {
        Parent p = new Child();
        p.sayHello();  // Выведет "Hello from Parent"

        Child c = new Child();
        c.sayHello();  // Выведет "Hello from Child"
    }
}
Что происходит:
Parent p = new Child(); p.sayHello();
— вызовется Parent.sayHello(), потому что статические методы привязаны к классу, а не к объекту. Здесь p имеет тип Parent, значит будет вызван метод sayHello() именно из Parent.
Child c = new Child(); c.sayHello();
— а тут уже тип Child, значит вызовется метод из Child. Вывод: ✅ Можно создать статический метод с тем же именем в подклассе — это не переопределение, а скрытие (method hiding). ❌ Полиморфизм (динамический вызов) на статические методы не работает. То есть JVM не выбирает метод на основе объекта во время выполнения, как с обычными методами. ✅ Да, "всё работает" — но не так, как с обычными (нестатическими) методами. НО! static void sayHello()
Parent c = new Child();
c.sayHello(); // Выведет: "Hello from Parent"
ЕЩЕ ОДНО НО! static void sayHello()
Parent c = new Child();
c.sayHello(); // Выведет "Child" — потому что это полиморфизм
illia
Уровень 32
4 марта 2025, 09:17
Тип ссылки (Cow) ограничивает, какие методы можно вызывать. Тип объекта (Whale) определяет, какая реализация методов будет выполнена (полиморфизм). Если метод есть только в Whale, его нельзя вызвать через Cow, пока не сделано приведение типа.
neptun020202
Уровень 32
27 февраля 2025, 08:06
На предыдущем уровне нас учили наследованию всяких зверушек от Animal и Pet. А сами тут кита от коровы наследуют. Сказочные...
9 января 2025, 15:57
👁
27 декабря 2024, 20:42
А есть ли смысл в создании статического метода внутри класса Cow, и если да, что он мог бы делать?
Vitalii Shevchenko
Уровень 1
1 апреля 2025, 03:20
Наприклад, рахувать кількість створених корів
Zick_Zibn
Уровень 6
12 декабря 2024, 21:01
Повторение мать учения! Так что ли?