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

Ответ

Переопределение методов (method overriding) — это механизм в объектно-ориентированном программировании, позволяющий дочернему классу предоставить свою, специфическую реализацию метода, который уже определён в его родительском классе.

Условия корректного переопределения в Java:

  1. Та же сигнатура: Имя метода, тип, количество и порядок параметров должны совпадать.
  2. Совместимый возвращаемый тип: В Java 5+ допускается ковариантный возвращаемый тип (можно вернуть подтип исходного типа).
  3. Нельзя сужать модификатор доступа: Доступ в дочернем классе должен быть таким же или шире (public → public, protected → public/protected).
  4. Нельзя добавлять новые проверяемые исключения: Можно добавлять только непроверяемые (unchecked) или сужать тип исключений.

Пример переопределения:

class Animal {
    // Метод, который будет переопределён
    public void makeSound() {
        System.out.println("The animal makes a sound");
    }

    protected String getFood() {
        return "some food";
    }
}

class Dog extends Animal {
    // Переопределение метода makeSound()
    @Override // Аннотация @Override — строгая рекомендация для проверки компилятором
    public void makeSound() {
        System.out.println("The dog barks: Woof!");
    }

    // Ковариантный возвращаемый тип (String — подтип Object)
    @Override
    public String getFood() {
        return "dog food";
    }
}

Что нельзя переопределить?

  • final методы.
  • static методы (это скрытие метода, а не переопределение).
  • private методы (они не видны в дочернем классе).

Зачем это нужно? Переопределение — основа полиморфизма времени выполнения (runtime polymorphism). Оно позволяет объектам дочерних классов отвечать на одни и те же вызовы методов по-разному, что делает код гибким и расширяемым.

Ответ 18+ 🔞

А, слушай, вот эта тема с переопределением методов — это ж классика, блядь, основа основ, но народ её так часто в говне пережёвывает, что аж тошно становится. Сейчас разжуем, как надо.

Представь, есть у тебя класс Animal, как бы общий предок, с методом makeSound(). Ну, животное и животное, издаёт какой-то звук. А теперь появляется его отпрыск, класс Dog. И этот пёс, блядь, не хочет мычать как попало, он хочет гавкать конкретно! Вот он и переопределяет родительский метод. То есть говорит: «Отстань, папаша, я сам знаю, как мне звучать!».

Но, сука, не всё так просто! Нельзя просто взять и переписать метод как вздумается. Есть правила, нарушишь — компилятор тебе ебальник набьёт.

Правила, блядь, железные:

  1. Сигнатура — святое. Имя метода, количество и тип параметров — должны совпадать один в один. Хочешь добавить лишний параметр? Иди нахуй, это будет уже другой метод, а не переопределение.
  2. Возвращаемый тип можно только уточнить. В старые добрые времена он тоже должен был совпадать. А щас, ёпта, можно вернуть не просто Object, а его конкретного сына, например String. Это называется ковариантный возвращаемый тип. Умное слово, но суть простая: можно быть точнее, но не более размытым.
  3. Модификатор доступа — только шире. Если у родителя метод protected, то в потомке можно сделать public. А вот наоборот — public сузить до protected — низя, блядь! Это же пиздец, все, кто раньше имел доступ, вдруг его лишатся. Полный пиздец и нарушение контракта.
  4. С исключениями — не наглей. Нельзя в переопределённом методе объявлять новые проверяемые исключения (checked exceptions), которых не было у родителя. Это тоже пиздец для того, кто код использует. А вот объявить меньше исключений или только непроверяемые (unchecked) — пожалуйста, на здоровье.

Смотри, как это выглядит в коде, тут всё честно:

class Animal {
    // Метод, который все будут переписывать
    public void makeSound() {
        System.out.println("The animal makes a sound");
    }

    protected String getFood() {
        return "some food";
    }
}

class Dog extends Animal {
    // ВОТ ОНО, ПЕРЕОПРЕДЕЛЕНИЕ!
    @Override // Эта аннотация — твой друг. Она кричит компилятору: «Чувак, проверь, я тут правильно переопределяю?». Если накосячил — получишь ошибку сразу, а не хуй пойми что в рантайме.
    public void makeSound() {
        System.out.println("The dog barks: Woof!");
    }

    // И тут переопределение, с ковариантным типом
    @Override
    public String getFood() {
        return "dog food";
    }
}

А теперь, блядь, что НИКОГДА не переопределяется:

  • final методы. Они final нахуй, точка. Приехали.
  • static методы. Это вообще не переопределение, а сокрытие метода. Совсем другая история, не путай, а то в рот меня чих-пых!
  • private методы. Они видны только внутри своего класса. Какой нахуй потомок? Он про них нихуя не знает.

Зачем весь этот цирк? А это и есть та самая магия полиморфизма времени выполнения, ёба! Ты создаёшь объект Dog, но ссылаешься на него как на Animal. И когда вызываешь animal.makeSound(), система смотрит: «Ага, объект-то на самом деле пёс!» — и запускает переопределённый метод из Dog. Не тот общий, а конкретно собачий. Это и есть гибкость, расширяемость и вся мощь ООП. Если, конечно, не ебашить в него с ноги, нарушая правила.