Абстрактные классы и интерфейсы являются двумя основными строительными блоками большинства API Java. В этой статье будут рассмотрены наиболее яркие различия между интерфейсами и абстрактными классами.
1. Абстрактный класс
Проще говоря, абстрактный класс — это класс, который объявлен abstract с использованием ключевого слова abstract. Он может содержать или не содержать какой-либо абстрактный метод.
В следующем примере TestAbstractClass имеет два метода: первый метод является абстрактным, а второй метод — обычным методом.
public abstract class TestAbstractClass {public abstract void abstractMethod();public void normalMethod() { ... method body ... }}
Обратите внимание, что если у нас есть абстрактный метод в классе, мы должны объявить сам класс абстрактным. Абстрактный метод добавляет неполноту к классу. Таким образом, компилятор хочет объявить весь класс абстрактным.
JVM определяет абстрактный класс как неполный класс, который не определил свое полное поведение. Объявление абстрактного класса навязывает только одно: мы не можем создать экземпляр этого класса.
Так зачем вообще создавать класс, экземпляр которого вообще нельзя создать? Ответ в его использовании для решения некоторых критических проблем дизайна.
Единственный способ использовать абстрактный класс в приложении — расширить этот класс. Его подклассы, если они не объявлены снова абстрактными, могут быть инстанциированы.
class ChildClass extends TestAbstractClass {@Overridepublic void abstractMethod() {//method body}}
2. Интерфейс
Интерфейсы — еще один базовый строительный блок большинства API Java. Интерфейс определяет контракты, которые реализующие классы должны соблюдать. Эти контракты по сути являются нереализованными методами. В Java уже есть ключевое слово для нереализованных методов, то есть abstract. В Java класс может реализовать любой открытый интерфейс, поэтому все методы, объявленные в интерфейсе, должны быть только открытыми.
public interface TestInterface {void implementMe();}
В приведенном выше примере любой реализующий класс должен переопределить метод implementMe().
public class TestMain implements TestInterface {@Overridepublic void implementMe() {//...}}
3. Абстрактный класс, реализующий интерфейс
Есть только один сценарий, когда мы реализуем интерфейс и не переопределяем его метод, т.е. объявляем сам класс абстрактным. Поскольку AbstractClass является абстрактным и не может быть инициирован, то полнота класса не нарушается.
public abstract class AbstractClass implements TestInterface {//No need to override implementMe()}
Когда класс расширяет указанный выше AbstactClass, он должен переопределить метод implementMe(), чтобы сделать себя полноценным классом.
public class ChildClass extends AbstractClass {@Overridepublic void implementMe() {//...}}
4. Разница между абстрактным классом и интерфейсом
Давайте для краткого обзора отметим различия между абстрактными классами и интерфейсами:
- Интерфейсы имеют все методы изначально публичные и абстрактные. Мы не можем переопределить это поведение, уменьшив доступность методов. Мы даже не можем объявлять статические методы. Разрешены только публичные и абстрактные методы. С другой стороны, абстрактные классы гибки в объявлении методов. Мы также можем определять абстрактные методы с защищенной доступностью. Кроме того, мы также можем определять статические методы, при условии, что они не являются абстрактными. Разрешены неабстрактные статические методы.
- Интерфейсы не могут иметь полностью определенные методы, за исключением методов по умолчанию. По определению, интерфейсы должны предоставлять единственный контракт. Абстрактные классы могут иметь неабстрактные методы без каких-либо ограничений.
- Дочерний класс может расширять только один родительский класс, но может реализовывать любое количество интерфейсов. Это свойство часто называют имитацией множественного наследования в Java.
Интерфейсы абсолютно абстрактны и не могут быть созданы экземплярами; абстрактный класс Java также не может быть создан экземплярами, но может быть вызван, если существует метод main().
5. Когда использовать?
Всегда помните, что выбор между интерфейсом или абстрактным классом не является сценарием «или/или», где выбор любого без надлежащего анализа даст одинаковые результаты. Выбор должен быть сделан очень разумно после понимания проблемы под рукой. Давайте попробуем добавить сюда немного интеллекта.
5.1. Добавление частичного поведения с помощью абстрактных классов
Абстрактные классы позволяют определять некоторые поведения; это делает их прекрасными кандидатами внутри фреймворков приложений.
Давайте рассмотрим пример HttpServlet. Это основной класс, который вы должны унаследовать, если разрабатываете веб-приложение с использованием технологии Servlets. Как мы знаем, каждый сервлет имеет определенные фазы жизненного цикла, то есть инициализацию, обслуживание и уничтожение. Что, если для каждого создаваемого нами сервлета нам придется снова и снова писать один и тот же фрагмент кода относительно инициализации и уничтожения? Конечно, это будет большой головной болью.
Разработчики JDK решили эту проблему, сделав HttpServlet абстрактным классом. Он содержит весь базовый код, уже написанный для инициализации сервлета и его уничтожения. Вам нужно только переопределить определенные методы, где вы пишете код, связанный с обработкой вашего приложения, вот и все. Имеет смысл, правда!!
Можете ли вы добавить вышеуказанную функцию с помощью интерфейса? Нет, даже если вы можете, дизайн будет как ад для большинства невинных программистов.
5.2. Используйте интерфейсы только для контрактного поведения
Теперь давайте рассмотрим использование интерфейсов. Интерфейс только предоставляет контракты, а реализация каждого отдельного контракта является обязанностью классов.
Интерфейс лучше всего подходит для случаев, когда вы хотите определить только характеристики класса и заставить все реализующие сущности реализовать эти характеристики.
Я хотел бы привести пример интерфейса Map в фреймворке коллекций. Он предоставляет только правила, как карта должна вести себя на практике. Например, она должна хранить пару ключ-значение, значение должно быть доступно с помощью ключей и т. д. Эти правила представлены в виде абстрактных методов в интерфейсе.
Все реализующие классы(такие как HashMap, HashTable, TreeMap или WeakHashMap) реализуют все методы по-разному и, таким образом, демонстрируют функции, отличные от остальных.
Также интерфейсы могут использоваться для определения разделения обязанностей. Например, HashMap реализует 3 интерфейса: Map, Serializable и Cloneable. Каждый интерфейс определяет отдельные обязанности; таким образом, реализующий класс выбирает, что он хочет реализовать, и предоставит эту сильно ограниченную функциональность.
6. Методы Java 8 по умолчанию в интерфейсах
С Java 8 вы теперь можете определять методы в интерфейсах. Они называются методами по умолчанию. Методы по умолчанию позволяют вам добавлять новые функции в интерфейсы ваших библиотек и обеспечивать бинарную совместимость с кодом, написанным для старых версий этих интерфейсов.
Как следует из названия, методы по умолчанию в Java 8 — это просто методы по умолчанию. Если вы их не переопределяете, то это методы, которые вызовут вызывающие классы.
public interface Moveable {default void move(){System.out.println("I am moving");}}
В приведенном выше примере интерфейс Moveable определяет метод move() и предоставляет реализацию по умолчанию. Если какой-либо класс реализует этот интерфейс, то ему не нужно реализовывать свою собственную версию метода move(). Он может напрямую вызывать instance.move().
public class Animal implements Moveable {public static void main(String[] args){Animal tiger = new Animal();tiger.move(); //I am moving}}
И если класс добровольно захочет настроить поведение, то он может предоставить свою собственную реализацию и переопределить метод. Теперь будет вызываться его собственный пользовательский метод.
public class Animal implements Moveable {public void move(){System.out.println("I am running");}public static void main(String[] args){Animal tiger = new Animal();tiger.move(); //I am running}}
7. Разница между абстрактным классом и интерфейсом в Java 8
Начиная с Java 8, мы теперь можем предоставить частичную реализацию с интерфейсами, используя методы по умолчанию, как и абстрактные классы. Так что по сути грань между интерфейсами и абстрактными классами стала очень тонкой. Теперь они предоставляют почти те же возможности.
Теперь, остается одно большое различие, что мы не можем расширять несколько классов, тогда как мы можем реализовать несколько интерфейсов. Помимо этого различия, вы можете достичь любой возможной функциональности из интерфейсов, которые абстрактные классы могут сделать возможными, и наоборот тоже верно.
Надеюсь, вы нашли достаточно информации об интерфейсах и абстрактных классах в Java.