Переопределение метода (аннотация @Override) в Java это возможность класса-наследника задать свою реализацию метода, уже описанного в родительском классе. При вызове метода у объекта выполняется версия из его собственного класса, а не из родителя. Так реализуется полиморфизм: один метод voice() ведет себя по-разному у Dog, Cat и Snake.

Кратко

  • Переопределение (@Override) это своя реализация родительского метода в классе-наследнике.
  • При вызове метода у объекта выполняется версия из его класса, а не из родителя.
  • Аннотация @Override не обязательна, но защищает от ошибок: компилятор проверит, что метод действительно переопределяет родительский.
  • У переопределенного метода те же имя и аргументы, совместимый тип возврата и не более строгий модификатор доступа, чем у родителя.
  • Переопределение (override) отличается от перегрузки (overload): перегрузка это методы с одним именем, но разными аргументами.
  • Переопределение это основной инструмент полиморфизма.
Привет! Ты уже используешь методы в Java и знаешь о них многое. Наверняка ты сталкивался с ситуацией, когда в одном классе было много методов с одинаковым названием, но разными аргументами. Если помнишь, в тех случаях мы использовали механизм перегрузки методов. Сегодня рассмотрим другую ситуацию. Представь, что у нас есть один общий метод, но он должен делать разные вещи в зависимости от того, в каком классе он был вызван. Как реализовать такое поведение? Чтобы разобраться, возьмем родительский класс Animal, обозначающий животных, и создадим в нем метод voice: «голос»:

public class Animal {
  
   public void voice() {

       System.out.println("Голос!");
   }
}
Хотя мы только начали писать программу, потенциальная проблема тебе, скорее всего, видна: животных в мире очень много, и все «говорят» по-разному: кошки мяукают, утки крякают, змеи шипят. Разные животные подают голос по-разному: мяукают, лают, шипят Наша цель проста: избежать создания кучи методов для подачи голоса. Вместо того, чтобы создавать методы voiceCat() для мяуканья, voiceSnake() для шипения и т.д., мы хотим, чтобы при вызове метода voice() змея шипела, кошка мяукала, а собака лаяла.

Что такое переопределение метода

Мы легко добьемся этого с помощью механизма переопределения методов (Override в Java). Википедия дает такое пояснение термина «переопределение»: Переопределение метода (англ. Method overriding) в объектно-ориентированном программировании, одна из возможностей языка программирования, позволяющая подклассу или дочернему классу обеспечивать специфическую реализацию метода, уже реализованного в одном из суперклассов или родительских классов. Оно, в общем-то, правильное. Переопределение позволяет взять какой-то метод родительского класса и написать в каждом классе-наследнике свою реализацию этого метода. Новая реализация «заменит» родительскую в дочернем классе.

Как переопределить метод (@Override)

Рассмотрим, как это выглядит на примере. Создадим 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 (который выводит в консоль просто «Голос!»). Переопределенный метод voice() заменяет родительский в каждом классе-наследнике

Ограничения переопределения

У переопределения есть ряд ограничений:
  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. У переопределенного метода должен быть тот же тип возвращаемого значения, что и у метода родителя.

    Точнее, тип возврата может быть таким же или его подтипом (это называется ковариантным возвратом и работает начиная с Java 5). А вот несовместимые типы (например, void и String) вызовут ошибку компиляции:

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

  3. Модификатор доступа у переопределенного метода можно сделать таким же или более открытым, но нельзя сузить.

    Например, заменить public на private нельзя, это вызовет ошибку:

    
    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() и т.д.

Переопределение и перегрузка: в чем разница

Их часто путают, поэтому сведем различия в таблицу:
ПризнакПереопределение (override)Перегрузка (overload)
Что этосвоя реализация метода родителя в наследникенесколько методов с одним именем в одном классе
Имя методасовпадает с родительскимодинаковое у всех перегрузок
Аргументытакие же, как у родителяразные (число или типы)
Гдеродитель и наследник (наследование)обычно один класс
Аннотация@Override (рекомендуется)не нужна
Связь с ООПполиморфизмудобство вызова
Больше о переопределении есть в документации Oracle (Overriding Methods).

Читайте также

Вопросы и ответы

Что такое переопределение метода в Java?

Это когда класс-наследник задает свою реализацию метода, уже описанного в родительском классе. При вызове у объекта выполняется его версия метода, а не родительская.

Зачем нужна аннотация @Override?

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

Чем переопределение отличается от перегрузки?

Переопределение (override) это своя реализация родительского метода в наследнике с той же сигнатурой. Перегрузка (overload) это несколько методов с одним именем, но разными аргументами, обычно в одном классе.

Обязательна ли аннотация @Override?

Нет, метод переопределяется и без нее. Но ее рекомендуют ставить: она защищает от ошибок и делает код понятнее.

Какие правила у переопределенного метода?

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

Как переопределение связано с полиморфизмом?

Благодаря переопределению у объектов разных классов один и тот же метод (например, voice()) работает по-разному. Это и есть полиморфизм: единый интерфейс, разное поведение.