JavaRush /Курси /JAVA 25 SELF /Перевизначення методів (override), анотація @Override

Перевизначення методів (override), анотація @Override

JAVA 25 SELF
Рівень 17 , Лекція 1
Відкрита

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: зміна типу, що повертається, на несумісний.
Можна повертати лише підтип типу, який повертає батьківський метод (коваріантність), але не зовсім інший тип.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ