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

1
Задача
JAVA 25 SELF, 17 уровень, 1 лекция
Недоступна
Виртуальный художник: Как рисовать круг 🎨
Виртуальный художник: Как рисовать круг 🎨
1
Задача
JAVA 25 SELF, 17 уровень, 1 лекция
Недоступна
Звуки зоопарка: Лай собаки 🐕
Звуки зоопарка: Лай собаки 🐕
1
Задача
JAVA 25 SELF, 17 уровень, 1 лекция
Недоступна
Иерархия приветствий: Кто кого приветствует? 🤝
Иерархия приветствий: Кто кого приветствует? 🤝
1
Задача
JAVA 25 SELF, 17 уровень, 1 лекция
Недоступна
Фруктовые корзины: Что внутри? 🍏
Фруктовые корзины: Что внутри? 🍏
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ksanders Уровень 32
26 ноября 2025
Не очень понятно в чем разница между задачей 2 и 3 - отличаются только тем. что метод public
Overbyaka Уровень 29
4 февраля 2026
Стоит обращать внимание, что уже автоматически написано в main 😁