Зачем делать класс абстрактным, если можно унаследоваться от обычного класса
Что проверяют
Понимаете ли вы, что abstract это не формальность, а способ переложить проверку на компилятор. Следом почти всегда спрашивают про отличие от интерфейса и про Template Method.
Как отвечать
Наследоваться можно и от обычного класса, но абстрактный даёт гарантии, которых у обычного нет:
- нельзя создать экземпляр:
new Shape()не скомпилируется. Это правильно, если база описывает неполное понятие без смысла самого по себе; - обязательная реализация: абстрактный метод подкласс обязан переопределить, иначе ошибка компиляции. В обычной базе на его месте заглушка, пустая или с
throw new UnsupportedOperationException(), и забытое переопределение всплывёт в рантайме или не всплывёт вовсе; - явный контракт для наследников: по сигнатурам видно, что должен дописать автор подкласса, а что уже готово.
Главный сценарий: Template Method. База держит алгоритм в final-методе и вызывает из него абстрактные шаги. Так устроен AbstractList из JDK (достаточно реализовать get(int) и size()) и InputStream с абстрактным read(). Любопытный пример: HttpServlet объявлен абстрактным без единого абстрактного метода, чтобы нельзя было создать «пустой» сервлет.
Отличие от интерфейса: абстрактный класс хранит состояние в полях, имеет конструкторы и protected-члены, но родитель у класса один. Интерфейс с Java 8 умеет default-методы, с Java 9 приватные, полей экземпляра не имеет, а реализовать их можно сколько угодно. Обычный подход: контракт в интерфейсе, общая реализация в абстрактном классе рядом, как List и AbstractList. Закрыть круг наследников помогает sealed с permits (Java 17).
Где спотыкаются
Говорят, что у абстрактного класса нет конструктора. Есть, он вызывается из подкласса через super(...) и инициализирует общее состояние.
Считают, что абстрактный класс обязан содержать абстрактный метод. Не обязан, пример выше.
Делают базу обычным классом с методами, которые возвращают null «на всякий случай». Объект создаётся, но ломается при первом вызове.