— Аміго, ти любиш китів?
— Китів? Ні, не чув про таке.
— Це як корова, тільки більше й плаває. До речі, кити походять від корів. Ну, чи мали спільного з ними предка. Це не суттєво.

— Так ось. Хочу розповісти тобі про ще один дуже потужний інструмент ООП – це поліморфізм. Він має особливості.
1) Перевизначення методу.
Уяви, що ти для гри написав клас «Корова». У ньому є багато полів та методів. Об'єкти цього класу можуть робити різні речі: йти, їсти, спати. Ще корови дзвонять у дзвіночок, коли ходять. Припустимо, ти реалізував у класі все до дрібниць.

А тут приходить замовник проєкту та каже, що хоче випустити новий рівень гри, де всі дії відбуваються у морі, а головним героєм буде кит.
Ти почав проєктувати клас «Кит» і зрозумів, що він лише трохи відрізняється від класу «Корова». Логіка роботи обох класів дуже схожа, і ти вирішив використати успадкування.
Клас «Корова» ідеально підходить на роль класу-батька, там є всі необхідні змінні та методи. Достатньо лише додати киту можливість плавати. Але є проблема: у твого кита є ноги, роги та дзвіночок. Адже ця функціональність реалізована усередині класу «Корова». Що тут можна зробити?

До нас на допомогу приходить перевизначення (заміна) методів. Якщо ми успадкували метод, який робить не зовсім те, що потрібно в нашому новому класі, ми можемо замінити цей метод на інший.

Як же це робиться? У нашому класі-нащадку ми оголошуємо такий самий метод, як і метод класу батька, який хочемо змінити. Пишемо в ньому новий код. І все — начебто старого методу в класі-батьку і не було.
Ось як це працює:
Код | Опис |
---|---|
|
Тут визначено два класи Cow і Whale . Whale успадкований від Cow .
У класі |
|
Цей код виведе на екран напис «Я – корова» |
|
Цей код виведе на екран надпис «Я – кит» |
Після успадкування класу Cow
і перевизначення методу printName
, клас Whale
фактично містить такі дані та методи:
Код | Опис |
---|---|
|
Про жодний старий метод ми й не знаємо. |
— Відверто кажучи, очікувано.
2) Але це ще не все.
— Припустимо у класі Cow
є метод printAll
, який викликає два інших методи. Тоді код працюватиме так:
На екран буде виведено надпис. Я – білий Я – кит
Код | Опис |
---|---|
|
|
|
На екран буде виведено надпис Я – білий Я – кит |
Зверніть увагу, коли викликається метод printAll() написаний у класі Cow, в об'єкта типу Whale, буде використовуватися метод printName класу Whale, а не Cow.
Головне не в якому класі написаний метод, а який тип (клас) об'єкта, у якого цей метод викликаний.
— Зрозуміло.
— Успадковувати і перевизначати можна лише нестатичні методи. Статичні методи не успадковуються і, отже, не перевизначаються.
Ось як виглядає клас Whale після застосування успадкування та перевизначення методів:
Код | Опис |
---|---|
|
Ось як виглядає клас Whale після застосування успадкування та перевизначення методу. Про жодний старий метод printName ми і не знаємо. |
3) Приведення типів.
Тут є ще цікавіший момент. Оскільки клас при успадкуванні отримує всі методи та дані класу батька, об'єкт цього класу дозволено зберігати (привласнювати) у змінні класу батька (і батька батька, аж до Object). Приклад:
Код | Опис |
---|---|
|
На екран буде виведено надпис Я – біла |
|
На екран буде виведено напис Я – біла |
|
На екран буде виведено напис Whale@da435a Метод toString() успадкований від класу Object. |
— Дуже цікаво. А навіщо це може знадобитися?
— Це цінна властивість. Згодом ти зрозумієш, що дуже, дуже цінна.
4) Виклик методу об'єкта (динамічна диспетчеризація методів).
Ось як це виглядає:
Код | Опис |
---|---|
|
На екран буде виведено надпис Я – кит. |
|
На екран буде виведено надпис Я – кит. |
Зверни увагу, що на те, який саме метод printName викличеться, від класу Cow чи Whale, впливає не тип змінної, а тип об'єкту, на який вона посилається.
У змінній типу Cow збережене посилання на об'єкт типу Whale, і викличеться метод printName, описаний в класі Whale.
— Це непросто для розуміння.
— Так, це не дуже очевидно. Запам'ятай головне правило:
Набір методів, які можна викликати у змінної, визначається типом змінної. А який саме метод/яка реалізація викликається, визначається типом/класом об'єкта, посилання на який зберігає змінна.
— Спробую.
— Ти постійно стикатимешся з цим, так що скоро зрозумієш і більше ніколи не забудеш.
5) Розширення та звуження типів.
Для посилальних типів, тобто класів, приведення типів працює не так, як для примітивних типів. Хоча у посилальних типів теж є розширення і звуження типу. Наприклад:
Розширення типу | Опис |
---|---|
|
Класичне розширення типу. Тепер кита узагальнили (розширили) до корови, але в об'єкта типу Whale можна викликати лише ті методи, які описано в класі Cow.
Компілятор дозволить викликати у змінної cow тільки ті методи, які є у її типу — класу Cow. |
Звуження типу | Опис |
---|---|
|
Класичне звуження типу з перевіркою. Змінна cow типу Cow, зберігає посилання на об'єкт класу Whale. Ми перевіряємо, що це так і є, і потім виконуємо операцію перетворення (звуження) типу. Або як її ще називають – downcast. |
|
Посилальне звуження типу можна провести і без перевірки типу об'єкта. При цьому, якщо у змінній cow зберігався об'єкт не класу Whale, буде згенеровано виняток – InvalidClassCastException. |
6) А тепер ще на закуску. Виклик оригінального методу
Іноді тобі хочеться не замінити успадкований метод своїм при перевизначенні методу, а лише трохи доповнити його.
У цьому випадку дуже хочеться виконати в новому методі свій код і викликати той самий метод, але базового класу. І така можливість у Java є. Робиться це так: super.method()
.
Приклади:
Код | Опис |
---|---|
|
|
|
На екран буде виведено надпис Я – білий Це неправда: Я – корова Я – кит |
— Гм. Оце так лекція. Мої робо-вуха мало не розплавилися.
— Так, це непростий матеріал, він один із найскладніших. Професор обіцяв підкинути посилання на матеріали інших авторів, щоб ти, якщо все ж таки щось не зрозумієш, міг заповнити ці пробіли.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ