JavaRush /Курсы /JAVA 25 SELF /Переопределение методов (overriding): отличие от перегруз...

Переопределение методов (overriding): отличие от перегрузки

JAVA 25 SELF
18 уровень , 2 лекция
Открыта

1. Переопределение методов

Переопределение (overriding) — это возможность в подклассе написать собственную версию метода, который уже есть в родительском классе. Благодаря этому работает настоящий полиморфизм во время выполнения программы (run-time).

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

Пример из жизни.
Представьте, что у вас есть команда животных, и вы просите каждого «издать звук». Для всех это команда makeSound(), но собака лает, кошка мяукает, а корова мычит. Для кода это выглядит как вызов одного и того же метода, но результат разный — вот она, магия переопределения!

Синтаксис переопределения

Базовый пример

class Animal {
    void makeSound() {
        System.out.println("Животное издаёт какой-то звук");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Гав!");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Мяу!");
    }
}

Здесь в классе Animal определён метод makeSound(). Подклассы Dog и Cat переопределяют этот метод, предоставляя свою реализацию.

Аннотация @Override

В Java рекомендуется (и это хорошая привычка) отмечать переопределяемые методы аннотацией @Override:

@Override
void makeSound() { ... }

Это не обязательно для работы кода, но:

  • Компилятор проверит, действительно ли вы переопределяете метод (а не случайно опечатались в имени или параметрах).
  • Улучшает читаемость кода — другим программистам сразу видно, что этот метод переопределяет родительский.

Вызов переопределённого метода

Animal myDog = new Dog();
myDog.makeSound(); // Выведет: Гав!

Хотя переменная типа Animal, реально она указывает на объект Dog, и вызывается метод из класса Dog. Это и есть динамическое (позднее) связывание.

2. Отличие переопределения (overriding) от перегрузки (overloading)

Перегрузка (overloading)

  • В одном классе (или в иерархии, но всегда «рядом»).
  • Методы имеют одинаковое имя, но разные параметры (тип, количество, порядок).
  • Выбор метода происходит во время компиляции.
void print(int x) { ... }
void print(String s) { ... }

Переопределение (overriding)

  • В разных классах: метод объявлен в суперклассе и переопределён в подклассе.
  • Методы имеют одинаковое имя и сигнатуру (параметры и возвращаемый тип).
  • Выбор метода происходит во время выполнения (run-time), на основе фактического типа объекта.

Таблица-сравнение

Перегрузка (overloading) Переопределение (overriding)
Где В одном классе В суперклассе и подклассе
Имя метода Одинаковое Одинаковое
Параметры Разные Одинаковые
Возвращаемый тип Может отличаться Должен совпадать или быть подтипом
Когда выбирается Компиляция Выполнение (run-time)
Аннотация Не требуется @Override (рекомендуется)

3. Правила переопределения методов

Переопределение — штука мощная, но с ней связаны строгие правила. Давайте разберём их по порядку.

Сигнатура метода должна совпадать

  • Имя метода, типы и порядок параметров должны быть идентичны методу в суперклассе.
  • Возвращаемый тип должен совпадать или быть ковариантным (то есть подтипом возвращаемого типа родительского метода).

Пример с ковариантным возвращаемым типом:

class Animal {
    Animal reproduce() { return new Animal(); }
}
class Cat extends Animal {
    @Override
    Cat reproduce() { return new Cat(); } // ОК! Cat — подтип Animal
}

Модификатор доступа

Модификатор доступа переопределённого метода не может быть более строгим, чем у метода в суперклассе. Если метод в родителе public, то в подклассе он обязан остаться таким же public. Сделать его менее доступным (protected или private) нельзя.

Пример:

class Parent {
    public void greet() { }
}
class Child extends Parent {
    // void greet() { } // Ошибка! Модификатор по умолчанию — package-private, менее доступен, чем public
    @Override
    public void greet() { } // ОК
}

Исключения

  • Переопределённый метод не может выбрасывать новые checked-исключения, которые не объявлены в базовом методе.
  • Можно выбрасывать меньше или те же исключения.

Пример:

class Parent {
    void doWork() throws IOException { }
}
class Child extends Parent {
    @Override
    void doWork() throws FileNotFoundException { } // ОК, FileNotFoundException — подтип IOException
    // void doWork() throws SQLException { } // Ошибка! SQLException не объявлен в родителе
}

Статические методы не переопределяются

Статические методы могут быть скрыты (hidden), но не переопределены. Если вы объявите статический метод с такой же сигнатурой в подклассе, это не переопределение! Это будет просто скрытие метода, а не полиморфизм.

class Animal {
    static void info() { System.out.println("Animal"); }
}
class Dog extends Animal {
    static void info() { System.out.println("Dog"); }
}

Вызов Dog.info() выведет "Dog", но если вызвать через переменную типа Animal, будет вызван метод Animal.info(). Это не полиморфизм!

final-методы нельзя переопределять

Если метод в суперклассе объявлен как final, попытка переопределить его приведёт к ошибке компиляции.

class Animal {
    final void sleep() { }
}
class Dog extends Animal {
    // @Override
    // void sleep() { } // Ошибка! Нельзя переопределять final-метод
}

4. Практические примеры

Давайте рассмотрим на практике, как работает переопределение и чем оно отличается от перегрузки.

Пример 1: Класс Shape и его потомки

class Shape {
    void draw() {
        System.out.println("Рисуем фигуру");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Рисуем круг");
    }
}

class Rectangle extends Shape {
    @Override
    void draw() {
        System.out.println("Рисуем прямоугольник");
    }
}

Используем полиморфизм:

public class Main {
    public static void main(String[] args) {
        Shape s1 = new Circle();
        Shape s2 = new Rectangle();
        s1.draw(); // Рисуем круг
        s2.draw(); // Рисуем прямоугольник
    }
}

Хотя переменные объявлены как Shape, вызывается метод именно того класса, к которому реально относится объект.

Пример 2: Отличие от перегрузки

class Printer {
    void print(String s) {
        System.out.println("Строка: " + s);
    }

    void print(int n) {
        System.out.println("Число: " + n);
    }
}

Здесь оба метода называются print, но у них разные параметры — это перегрузка, а не переопределение.

5. Переопределение и вызов родительского метода (super)

Иногда в переопределённом методе хочется сначала выполнить логику родителя, а потом добавить своё. Для этого используется ключевое слово super.

class Animal {
    void makeSound() {
        System.out.println("Животное издаёт звук");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        super.makeSound(); // Вызов метода родителя
        System.out.println("Гав!");
    }
}

Вызов new Dog().makeSound() выведет:

Животное издаёт звук
Гав!

Как работает динамическое связывание (late binding)

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

Animal a = new Cat();
a.makeSound(); // Вызовет Cat.makeSound(), а не Animal.makeSound()

Это и есть основа полиморфизма в Java.

6. Как это связано с вашим приложением

В нашем учебном приложении (например, системе учёта сотрудников) вы можете создать базовый класс Employee с методом work(), а подклассы Manager и Developer могут по-своему реализовать этот метод:

class Employee {
    void work() {
        System.out.println("Сотрудник работает");
    }
}

class Manager extends Employee {
    @Override
    void work() {
        System.out.println("Менеджер руководит");
    }
}

class Developer extends Employee {
    @Override
    void work() {
        System.out.println("Разработчик пишет код");
    }
}

Теперь вы можете хранить всех сотрудников в одном массиве или списке:

Employee[] employees = {new Manager(), new Developer(), new Developer()};
for (Employee e : employees) {
    e.work(); // Для каждого — свой вывод!
}

7. Типичные ошибки при переопределении методов

Ошибка №1: опечатка в имени метода или параметрах. Если вы случайно ошиблись в имени метода или его параметрах, вы не переопределяете метод, а создаёте новый. В результате полиморфизм не работает. Именно поэтому всегда используйте аннотацию @Override — компилятор сразу вас поправит.

Ошибка №2: более строгий модификатор доступа. Если в родителе метод public, а в дочернем классе вы объявили его как protected или без модификатора — получите ошибку компиляции.

Ошибка №3: попытка переопределить static или final метод. Статические методы не переопределяются, а final-методы вообще не поддаются переопределению. Если вы это попробуете — компилятор вас остановит.

Ошибка №4: изменение типа возвращаемого значения на несовместимый. Если возвращаемый тип метода в подклассе не совпадает с типом в суперклассе (и не является его подтипом), компилятор не позволит переопределять метод.

Ошибка №5: добавление новых checked-исключений. Переопределённый метод не может выбрасывать новые checked-исключения, которых нет в объявлении базового метода. Если это сделать — компилятор выдаст ошибку.

Ошибка №6: забыли про super. Если в переопределённом методе вы хотите сохранить часть поведения родителя, не забудьте явно вызвать super.methodName(). Java сама этого не сделает.

Теперь вы знаете, как работает переопределение методов, чем оно отличается от перегрузки, и как с его помощью реализуется полиморфизм в Java. В следующей лекции мы рассмотрим, как применять полиморфизм на практике — с коллекциями, массивами и реальными задачами!

1
Задача
JAVA 25 SELF, 18 уровень, 2 лекция
Недоступна
Голоса зоопарка: Переопределение метода в подклассе
Голоса зоопарка: Переопределение метода в подклассе
1
Задача
JAVA 25 SELF, 18 уровень, 2 лекция
Недоступна
Магия печати: Отличие между переопределением и перегрузкой
Магия печати: Отличие между переопределением и перегрузкой
1
Задача
JAVA 25 SELF, 18 уровень, 2 лекция
Недоступна
Искусство рисования: Использование аннотации @Override
Искусство рисования: Использование аннотации @Override
1
Задача
JAVA 25 SELF, 18 уровень, 2 лекция
Недоступна
Музыкальное представление питомца: Вызов родительского метода с помощью super
Музыкальное представление питомца: Вызов родительского метода с помощью super
Комментарии (10)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
10 марта 2026
Мне кажется, будто я застрял во временной петле. Уже который час одним и тем же занимаюсь.
DmitryMandgik #6469384 Уровень 21
10 марта 2026
«Я уже говорил тебе, что такое безумие? Безумие - это точное повторение одного и того же действия, раз за разом, в надежде на изменение»
kasnil Уровень 6
19 января 2026
Отличие переопределения (overriding) от перегрузки (overloading): Перегрузка (overloading): В одном классе (или в иерархии, но всегда «рядом»). Таблица-сравнение: Перегрузка (overloading): В одном классе
Ksanders Уровень 32
27 ноября 2025
У меня все больше впечатления что часть курса писал чат ГПТ. Во-первых лекция почти полностью дублирует по тексту лекцию 2 уровень 17. Во-вторых: первая задача этой лекции ПОЛНОСТЬЮ копирует задачу 2 из лекции 2 уровень 17. Только тут собака говорит по-русски, а там говорила на-английском. Я думал у меня уже крыша едет, но это уже совсем КАПЕЦ ТОВАРИЩИ! Сравните сами: Тоже самое и с задачей про круг 🧐 Полный дубль задачи из уровня про Наследование. JavaRush ты точно с ума сведешь таким темпами. 👹👹👹
Cherepoq Уровень 19
12 марта 2026
Я проходил лекции в разные дни и на третьей возникло чувство, что у меня браузер забагался или вкладка открылась старая, читаешь 1 в 1 тот же текст
nastya_zhadan Уровень 66
22 сентября 2025
Тяжело выполнять много однотипных задач..
Anton Pohodin Уровень 27
7 октября 2025
Да уж... зато руку набьете и собеседование не завалите)
Ksanders Уровень 32
27 ноября 2025
Только на это вся и надежда, что когда дойдет до дела - руки магическим образом сделают все сами!
Ksanders Уровень 32
27 ноября 2025
Но в этом есть и определенная проблема - в оригинальном курсе, насколько я помню, задачи были более комплексные. Были простые - где просто закреплялась теория, а были посложнее - где вместе с пройденным материалом текущей лекции нужно было подумать и использовать знания, полученные в прошлых лекциях. Посмотрим, что из этого выйдет.
Anton Pohodin Уровень 27
27 ноября 2025
Лично я просто закрываю гештальт не закрытый в 2015 году. Тогда я не закончил JavaRush, но сейчас закончу. Но и сейчас уже совершенно ясно, что Java - это не мой язык. Мне больше по душе C# с его обширной и полностью стандартизированной экосистемой. Вот, что значит, когда язык разрабатывает одна компания. Но если бы не куча совершенно ненужного бойлерплейта, Java был бы вполне хорошим и понятным для меня языком программирования... А так Java в погоне за кроссплатформенностью утратила самое привлекательное в разработке - лёгкость в написании кода и, соответственно, быстрый debug.