1. Перевизначення методу
У житті часто трапляються ситуації, коли «нащадок» поводиться по‑особливому. Наприклад, усі тварини вміють видавати звуки, та у кішки — «няв», у собаки — «гав», а у програміста — «ой, знову баг!». У програмуванні це реалізується через перевизначення методу (override).
Перевизначення методу — це коли підклас надає власну реалізацію методу, уже оголошеного в батьківському класі. Тобто «підміняє» стандартну поведінку на свою, специфічнішу.
Аналогія. Якщо уявити батьківський клас як фірмовий рецепт борщу, то перевизначення методу — це коли бабуся додає до нього свій секретний інгредієнт. Борщ лишається борщем, але смак у кожного — свій.
Щоб перевизначити метод, у підкласі слід оголосити метод із точно такою самою сигнатурою (імʼя та параметри), як у батьківського, і з тим самим або коваріантним типом, що повертається.
Приклад: тварини та їхні звуки
class Animal {
void makeSound() {
System.out.println("Some generic animal sound");
}
}
class Dog extends Animal {
// Перевизначаємо метод makeSound()
void makeSound() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
// Перевизначаємо метод makeSound()
void makeSound() {
System.out.println("Meow!");
}
}
Тепер, якщо створити обʼєкт Dog і викликати makeSound(), ви почуєте «Woof!», а не «Some generic animal sound».
Демонстрація в коді
public class Main {
public static void main(String[] args) {
Animal generic = new Animal();
Dog dog = new Dog();
Cat cat = new Cat();
generic.makeSound(); // Some generic animal sound
dog.makeSound(); // Woof!
cat.makeSound(); // Meow!
}
}
Важливо: якщо підклас не має методу з такою самою сигнатурою, буде використано метод батьківського класу.
2. Анотація @Override: для чого потрібна і як використовувати
У Java прийнято позначати перевизначені методи спеціальною анотацією @Override. Це не просто прикраса для коду, а корисний інструмент:
- Компілятор перевіряє, чи справді ви перевизначаєте метод батьківського класу. Якщо ви помилилися в імені, типі параметра або типі, що повертається, — компілятор видасть помилку.
- Підвищує читабельність коду. Інший розробник одразу бачить: «О, цей метод перевизначає батьківський».
Приклад із @Override
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
Якщо випадково написати void makeSond() (друкарська помилка!) у методі, позначеному анотацією @Override, компілятор поскаржиться: «Method does not override or implement a method from a supertype».
Сучасні стандарти. Використовувати @Override — це добрий тон і стандарт індустрії. Навіть якщо компілятор цього не вимагає, завжди додавайте цю анотацію — і вам, і колегам буде простіше жити.
3. Як працює виклик перевизначеного методу
Коли ви викликаєте метод на обʼєкті підкласу, буде використано реалізацію підкласу, навіть якщо змінну оголошено як батьківський тип.
Приклад: поліморфізм у дії
Animal animal = new Dog();
animal.makeSound(); // "Woof!", а не "Some generic animal sound"
Тут змінна типу Animal, але насправді в ній лежить обʼєкт Dog. Java «розуміє», що потрібно викликати перевизначений метод із Dog. Це і є поліморфізм (детальніше — у наступних лекціях).
4. Обмеження та правила перевизначення
Сигнатура методу
- Імʼя, типи й порядок параметрів мають збігатися з методом у батьківському класі.
- Тип, що повертається, має збігатися або бути коваріантним (підтипом типу, що повертається у батьківського методу). Наприклад, якщо батьківський метод повертає Animal, а дочірній — Dog, це дозволено.
Модифікатори доступу
- Не можна зробити доступ суворішим, ніж у батьківського методу.
- Якщо батьківський метод public, то й перевизначений має бути public.
- Якщо батьківський — protected, то перевизначений може бути protected або public.
Якщо спробувати зробити навпаки, компілятор скаже: «Cannot reduce the visibility of the inherited method».
Винятки
- Перевизначений метод не може викидати новий checked‑виняток, якого немає в оголошенні батьківського методу.
- Можна викидати менше винятків, ніж батьківський метод, або їхні підтипи.
static, final, private
- Не можна перевизначити методи, оголошені як static або final, а також приватні (private).
- static — це приховування (hiding), а не перевизначення.
- final — такі методи неможливо перевизначити: Java їх захищає.
- private — їх не видно нащадкам, перевизначити не можна (можна лише оголосити новий метод із тим самим імʼям).
Конструктори
Конструктори не успадковуються і не перевизначаються. У кожного класу свої конструктори.
5. Розвиваємо навчальний застосунок «Зоопарк»
Час застосувати теорію на практиці. Продовжімо розвивати наш застосунок «Зоопарк».
Крок 1. Базовий клас Animal
public class Animal {
public void makeSound() {
System.out.println("Some generic animal sound");
}
public void sleep() {
System.out.println("Zzz...");
}
}
Крок 2. Підкласи Dog і Cat
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof!");
}
// Додатковий метод лише для Dog
public void fetch() {
System.out.println("Dog brings the stick!");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow!");
}
// Додатковий метод лише для Cat
public void scratch() {
System.out.println("Cat scratches the sofa!");
}
}
Крок 3. Використовуємо перевизначення
public class ZooTest {
public static void main(String[] args) {
Animal generic = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
generic.makeSound(); // Some generic animal sound
dog.makeSound(); // Woof!
cat.makeSound(); // Meow!
// dog.fetch(); // Помилка! Змінна типу Animal не знає про fetch()
// cat.scratch(); // Аналогічно
// Але якщо виконати явне перетворення типу:
if (dog instanceof Dog) {
((Dog) dog).fetch(); // Dog brings the stick!
}
if (cat instanceof Cat) {
((Cat) cat).scratch(); // Cat scratches the sofa!
}
}
}
Коментар:
Метод makeSound() працює поліморфно — викликається версія з реального класу обʼєкта. Натомість специфічні методи (fetch, scratch) доступні лише через явне перетворення типу — це важливо для розуміння, як працюють успадкування та перевизначення.
6. Приклад із типом, що повертається (коваріантність)
Іноді хочеться, щоб перевизначений метод повертав більш «вузький» тип. Наприклад:
class Animal {
Animal getFriend() {
return new Animal();
}
}
class Dog extends Animal {
@Override
Dog getFriend() { // Тип, що повертається, — Dog, підтип Animal
return new Dog();
}
}
Це називається коваріантністю типу, що повертається, і дозволено в Java (починаючи з Java 5).
7. Що буде, якщо не використовувати @Override?
Якщо ви випадково помилилися в імені методу або параметрах, Java не скаржитиметься, якщо немає анотації @Override. У результаті ви не перевизначите, а створите новий метод, і очікувана поведінка не зміниться.
Приклад помилки
class Dog extends Animal {
// Друкарська помилка: makeSoud замість makeSound
void makeSoud() {
System.out.println("Woof!");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.makeSound(); // Виведе "Some generic animal sound"
}
}
Якби стояла анотація @Override, компілятор видав би помилку: «Method does not override or implement a method from a supertype».
8. Типові помилки під час перевизначення методів
Помилка № 1: відсутність анотації @Override.
Без неї легко помилитися в імені методу або параметрах. У результаті метод не буде перевизначено, і програма поводитиметься не так, як ви очікували.
Помилка № 2: спроба звузити модифікатор доступу.
Якщо батьківський метод public, а ви пишете protected або private, — отримаєте помилку компіляції.
Помилка № 3: невідповідність сигнатури.
Якщо параметри відрізняються хоча б за типом — це вже не перевизначення, а перевантаження (overloading).
Помилка № 4: спроба перевизначити метод final або static.
Java цього не дозволить: final захищає від перевизначення, а методи static взагалі не перевизначаються (лише приховуються).
Помилка № 5: зміна типу, що повертається, на несумісний.
Можна повертати лише підтип типу, який повертає батьківський метод (коваріантність), але не зовсім інший тип.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ