Привіт! Ти вже використовуєш методи Java і знаєш про них багато чого. Як влаштований механізм перевизначення методів - 1Напевно, ти стикався з ситуацією, коли в одному класі було багато методів з однаковою назвою, але різними аргументами. Якщо пам'ятаєш, тоді ми використовували механізм перевантаження методів. Сьогодні розглянемо іншу ситуацію. Уяви, що в нас є один загальний метод, але він повинен робити різні речі залежно від того, в якому класі його викликано. Як реалізувати таку поведінку? Щоб розібратися, візьмемо батьківський клас Animal, що позначає тварин, і створимо в ньому метод voice — «голос»:

public class Animal {
  
   public void voice() {

       System.out.println("Голос!");
   }
}
Хоча ми тільки почали писати програму, потенційна проблема тобі, мабуть, очевидна: тварин у світі дуже багато, і всі вони «говорять» по-різному: коти нявкають, качки крякають, змії шиплять. Як влаштований механізм перевизначення методів  - 2 У нас проста мета: уникнути створення купи методів для подачі голосу. Замість того, щоб створювати методи voiceCat() для нявкання, voiceSnake() для шипіння тощо, ми хочемо, щоб при виклику методу voice() змія шипіла, кішка нявкала, а собака гавкав. Ми легко досягнемо цього за допомогою механізму перевизначення методів (Override в Java). Вікіпедія дає таке тлумачення терміна «перевизначення»: Перевизначення метода (англ. Method overriding) в об'єктно-орієнтованому програмуванні — одна з можливостей мови програмування, що дозволяє підкласу або дочірньому класу забезпечувати специфічну реалізацію методу, що вже реалізований в одному із суперкласів або батьківських класів. Воно загалом правильне. Перевизначення дозволяє взяти якийсь метод батьківського класу та написати у кожному класі-спадкоємці свою реалізацію цього методу. Нова реалізація «замінить» батьківську в дочірньому класі. Розглянемо, як це виглядає на прикладі. Створимо 4 класи-спадкоємці для нашого класу Animal:

public class Bear extends Animal {
   @Override
   public void voice() {
       System.out.println("Р-р-р!");
   }
}
public class Cat extends Animal {

   @Override
   public void voice() {
       System.out.println("Мяу!");
   }
}

public class Dog extends Animal {

   @Override
   public void voice() {
       System.out.println("Гав!");
   }
}


public class Snake extends Animal {

   @Override
   public void voice() {
       System.out.println("Ш-ш-ш!");
   }
}
Невеликий лайфхак на майбутнє: щоб перевизначити методи батьківського класу, перейди в код класу-спадкоємця в Intellij IDEa, натисни Ctrl+O та обери в меню «Override methods...». Звикай користуватись гарячими клавішами з початку, це прискорює написання програм! Щоб задати потрібну нам поведінку, ми зробили декілька речей:
  1. Створили в кожному класі-спадкоємці метод з такою ж назвою, що і у методу батьківського класу.
  2. Повідомили компілятору, що ми не просто так назвали метод так само, як у батьківському класі: хочемо перевизначити його поведінку. Для цього «повідомлення» компілятору ми поставили над методом аннотацію @Override («перевизначено»).
    Проставлена над методом анотація @Override повідомляє компілятору (та й програмістам, що читають твій код теж): «Все ок, це не помилка і не моя забудькуватість. Я пам'ятаю, що такий метод вже є, і хочу його перевизначити».

  3. Написали потрібну нам реалізацію для кожного класу-нащадка. Змія під час виклику voice() повинна шипіти, ведмідь — гарчати і т.д.
Давай подивимося, як це працюватиме у програмі:

public class Main {

   public static void main(String[] args) {

       Animal animal1 = new Dog();
       Animal animal2 = new Cat();
       Animal animal3 = new Bear();
       Animal animal4 = new Snake();
      
       animal1.voice();
       animal2.voice();
       animal3.voice();
       animal4.voice();
   }
}
Виведення у консолі: Гав! Няв! Р-р-р! Ш-ш-ш! Чудово, все працює як слід! Ми створили 4 змінні-посилання батьківського класу Animal і привласнили їм 4 різних об'єкти класів-спадкоємців. У результаті кожен об'єкт поводиться по-своєму. Для кожного із класів-спадкоємців перевизначений метод voice() замінив «рідний» метод voice() з класу Animal (який виводить у консолі просто «Голос!»). Як влаштований механізм перевизначення методів - 3 У перевизначення є низка обмежень:
  1. У перевизначеного методу мають бути ті ж аргументи, що і в батьківського методу.

    Якщо метод voice батьківського класу приймає на вхід String, перевизначений метод у класі-нащадку теж повинен приймати на вхід String, інакше компілятор видасть помилку:

    
    public class Animal {
    
       public void voice(String s) {
    
           System.out.println("Голос! " + s);
       }
    }
    
    public class Cat extends Animal {
    
       @Override//помилка!
       public void voice() {
           System.out.println("Няв!");
       }
    }
    

  2. У перевизначеного методу має бути той самий тип значення, що повертається, що і у батьківського методу

    Інакше ми отримаємо помилку компіляції:

    
    public class Animal {
    
       public void voice() {
    
           System.out.println("Голос!");
       }
    }
    
    
    public class Cat extends Animal {
    
       @Override
       public String voice() {         //помилка!
           System.out.println("Мяу!");
           return "Мяу!";
       }
    }
    

  3. Модифікатор доступу в перевизначеного методу також не може відрізнятись від «оригінального»:

    
    public class Animal {
    
       public void voice() {
    
           System.out.println("Голос!");
       }
    }
    
    public class Cat extends Animal {
    
       @Override
       private void voice() {      //помилка!
           System.out.println("Мяу!");
       }
    }
    
Перевизначення методів в Java — один із інструментів для реалізації ідеї поліморфізму. Тому головною перевагою його використання буде та сама гнучкість, про яку ми говорили раніше. Ми можемо вибудувати просту і логічну систему класів, кожен з яких матиме специфічну поведінку (собаки гавкають, кішки м'явкають), але єдиним інтерфейсом — один метод voice() на всіх замість купи методів voiceDog(), voiceCat() тощо.