Переопределение методов
Определение
Переопределение (overriding) — новая реализация унаследованного instance-метода с той же сигнатурой. Вызов выбирается по реальному классу объекта:
class Formatter {
String format(Object value) {
return value.toString();
}
}
class JsonFormatter extends Formatter {
@Override
String format(Object value) {
return "{\"value\":\"" + value + "\"}";
}
}
Formatter formatter = new JsonFormatter();
formatter.format("ok"); // реализация JsonFormatterАннотация @Override заставляет компилятор проверить намерение и защищает от случайной перегрузки.
Правила
Переопределяющий метод:
- имеет ту же сигнатуру после учёта правил generics/стирания;
- возвращает тот же тип либо его подтип для ссылочного результата;
- не сужает доступ;
- не объявляет более широкие checked-исключения;
- может убрать checked-исключение или заменить его подтипом;
- не может переопределить
final-метод.
private-метод не виден наследнику и не переопределяется. static-метод скрывается, а не переопределяется. Поля тоже скрываются и разрешаются по статическому типу.
Ковариантный результат
class Document {
Document copy() {
return new Document();
}
}
class Report extends Document {
@Override
Report copy() {
return new Report();
}
}Возврат Report допустим, потому что это подтип Document. Для примитивного результата ковариантности нет.
Вызов базовой реализации
super.method() позволяет дополнить поведение родителя:
@Override
void validate(Input input) {
super.validate(input);
validateFormat(input);
}Это создаёт зависимость от контракта базовой реализации. Если порядок критичен, Template Method с final-алгоритмом и отдельными hooks часто яснее.
Частая ошибка с equals
class User {
boolean equals(User other) { // перегрузка, не override
return false;
}
}Контракт Object требует equals(Object). Без @Override ошибка может остаться незамеченной:
@Override
public boolean equals(Object other) {
// проверка типа и полей
}Полиморфизм и конструктор
Динамический вызов работает даже из конструктора базового класса. В этот момент поля наследника ещё могут иметь значения по умолчанию, поэтому вызов переопределяемых методов из конструктора опасен.
Ловушки
- Изменить тип параметра и получить перегрузку вместо override.
- Скрыть static-метод и ожидать динамический dispatch.
- Сузить модификатор доступа.
- Расширить список checked-исключений.
- Переопределить
equalsс параметром конкретного класса. - Нарушить поведенческий контракт родителя при формально корректной сигнатуре.
Вопросы на собеседовании
- По какому типу выбирается переопределённый instance-метод?
- Можно ли изменить возвращаемый тип?
- Что происходит со static-методами и полями?
- Можно ли добавить более общее checked-исключение?
- Почему
equals(MyClass)не переопределяетObject.equals? - Зачем всегда ставить
@Override?
Связанные заметки
Наследование · Полиморфизм · Перегрузка Java · Методы Java · Equals и hashCode · Абстрактные классы