Полиморфизм

Subtype polymorphism позволяет обращаться к разным реализациям через общий тип. Переопределённый экземплярный метод выбирается по runtime-типу объекта.

interface Formatter {
    String format(long value);
}
 
final class DecimalFormatter implements Formatter {
    public String format(long value) {
        return Long.toString(value);
    }
}
 
final class HexFormatter implements Formatter {
    public String format(long value) {
        return Long.toHexString(value);
    }
}
 
Formatter formatter = new HexFormatter();
System.out.println(formatter.format(42)); // 2a

Клиент знает контракт Formatter, а не проверяет реализацию через instanceof.

Что выбирается когда

КонструкцияВыбор
override instance-методаruntime по объекту
overloadcompile time по объявленным типам аргументов
static-методcompile time по типу ссылки/имени класса
полеcompile time; поля скрываются
private/final методпереопределения нет
void print(Object value) { System.out.println("object"); }
void print(String value) { System.out.println("string"); }
 
Object value = "text";
print(value); // object: overload выбран при компиляции

Подтип должен сохранять обещания базового типа: не усиливать предусловия и не разрушать ожидаемые свойства. Иначе динамическая подстановка формально компилируется, но ломает клиента.

Ошибки

  • путать overload с override;
  • делать type switch по всем реализациям вместо виртуального вызова;
  • менять смысл метода в наследнике;
  • ожидать полиморфизм полей или static-методов.

На собеседовании

По чему выбирается override? По фактическому классу объекта.

По чему выбирается overload? По compile-time типам и доступным сигнатурам.

Можно ли переопределить static-метод? Нет, его можно только скрыть.

Зачем контракту Liskov? Чтобы объект подтипа безопасно заменял базовый тип.

Связано: Переопределение Java, Перегрузка Java, Принципы SOLID.