Итак, абстрактный класс — это класс, который может иметь абстрактные методы, не может быть создан (инстанциирован), но можем иметь конструктор и состояние. Абстрактные методы — это методы, имеющие сигнатуру (название метода, принимаемые параметры и возвращаемый тип), но не имеющие реализации (тела метода). Абстрактный класс также может иметь неабстрактные методы, которые имеют реализацию (тело метода). Все конкретные классы, наследующиеся от абстрактного класса, должны определять реализацию (тело метода) для всех абстрактных методов. Состояние класса можно определить как переменные экземпляра и неабстрактные методы, которые могут получать доступ к этим переменным и изменять их.
Интерфейс, в свою очередь, — это набор абстрактных методов, которые должны быть реализованы классом. Один класс может реализовать множество интерфейсов, но наследоваться только от одного класса. Это дает возможность использовать интерфейсы для, своего рода, реализации множественного наследования, которое не поддерживается Java-классами. Когда класс реализует интерфейс, он должен предоставить реализацию для всех методов, объявленных в интерфейсе — аналогично абстрактным методам в абстрактном классе.
Главное различие между абстрактным классом и интерфейсом заключается в том, что абстрактный класс может иметь состояние, тогда как интерфейс нет. Отсюда вытекает тот факт, что абстрактный класс может иметь конструктор, тогда как интерфейс нет. Когда создается подкласс, конструктор его супер-класса (включая любой абстрактный класс) вызывается автоматически. Интерфейс не может иметь конструктор, потому что он не может быть создан.
Здесь сразу возникает очевидный вопрос — почему мы не можем создать (инстанциировать) абстрактный класс, если у него есть конструктор? Несмотря на наличие конструктора, абстрактный класс не может быть создан (инстанциирован) напрямую потому что он не является полноценным классом из-за отсутствия деталей имплементации — абстрактных методов. Если бы мы давали возможность напрямую создавать абстрактные классы, то у нас бы возникали исключительные ситуации при попытке вызвать его абстрактные методы из-за отсутствия у них реализации (тела метода), поэтому такое решение просто не имеет смысла.
Когда мы создаем объект класса наследника, конструктор абстрактного класса вызывается неявно для инициализации полей абстрактного класса (его состояния). Следовательно, можно считать, что конструктор абстрактного класса вызывается косвенно, а не напрямую. Это же объясняет и отсутствия конструктора у интерфейса в Java — так как у интерфейса нет состояния в виде переменных экземпляра и методов имеющих к ним доступ, то нет необходимости и в конструкторе, который их инициализирует.
Неплохо. Хабр торт
Спасибо!
А в чем смысл использования sealed классов, если при наследовании подкласс можно объявить non-sealed и дальше от него уже свободно наследоваться? Смысл теряется получается
Хороший вопрос! С одной стороны да, если задуматься по логике, то в чем смысл sealed класса или интерфейса если все можно легко обойти? Но если посмотреть в стандарт JEP 409, то можно заметить, что создатели языка не преследовали цели предоставить новый модификатор доступа:
1. «It is not a goal to provide new forms of access control such as "friends"»
2. «It is not a goal to change final in any way.»
Sealed класс/интерфейс дает более гибкий контроль над наследованием. Он может быть наследован только ограниченным заранее определенным набором других классов. Это позволяет нам контролировать наследование в иерархии классов, но при этом давая возможность подклассам самим решать вопрос дальнейших ограничений. Главная цель здесь — это предотвратить непреднамеренное или некорректное наследование, что делает более ясной структуру нашей программы. Это также может помочь компилятору Java делать некоторые оптимизации, поскольку он может быть уверен в иерархии классов.
Если нам нужно гарантировать, что поведение класса не будет изменено через наследование, то нужно использовать модификатор final для класса, что полностью заблокирует дальнейшее наследование. В этой статье, классы Circle и Rectangle, наследующие от sealed класса Shape, как раз и объявлены финальными.
А зачем вообще объявлять наследников non-sealed, а не final?
Спасибо за полезную статью!
Хотелось бы более подробно узнать про сценарии использования
Может есть какие-то четкие примеры, когда нужно использовать абстрактные классы, а когда - только интерфейсы?
Постараюсь ответить кратко, потому что про сценарии использования можно написать целую отдельную статью :)
В этой статье мы говорили про пример с определением контракта для класса и что с этим лучше всего справляется интерфейс. Это один из однозначных сценариев когда нужно использовать именно интерфейс. Другими словами это еще могут называть API класса. Казалось бы абстрактный класс тоже отлично справиться с заданием контракта и это действительно так, но из-за того что Java не поддерживает множественное наследование, мы всегда будем ограничены только одним базовым классом. А так как в реальной практике нам очень часто нужно использовать несколько разных контрактов, то абстрактные классы будут неподходящим инструментом для этого случая и лучшей практикой считается использование интерфейсов.
Другой пример, когда однозначно нужно использовать интерфейс — это реализация так называемых признаков и маркеров. Например, если вам нужно сравнивать объекты, вы можете реализовать интерфейс "Comparable" чтобы быть уверенным, что класс поддерживает сравнение. В свою очередь интерфейс "Cloneable", который является маркером, будет просто означать что ваш тип поддерживает клонирование.