Переопределение методов в Java - Вопросы

Всего: 6 вопросов

1. 

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

Переопределение методов (method overriding) — это объявление в подклассе метода с тем же именем и тем же списком параметров, что и у метода суперкласса, но с собственной реализацией.

Какая именно реализация выполнится, решает JVM во время выполнения по типу объекта, а не по типу переменной. Этот механизм называется динамической диспетчеризацией методов (позднее связывание, late binding), и именно так в Java реализован динамический полиморфизм.

public class Toy {
    public void printName() {
        System.out.println("Toy");
    }
}

public class Doll extends Toy {
    @Override
    public void printName() {
        System.out.println("Doll");
    }
}

Toy doll = new Doll();
doll.printName();   // выведет "Doll"

Тип переменной Toy определяет только то, какие методы вообще можно вызвать, а фактический объект Dollкакая реализация выполнится.

2. 

Какие правила должен соблюдать переопределяющий метод в Java?

Компилятор признаёт метод переопределяющим, только если выполнены все условия:

1. Имя и список параметров. Должны полностью совпадать с методом суперкласса: те же типы, тот же порядок, то же количество. Если список параметров отличается — это перегрузка (overload), а не переопределение.

2. Тип возвращаемого значения. Тот же или ковариантный (подтип исходного) — разрешено начиная с Java 5. Расширить тип возврата нельзя, будет ошибка компиляции.

3. Модификатор доступа. Можно расширить (protectedpublic), сузить нельзя — компилятор выдаст ошибку attempting to assign weaker access privileges.

4. Проверяемые исключения. Можно убрать или сузить, добавить новые или более широкие нельзя. На непроверяемые исключения (RuntimeException, Error) ограничений нет.

5. Наследование. Переопределять можно только унаследованные методы экземпляра, поэтому static, final, private методы и конструкторы исключены. Абстрактный метод обязан быть реализован, если подкласс сам не объявлен abstract.

3. 

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

Перегрузка — это несколько методов с одним именем и разными списками параметров; нужный вариант выбирает компилятор по объявленным типам аргументов. Переопределение — это метод подкласса с тем же именем и тем же списком параметров, что у метода суперкласса; нужную реализацию выбирает JVM по фактическому типу объекта.

Главный критерий: перегрузку разрешает компилятор, переопределение — JVM во время выполнения.

Переопределение: методы в разных классах, связанных наследованием; список параметров одинаковый; тип возврата тот же или ковариантный; доступ можно только расширить; проверяемые исключения расширять нельзя; аннотация @Override применима; это динамический полиморфизм.

Перегрузка: методы обычно в одном классе; список параметров обязательно разный; тип возврата любой (но сам по себе перегрузку не создаёт); модификатор доступа и исключения любые; @Override неприменима; это статический полиморфизм. Методы static, final, private и конструкторы переопределить нельзя, но перегрузить можно все.

4. 

Можно ли переопределить static, final, private методы и конструкторы?

Нет, ни один из них переопределить нельзя, хотя перегрузить можно все.

static. Наследник может объявить статический метод с такой же сигнатурой, но это не переопределение, а сокрытие метода (method hiding): версия выбирается на этапе компиляции по типу ссылки.

public class Base {
    public static void go() {
        System.out.println("метод из Base");
    }
}

public class Sub extends Base {
    public static void go() {
        System.out.println("метод из Sub");
    }
}

Base ob = new Sub();
ob.go();   // выведет "метод из Base" - компилятор смотрит на тип переменной

final. Метод, помеченный final, переопределить нельзя вообще — ошибка компиляции cannot override final method. Класс final нельзя даже унаследовать.

private. Приватный метод не наследуется, поэтому одноимённый метод в наследнике — самостоятельный метод; аннотация @Override на нём не компилируется.

Конструкторы. Не наследуются, поэтому не переопределяются; одноимённые конструкторы с разными параметрами — это перегрузка.

5. 

Как вызвать реализацию суперкласса из переопределённого метода?

Через ключевое слово super: super.имяМетода(). Это единственный способ добраться до версии суперкласса, потому что обычный вызов по имени внутри подкласса приведёт к рекурсивному вызову переопределяющего метода.

public class N extends M {
    @Override
    public void print() {
        super.print();   // сначала выполнится вариант из класса M
        System.out.println("Метод N k = " + k);
    }
}

Такой вызов допустим только внутри самого класса-наследника и поднимается ровно на один уровень иерархии — конструкции super.super.print() в Java не существует. Если нужна логика прародителя, её выносят в отдельный метод.

6. 

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

Аннотация необязательна — без неё переопределение работает точно так же. Она добавляет проверку компилятора: помеченный метод обязан действительно переопределять метод суперкласса или интерфейса, иначе код не скомпилируется.

Это превращает молчаливый баг в ошибку сборки. Классический случай — equals: если написать equals(User obj) вместо equals(Object obj), получится перегрузка, коллекции продолжат использовать исходный equals из Object, и баг проявится далеко от места объявления.

@Override
public boolean equals(Object obj) {   // с @Override ошибку в сигнатуре видно сразу
    if (this == obj) return true;
    if (!(obj instanceof User)) return false;
    return Objects.equals(name, ((User) obj).name);
}

На перегруженный метод @Override поставить нельзя — это самый надёжный способ убедиться, что вы написали именно переопределение. Начиная с Java 6 аннотацию можно ставить и на методы, реализующие интерфейс; в Java 5 это считалось ошибкой компиляции.

Страница 1 из 1