Что такое абстрактный класс
Интерфейс объявляет поведение без состояния. Обычный класс полностью реализован, и его можно инстанцировать. Абстрактный класс находится между ними: он может содержать поля, конструкторы и завершённые методы, как обычный класс, но также может оставить некоторые методы нереализованными и запрещает создавать свой экземпляр напрямую. Вы помечаете его ключевым словом abstract.
Идея в том, чтобы собрать в одном месте всё общее для подклассов, при этом заставляя каждый подкласс реализовать те части, которые действительно различаются.
Animal определяет getName() один раз для всех подклассов и объявляет sound() как abstract — метод с сигнатурой, но без тела, который должен предоставить Dog.
Нельзя создать экземпляр абстрактного класса
Поскольку у абстрактного класса могут быть незавершённые методы, его прямое создание оставило бы вас с неполным объектом. Компилятор это отвергает:
Animal a = new Animal("???"); // error: Animal is abstract; cannot be instantiated
Вы всегда создаёте экземпляр конкретного подкласса — того, который реализовал каждый абстрактный метод. Этот экземпляр подкласса затем можно хранить в переменной абстрактного типа — именно так и используется абстракция.
Абстрактные методы заставляют подклассы принимать решение
abstract-метод — это обещание, которое подкласс обязан выполнить. Если подкласс забывает реализовать один из них, сам подкласс становится абстрактным, и компилятор сообщает вам об этом. Это главный рычаг абстрактного класса: он гарантирует, что определённое поведение существует, не диктуя, что именно оно делает.
describe() написан в Shape один раз, но при этом вызывает собственный area() каждого подкласса. Абстрактный класс предоставляет общий каркас; подклассы предоставляют конкретику.
Общее состояние и конструкторы
В отличие от традиционного интерфейса, абстрактный класс может содержать поля экземпляра и определять конструкторы. Конструктор сам по себе никогда не создаёт Animal или Shape — он выполняется через super(...) при создании подкласса, инициализируя общее состояние.
Поле balance, методы deposit и applyInterest находятся в одном месте. Абстрактной остаётся только та политика, которая действительно меняется, — interestRate(). Конструктор подкласса обязан вызвать super(...), чтобы инициализировать это унаследованное состояние.
Подводный камень: смешивание abstract и final
abstract и final — противоположности. abstract-метод требует переопределения; final-метод его запрещает. Пометить одно и то же обоими способами — или сделать абстрактный класс final — это ошибка компиляции. Помните также, что у абстрактного класса может быть ноль абстрактных методов: объявить класс abstract только для того, чтобы запретить создание его экземпляров, вполне допустимо и иногда полезно для базовых типов, которые вы хотите только наследовать.
abstract final class Bad { } // error: abstract and final conflict
abstract class Base {
abstract final void f(); // error: an abstract method can't be final
}
Абстрактный класс против интерфейса
Они пересекаются, поэтому выбор сводится к тому, что именно вам нужно разделять:
- Абстрактный класс — используйте его для тесно связанных классов, которые разделяют состояние и код. И
Savings, иCheckingнаследуютAccount, получая полеbalanceи логикуdeposit. Класс наследует только один. - Интерфейс — используйте его для возможности, которую могут разделять несвязанные классы. И
Bird, иAirplaneмогут бытьFlyable, не разделяя никакой реализации. Класс может реализовать множество интерфейсов.
Распространённый приём сочетает их: интерфейс задаёт контракт, а абстрактный класс реализует шаблонный код, так что конкретным подклассам остаётся заполнить лишь то, что уникально.
Далее: Полиморфизм
Обратите внимание, что в каждом примере выше мы хранили экземпляр подкласса в переменной абстрактного типа, затем вызывали метод и автоматически получали поведение подкласса. Именно эта способность — один ссылочный тип, множество вариантов поведения во время выполнения — и есть полиморфизм, и именно благодаря ему абстрактные классы и интерфейсы оправдывают себя. Об этом и пойдёт речь на следующей странице.
Часто задаваемые вопросы
Что такое абстрактный класс в Java?
Абстрактный класс — это класс, объявленный с ключевым словом abstract, экземпляр которого нельзя создать напрямую. Он предназначен для наследования. Он может сочетать полностью реализованные методы и поля (общее состояние и код для подклассов) с abstract-методами без тела — их реализация становится обязанностью каждого подкласса.
Можно ли создать экземпляр абстрактного класса в Java?
Нет. new AbstractType() вызывает ошибку компиляции, потому что у абстрактного класса могут быть нереализованные (abstract) методы, и объект получился бы неполным. Вы создаёте экземпляр конкретного подкласса, который реализует все абстрактные методы, а затем сохраняете его в переменной абстрактного типа.
В чём разница между абстрактным классом и интерфейсом в Java?
У абстрактного класса могут быть поля экземпляра, конструкторы и частично реализованная логика, но класс может наследовать только один абстрактный класс. Интерфейс объявляет поведение без состояния экземпляра, и класс может реализовать множество интерфейсов. Используйте абстрактный класс, чтобы разделять состояние и код между тесно связанными подклассами; используйте интерфейс, чтобы дать несвязанным классам общую возможность.