Что такое абстрактные классы и методы в Java
Абстрагирование – это процесс, в ходе которого от пользователя скрываются многие детали реализации, а предоставляются только те детали, которые действительно важны. Так удаётся сфокусироваться на том, что делает объект, а не как он это делает.
В Java абстрактные классы и методы – это основные инструменты для реализации абстракций. Абстрактные классы служат шаблонами для создания субклассов, а абстрактные методы можно сравнить с чертежами, описывающими поведение этих субклассов.
Если вы новичок в Java или хотите освежить знания о том, чем отличаются абстрактные классы или интерфейсы, то можете почитать руководство и на эту тему: Difference Between Interface and Abstract class in Java.
Абстрактные классы
Определение синтаксиса абстрактных классов в Java
Абстрактным называется такой класс, экземпляр которого нельзя создать сам по себе – он служит основой для других классов. При объявлении такого класса в его определении ставится ключевое слово abstract .
public abstract class Shape < // class body >
Обратите внимание: абстрактные классы могут содержать как абстрактные, так и неабстрактные методы, а также переменные экземпляров, конструкторы и другие члены. Правда, напрямую инстанцировать абстрактные классы нельзя, а это означает, что их не получится напрямую использовать для создания объектов.
Различия между абстрактными и конкретными классами
Для абстрактных классов (в отличие от конкретных) нельзя напрямую создавать экземпляры. Напротив, абстрактные классы непосредственно предназначены для того, чтобы их расширяли другие классы, которые затем могут реализовывать абстрактные методы и наследовать неабстрактные члены и поведение абстрактного класса.
Кроме того, у абстрактных классов могут быть конструкторы, но их нельзя вызывать прямо из субклассов. Напротив, конструктор конкретного субкласса обязан явно вызывать конструктор соответствующего суперкласса. Для этого вызывается либо super() , либо конкретный конструктор суперкласса.
Когда использовать абстрактные классы: пример
Абстрактные классы часто применяются для определения общего интерфейса или такого поведения, которое может совместно использоваться сразу множеством субклассов. Например, представьте, что вы проектируете игру, в которой используются различные фигуры – скажем, круги, квадраты и треугольники. У всех этих фигур есть определённые общие свойства, например, периметр и площадь. Но у них могут быть и уникальные характеристики, такие как количество сторон и радиус.
Чтобы представить эти фигуры в вашей игре, можно создать абстрактный класс Shape, определяющий общее поведение для всех фигур:
public abstract class Shape < protected int x, y; public Shape(int x, int y) < this.x = x; this.y = y; >public abstract double getArea(); public abstract double getPerimeter(); >
В этом примере класс Shape содержит переменные экземпляра для координат x и y некоторой фигуры, а также абстрактные методы для вычисления площади и периметра. Определяя эти методы как абстрактные, мы позволяем каждому конкретному субклассу реализовать их по-своему.
Например, можно было бы создать субкласс Circle , расширяющий класс Shape и реализующий собственные версии методов getArea() и getPerimeter() :
public class Circle extends Shape < private double radius; public Circle(int x, int y, double radius) < super(x, y); this.radius = radius; >public double getArea() < return Math.PI * radius * radius; >public double getPerimeter() < return 2 * Math.PI * radius; >>
Аналогично, можно было бы создать субклассы Square и Triangle , также реализующие собственные версии методов getArea() и getPerimeter() , в то же время наследующие координаты x и y от суперкласса Shape .
Используя абстрактные классы таким образом, мы создаём гибкую и расширяемую систему, позволяющую представлять в нашей игре различные фигуры, в то же время выдерживая общий интерфейс для всех субклассов, обеспечивающий общность их поведения.
Абстрактные методы
Определение и синтаксис абстрактных методов в Java
Абстрактным называется такой метод, который объявляется, но не предоставляет реализации. Он объявляется при помощи ключевого слова abstract, присутствующего в сигнатуре этого метода, и это ключевое слово обязательно должно быть включено в абстрактный класс.
public abstract double getArea();
Обратите внимание: в абстрактных методах нет тела, но они могут содержать возвращаемый тип, параметры и модификаторы. Ответственность за реализацию абстрактного метода ложится на конкретные субклассы, расширяющие абстрактный класс.
Правила и ограничения при работе с абстрактными методами
Существует несколько правил и ограничений, применимых к абстрактным методам в Java:
- Абстрактный метод не может быть объявлен как final или private.
- Абстрактный метод обязательно должен объявляться внутри абстрактного класса.
- Конкретный субкласс, расширяющий абстрактный класс, обязательно должен предоставлять реализацию для всех его унаследованных абстрактных методов.
Мы уже определили абстрактный класс Shape , содержащий абстрактные методы для вычисления площади и периметра фигуры:
public abstract class Shape < protected int x, y; public Shape(int x, int y) < this.x = x; this.y = y; >public abstract double getArea(); public abstract double getPerimeter(); >
Чтобы создать конкретный субкласс Shape , необходимо реализовать следующие абстрактные методы:
public class Circle extends Shape < private double radius; public Circle(int x, int y, double radius) < super(x, y); this.radius = radius; >public double getArea() < return Math.PI * radius * radius; >public double getPerimeter() < return 2 * Math.PI * radius; >>
В данном примере класс Circle реализует методы getArea() и getPerimeter() при помощи собственных уникальных формул для расчёта площади круга и периметра окружности.
Аналогично, можно было бы создать субклассы Square и Triangle , реализующие собственные версии методов getArea() и getPerimeter().
Используя абстрактные методы в сочетании с абстрактными классами, можно предоставить общий интерфейс для вычисления площади и периметра различных фигур, в то же время позволяя каждому конкретному субклассу по-своему реализовывать эти методы.
Наилучшие практики
Работая с абстрактными классами и методами в Java, нужно держать в уме некоторые наилучшие практики. Вот некоторые рекомендации, которые помогут вам эффективно пользоваться этими мощными инструментами:
- Пользуйтесь абстрактными классами, чтобы моделировать иерархии взаимосвязанных классов: при помощи абстрактных классов очень удобно представить группу таких взаимосвязанных классов, которые совместно используют некий общий функционал. Создавая абстрактный класс, который определяет общие методы и свойства, вы тем самым избегаете дублирования, повышаете модульность вашего кода и облегчаете его поддержку.
- Пользуйтесь абстрактными методами, чтобы определять общее поведение: абстрактные методы полезны в тех случаях, когда требуется навязать конкретное поведение сразу для множества субклассов. Определяя абстрактный метод в абстрактном классе, вы можете гарантировать, что все субклассы (каждый по-своему) будут реализовывать одно и то же поведение.
- Не переборщите с абстрактными классами и методами: притом, что абстрактные классы и методы могут быть довольно мощными, важно не прибегать к ним сверх меры. Вообще, следует создавать абстрактный класс или метод только в том случае, когда на то есть явная причина. Если слишком активно ими пользоваться, то код может излишне усложниться, и его станет тяжелее поддерживать.
- Придерживайтесь соглашений об именованиях: подбирая имена для абстрактных классов и методов, важно придерживаться стандартных соглашений об именованиях, действующих в Java. Именем абстрактного класса должно быть абстрактное существительное (напр., “Shape”), а имена абстрактных методов должны быть глаголами (напр. “draw”).
- Документируйте ваш код: как и при работе с любым кодом, абстрактные классы и методы важно документировать, чтобы они были понятнее другим разработчикам. Снабжайте код чёткими и краткими комментариями, которые поясняли бы смысл поведения каждого класса и метода, а также их назначение.
Заключение
Итак, абстрактные классы и методы – это мощные инструменты для реализации абстракций в Java. Умело пользуясь ими, вы сможете создавать более модульный и удобный в поддержке код, который проще понимать и расширять. Если вы моделируете иерархии взаимосвязанных классов или определяете общее поведение, которое должно реализовываться сразу во множестве субклассов, то вам в этом могут особенно помочь абстрактные классы и методы. Пользуясь рекомендованными практиками, рассмотренными выше, вы сможете освоить этот важный аспект программирования на Java.
Часто задаваемые вопросы
• Могут ли у абстрактных классов быть конструкторы?
Да, у абстрактных классов могут быть конструкторы. Правда, их нельзя напрямую инстанцировать, поэтому их конструкторы обычно вызываются конструкторами их субклассов.
• Могут ли абстрактные классы реализовывать интерфейсы?
Да, абстрактные классы могут реализовывать интерфейсы, это могут делать вообще любые классы. Эта возможность может пригодиться, если вы хотите предоставить некое общее поведение сразу для нескольких классов, так, чтобы они реализовывали один и тот же интерфейс.
• Могут ли абстрактные методы быть статическими?
Нет, абстрактные методы не могут быть статическими, так как для их работы требуется вызвать экземпляр класса. Правда, статические методы могут вызывать абстрактные методы, если они реализованы в конкретных субклассах.
• Могут ли абстрактные классы быть финальными?
Нет, абстрактные классы не могут быть финальными, так как они предназначены именно для расширения субклассами. Если сделать абстрактный класс финальным, то такое расширение будет исключено.
• Когда следует использовать абстрактные классы вместо интерфейсов?
Абстрактные классы хорошо вам подойдут, если вы хотите предоставить некоторый общий функционал для множества классов, входящих в общую иерархию. Интерфейсы лучше подходят для определения такого набора, методы из которого разные классы могут реализовывать независимо. Правило такое: пользуйтесь абстрактными классами, когда хотите предоставить базовую реализацию для группы взаимосвязанных классов. Интерфейсы уместнее, когда нужно определить группу методов для реализации разрозненными классами, в каждом по-своему.
- Блог компании Издательский дом «Питер»
- Java
- Совершенный код
- ООП
Что такое абстрактные классы в Java
Изучите абстрактные классы в Java: их определение, особенности, синтаксис и примеры использования для создания мощных приложений.
Алексей Кодов
Автор статьи
9 июня 2023 в 16:31
Абстрактные классы являются ключевым понятием в Java, позволяющим создавать общую структуру для ряда связанных классов. Они представляют собой основу для наследования и полиморфизма в объектно-ориентированном программировании. В этой статье мы разберемся, что такое абстрактные классы, их особенности и синтаксис.
Определение абстрактного класса
Абстрактный класс — это класс, который может содержать как обычные методы, так и абстрактные методы. Абстрактные методы — это методы, которые объявлены, но не имеют реализации в абстрактном классе. Они должны быть реализованы в классах-наследниках. Создавать объекты абстрактного класса нельзя, так как он предназначен только для наследования.
Чтобы объявить абстрактный класс, используйте ключевое слово abstract перед class .
abstract class Shape < // Объявление абстрактного метода abstract void draw(); >
Java-разработчик: новая работа через 11 месяцев
Получится, даже если у вас нет опыта в IT
Особенности абстрактных классов
- Абстрактные классы не могут быть использованы для создания объектов. Они служат только в качестве базовых классов для других классов.
- Абстрактные классы могут содержать как абстрактные, так и обычные методы.
- Класс, который наследует абстрактный класс, должен реализовать все абстрактные методы базового класса, иначе он тоже должен быть объявлен как абстрактный.
Пример использования абстрактного класса
abstract class Animal < abstract void makeSound(); void eat() < System.out.println("I can eat"); >> class Dog extends Animal < void makeSound() < System.out.println("Woof"); >> class Main < public static void main(String[] args) < Dog myDog = new Dog(); myDog.makeSound(); myDog.eat(); >>
В этом примере мы создали абстрактный класс Animal с абстрактным методом makeSound() и обычным методом eat() . Класс Dog наследует абстрактный класс Animal и реализует абстрактный метод makeSound() . В методе main мы создаем объект класса Dog и вызываем методы makeSound() и eat() .
Теперь вы знаете, что такое абстрактные классы в Java, их особенности и как их использовать. Продолжайте изучать Java, и вы станете отличным разработчиком!
Что такое абстрактные классы в Java и чем они отличаются от интерфейсов
Абстрактные классы — почти как интерфейсы, но дают дополнительные возможности.

Анастасия Хамидулина
Автор статьи
5 мая 2023 в 11:06
В объектно-ориентированном программировании есть четыре основополагающих принципа: композиция, полиморфизм, наследование и делегация. Их нужно эффективно реализовывать в Java. Для этого есть специальные инструменты — абстрактные классы, и любому программисту на Java нужно понимать, что это такое и как грамотно использовать эти инструменты.
Абстрактные методы и классы
Абстрактный класс объявляется при описании класса: добавляют оператора abstract перед оператором class. Абстрактные классы Java нельзя инициализировать как объект, но от них можно наследоваться.
Пример объявления абстрактного класса:
public abstract class GraphicObject < // объявление полей // объявление не абстрактных методов abstract void draw(); >
Следующий код выдаст ошибку компиляции:
GraphicObject object = new GraphicObject();
Пример класса, наследующего от абстрактного класса:
public class Circle extends GraphicObject < void draw() < System.out.println(“Нарисовали круг.”); >>
Абстрактный метод Java — это метод, который объявлен, но в нём не описана логика, то есть метод не реализован. В таком случае нет фигурных скобок, а после объявления метода сразу идет точка с запятой.
abstract void moveTo(double deltaX, double deltaY);
Абстрактный класс необязательно должен содержать абстрактные методы. Но если какие-то методы класса объявлены абстрактными, то и он должен быть таким.
Следующий код выдаст ошибку компиляции:
public class GraphicObject
Если класс наследуется от абстрактного класса, в нём нужно описать реализацию всех абстрактных методов родительского класса. Если же какие-то из абстрактных методов родителя не реализованы, класс-наследник тоже должен быть абстрактным.
Java-разработчик: новая работа через 11 месяцев
Получится, даже если у вас нет опыта в IT

Методы интерфейсов, которые не объявлены статическими (оператор static) или методами по умолчанию (оператор default), тоже абстрактные. Но задается это неявно: чтобы уменьшить количество кода, обычно не пишут оператор abstract у методов интерфейса. Хотя добавить его можно — это не вызовет ошибки компиляции.
Разобраться во всех тонкостях языка программирования Java можно на курсе Skypro «Java-разработчик». За несколько месяцев изучите типы данных и переменные, разберетесь в циклах и массивах. В программе — необходимый минимум теории и много практики. Уже в процессе обучения начнете писать код, тестировать его и исправлять ошибки. А результаты практических заданий сможете положить в портфолио, чтобы быстрее найти работу по новой специальности.
Абстрактные классы и интерфейсы: сравнение
https://sky.pro/media/redaktory-dlya-javascript/
Абстрактные классы очень похожи на интерфейсы. Интерфейсы, как и абстрактные классы, нельзя инициализировать в объект, и они могут содержать описание методов: с реализацией и без нее.
Но есть важные отличия. В интерфейсах все поля по умолчанию публичные, статические и неизменяемые, а методы могут быть только публичными. В абстрактном классе можно объявить нестатическое поле класса изменяемым и непубличным. А методы абстрактного класса могут иметь все допустимые уровни доступа:
- публичный (public);
- приватный (private);
- защищенный (protected);
- на уровне пакета (package).
Абстрактный класс может иметь внутреннее состояние с одинаковым кодом управления для всех его потомков. А правила доступа к логике абстрактного класса можно спроектировать более гибко.
Еще одно важное отличие абстрактного класса от интерфейса: в Java можно наследовать только один абстрактный класс, а интерфейсов реализовать множество.
Когда использовать абстрактный класс, а когда — интерфейс
Используйте абстрактный класс, если к вашей задаче применимо одно или несколько условий:
Необходимо распределить некоторый общий код между несколькими тесно связанными классами.
У классов-наследников много общих полей и методов с уровнями доступа, отличными от публичного.
У объекта класса какое-то внутреннее состояние. Это потребует объявления нестатических, изменяемых полей и методов доступа.
Используйте интерфейс, если к вашей задаче применимы утверждения:
Нужно объявить только контракт работы с каким-то типом данных без каких-либо указаний на его внутреннее состояние.
Объявленный контракт будет использоваться не только в связанных классах, но и в типах, которые напрямую не связаны с ним. Или даже в коде за пределами текущего приложения или библиотеки. Например, интерфейсы из стандартной библиотеки Java Comparable и Cloneable реализованы многими сторонними библиотеками.
Структура типа данных предполагает, что будет участвовать во множественном наследовании.
В качестве примера абстрактного класса из стандартной библиотеки Java приведем класс AbstractMap. У его наследников — HashMap, TreeMap и ConcurrentHashMap — много общих методов. Например, get, put, isEmpty, containsKey и containsValue, которые изначально были объявлены в AbstractMap.
Пример класса, который реализует несколько интерфейсов, — класс HashMap. Он реализует интерфейсы Serializable, Cloneable, и Map. Независимо от конкретной реализации этого класса он будет поддерживать клонирование, конвертацию в массив байтов. А еще иметь функции таблицы: предоставлять доступ к внутренним данным по ключу.
Многие библиотеки используют абстрактный класс и интерфейсы одновременно. Например, класс HashMap реализует несколько интерфейсов и наследует класс AbstractMap.
Почему в Java нет множественного наследования классов
В объектно-ориентированных языках программирования множественным наследованием называется возможность описать класс, который наследуется от нескольких классов-родителей.
В отличие от других популярных ООП-языков, таких как C++, Java не поддерживает множественное наследование, так как оно вызывает проблему ромба — или, по-другому, проблему алмаза.
Проблема ромба
Рассмотрим пример наследования, где дерево классов можно представить диаграммой:

Допустим, у нас есть абстрактный класс SuperClass. В нём объявлен метод, который реализуется классами-наследниками ClassA и ClassB:
SuperClass.java
public abstract class SuperClass
ClassA.java
public class ClassA extends SuperClass < @Override public void doSomething()< System.out.println("doSomething implementation of A"); >//ClassA own method public void methodA() > >
ClassB.java
public class ClassB extends SuperClass < @Override public void doSomething()< System.out.println("doSomething implementation of B"); >//ClassB specific method public void methodB() < >>
Теперь предположим, что при разрешенном множественном наследовании ClassC наследует оба класса: ClassA и ClassB.
ClassC.java
//Это гипотетический пример описания класса //В реальном компиляторе Java данный код вызовет //ошибку компиляции, public class ClassC extends ClassA, ClassB < public void test()< //calling super class method doSomething(); >>
Заметьте, что метод test() вызывает родительский метод doSomething(). Это ведет к неопределенности: мы не знаем, какой именно код будет выполнен в итоге, так как метод doSomething() реализован и в ClassA, и в ClassB. Компилятору потребуются дополнительные инструкции, чтобы разрешить эту ситуацию. Это и называется проблемой ромба.
Мы рассмотрели простой пример, но проблема ромба усугубляется, если наследоваться от трех и более классов, которые реализуют один и тот же метод. У разработчиков Java был выбор: простота языка или дополнительные языковые конструкции для поддержки множественного наследования. Они выбрали первое. Поэтому в Java нет множественного наследования, но его можно реализовать самостоятельно. Например, с помощью паттерна программирования «композиция».
Если хотите стать Java-разработчиком, пройдите курс Skypro. Учим с нуля даже тех, у кого вообще нет опыта в IT. Уже через 11 месяцев получите новую профессию, диплом о профпереподготовке, портфолио с проектами и работу. Не просто помогаем с поиском, а устраиваем на работу: гарантию фиксируем в договоре.

Пример абстрактного класса
Рассмотрим пример приложения для рисования. Его создают по принципам объектно-ориентированного программирования. В коде приложения нужно реализовать несколько классов, которые описывают графические объекты. Например, круг, прямоугольник, линию и кривую Безье.
У всех этих объектов должны быть общие атрибуты для описания состояния объекта:
- позиция относительно сетки координат;
- размер;
- ориентация;
- цвета обводки и цвет заполнения.
А еще методы для управления состоянием: передвинуть, повернуть, изменить размер, отрисовать.
Позиция, цвет заполнения и метод «передвинуть» будут одинаковыми для всех графических объектов. Другие придется реализовывать отдельно для каждого объекта. Например, «изменить размер» и «отрисовать».
Все графические объекты должны уметь менять размер и отрисовывать себя, но способы будут разные. Это идеальная ситуация, чтобы использовать абстрактный класс-родитель.
Воспользуемся преимуществами абстрактного класса и реализуем все общие атрибуты и методы в общем предке. Назовем его GraphicObject. А всю различающуюся логику опишем в каждом классе-наследнике отдельно.
Структура дерева классов будет такая:

Сначала объявим класс GraphicObject, чтобы описать общие для всех потомков поля и методы: текущая позиция и метод для ее изменения (moveTo). В классе GraphicObject тоже объявим абстрактные методы с уникальной реализацией для каждого потомка. Например, методы draw or resize для отрисовки и изменения размера объекта соответственно. Код класса GraphicObject будет выглядеть примерно так:
abstract class GraphicObject
Все неабстрактные классы-потомки GraphicObject (Circle и Rectangle) должны содержать реализацию методов draw и resize. Их код будет выглядеть примерно так:
class Circle extends GraphicObject < void draw() < . >void resize() < . >>
class Rectangle extends GraphicObject < void draw() < . >void resize() < . >>
Если абстрактный класс реализует интерфейс
Когда класс реализует интерфейс, в нём нужно имплементировать все методы интерфейса. Но если объявить такой класс абстрактным, то некоторые или все методы можно не имплементировать. Вместо этого их можно реализовать через классы-наследники. Пример:
interface Y
abstract class X implements Y < @Override void methodA() < System.out.println(“Inside method A in class X”); >>
class XX extends X < @Override void methodB() < System.out.println(“Inside method B in class XX”); >>
В этом примере класс Х обязательно нужно объявить абстрактным, потому что он реализует только один из методов интерфейса Y. А класс ХХ, наследуя от класса Х, должен обязательно реализовать methodB().
Члены класса
У абстрактного класса могут быть статические поля и методы. Доступ к таким членам класса получают через ссылку на класс. При этом не создается его экземпляр. Например:
AbstractClass.staticMethod().
Главное про разницу между интерфейсом и абстрактным классом
- Абстрактные классы похожи на интерфейсы, но в отличие от них поддерживают внутреннее состояние. А методы абстрактного класса могут иметь уровень доступа, отличный от публичного.
- Абстрактные классы — важная часть парадигмы ООП, реализованной в Java. Как и абстрактные методы, их задают с помощью оператора abstract.
- Абстрактные классы могут не содержать абстрактные методы и реализовывать интерфейсы.
- Java не поддерживает множественное наследование, чтобы избежать проблемы ромба.
Что такое абстрактный класс java

Абстрактные классы vs интерфейсы в Java
Различия между абстрактными классами и интерфейсами в Java
В процессе профессионального развития каждого начинающего программиста всегда наступает тот момент, когда требуется понять и осознать разницу между интерфейсом и абстрактным классом. Ясно представлять себе границы их использования, а также уметь выбирать, что из этого лучше применить в том или ином месте кода.
Кроме того, обсуждаемый в данной статье вопрос о различиях, является одним из самых распространенных на собеседованиях, на который зачастую отвечают либо неполно, либо неверно.
Почему же на этот вопрос отвечают неправильно? Дело в том, что Java развивается, и границы отличий между абстрактным классом и интерфейсом постепенно стираются. А новички, читая устаревшие книги по Java (или не читая их вовсе) или даже современные статьи-перепечатки, где рассказывается об интерфейсах до Java 8, не имеют представления о том, что в своем развитии они ушли далеко вперед.
Например, до появления Java 8, при возникновении вопроса на собеседовании типа: «Назовите разницу между абстрактным классом и интерфейсом», вы смело могли сказать, что интерфейс представляет собой контракт, содержащий методы без реализации, которые должны быть реализованы в классах, имплементирующих этот интерфейс. А абстрактный класс — это класс, который содержит (или не содержит) абстрактные методы и общий для его потомков код.
Также раньше вы могли сказать, что интерфейс не может содержать статические, приватные или методы по умолчанию, и что в нем вообще не может быть реализовано никакой логики. В техническом плане все ограничения остались в прошлом — граница между этими понятиями сейчас уже почти стерта. Но с фундаментальной точки зрения ничего не изменилось!
В данной статье будут рассмотрены как фундаментальные, так и технические различия между интерфейсом и абстрактным классом.
1. Фундаментальное отличие
Фундаментальная разница между интерфейсом и абстрактным классом заключается в том, что интерфейс определяет только поведение. Он не сообщает ничего про объект, который будет его реализовывать.
Например, такое поведение как «движение» может быть применимо к разным типам объектов: машина, кот, котировки курса и т. д. Эти объекты не имеют ничего общего, кроме того, что они могут двигаться.
Перефразируем немного иначе — если существует «движущийся» объект, то глядя на реализованный им интерфейс, мы не сможем понять, какой именно тип объекта имеется ввиду. Это может быть машина, кот, котировка курса валют и много других вариантов.
Если перенести это поведение в код, написанный на языке Java, то получится следующее:
interface Movable
Абстрактный же класс описывает некий абстрактный объект (автомобиль, человека, кота и т. д.), а не только поведение.
Если мы рассуждаем про абстрактный «автомобиль», то в нашей голове сразу формируется картинка с объектом. Мы сразу понимаем, что автомобиль содержит двигатель, колеса и может двигаться, поворачивать, ускоряться или тормозить. Он также будет иметь поля для хранения внутренних деталей, таких как мотор, колеса и тормоза. Вы получите все это просто сказав слово «автомобиль».
При этом кот и котировка курса валют не могут быть автомобилем, даже если они также могут двигаться.
На основе вышесказанного создадим класс Automobile:
abstract class Automobile
Интерфейс и абстрактный класс — понятия не взаимозаменяемые. Даже если абстрактный класс с абстрактными методами выглядит подобно интерфейсу, а интерфейс, с его методами по умолчанию, подобен абстрактному классу с методами, имеющими реализации, то это все равно будут два фундаментально разных понятия. Если нам нужно поведение — необходимо использовать интерфейс. Если речь про концептуальный объект — мы должны использовать абстрактный класс.
А теперь поговорим про различия в технической реализации.
2. Технические отличия
В этом разделе мы обсудим технические (синтаксические) различия в реализации этих концепций в Java. Именно так обычно отвечают на вопрос о разнице между интерфейсами и абстрактными классами (но мы-то уже знаем, что это только вторая часть ответа, и при этом не главная).
2.1. Синтаксис создания
Итак, при создании абстрактного класса указывается ключевое слово abstract, а при определении интерфейса — interface.
Пример абстрактного класса:
public abstract class MyAbstractClass < // поля и конструкторы // абстрактные методы // методы с реализацией >
Пример интерфейса:
public interface MyInterface < // объявление констант // методы без реализации // статические методы // методы по умолчанию (default) // приватные методы >
2.2 Синтаксис использования
При наследовании от абстрактного класса используется ключевое слово extends (с англ. «расширяет»), а при реализации интерфейса — implements (с англ. «реализует»).
В приведенном ниже коде класс MyClass расширяет MyAbstractClass:
public class MyClass extends MyAbstractClass < // реализация абстрактных методов // иной код >
А тут класс MyClass реализует интерфейс MyInterface:
public class MyClass implements MyInterface < // реализация методов интерфейса // иной код >
Класс может одновременно и наследоваться от абстрактного класса (только одного) и реализовать один или множество интерфейсов.
Например, класс MyClass реализует интерфейс MyInterface, MyInterface_2 и MyInterface_3:
class MyClass extends MyAbstractClass implements MyInterface, MyInterface_2, MyInterface_3 < // реализация абстрактных методов абстрактного класса // реализация методов из интерфейсов // иной код >
2.3 Наличие конструктора
Как вы знаете, невозможно создать экземпляр абстрактного класса. Но, объявить и определить в нем конструктор мы можем. В противном случае за нас это сделает компилятор, создав конструктор по умолчанию. Без него код просто не скомпилируется, поскольку при создании конкретного класса первым оператором будет неявный вызов super() конструктора суперкласса, в данном случае абстрактного.
Добавим в абстрактный класс MyAbstractClass конструктор:
public abstract class MyAbstractClass < public MyAbstractClass() < System.out.println("Конструктор из MyAbstractClass"); >>
Также добавим конструктор в MyClass:
public class MyClass extends MyAbstractClass < public MyClass() < System.out.println("Конструктор из MyClass"); >>
В классе Main создадим объект и запустим программу:
public class Main < public static void main(String[] args) < MyAbstractClass myObject = new MyClass(); >>
Результат выполнения программы:

Для интерфейсов понятия «конструктор» не существует.
2.4 Типы переменных
Все переменные в интерфейсах неявно являются public static final (т.е. константами). «final» подразумевает, что переменной обязательно должно быть присвоено значение во время инициализации.
Рассмотрим следующий код:
public interface MyInterface < // эта строка не скомпилируется int value_1; int value_2 = 1; public final int value_3 = 1; static int value_4 = 1; public final static int value_5 = 1; static final int value_6 = 1; >
Поскольку value _1 не присвоено конкретное значение, а она является неявно final, код с такой строкой не скомпилируется. Остальные строки не вызовут ошибок, т.к. public static final можно не указывать (если указать, то IDE выделит их серым цветом, подчеркивая «избыточность»).
В абстрактных классах переменные могут быть любыми — абстрактность класса не накладывает ограничений.
Например, следующий код является корректным:
public abstract class MyAbstractClass
2.5 Модификаторы доступа методов
Модификаторы доступа для абстрактных классов могут быть любыми. При этом, все методы, кроме абстрактных, должны иметь реализацию.
А вот в случае с интерфейсами модификаторы доступа могут быть только двух типов, public и private (последний — начиная с Java 9).
Причем private может быть применим только к методам, имеющим реализацию, которые, в свою очередь, могут использоваться только методами по умолчанию, находящимися в интерфейсе. Класс, реализующий интерфейс, не будет иметь к ним доступ. Методы интерфейса без реализации являются неявно public, поэтому этот модификатор можно не писать.
Рассмотрим следующий код:
public interface MyInterface_2 < void publicAbstractMethod_1(); public void publicAbstractMethod_2(); void publicAbstractMethod_3(); private void privateMethod() < // Реализация метода >>
Методы publicAbstractMethod_1 и publicAbstractMethod_3 являются неявно public.
2.6 Методы с реализацией
Для абстрактного класса все методы, кроме абстрактных, должны иметь реализацию.
Для интерфейсов, начиная с Java 8, вводится понятие метода по умолчанию. Такие методы, во-первых, должны иметь реализацию в интерфейсе, а во-вторых, помечены ключевым словом default. Они также являются неявно public. При этом они не должны в обязательном порядке иметь реализацию в реализующем интерфейс классе, но могут быть в нем переопределены.
Также, начиная с Java 8, в интерфейсах допустимы статические методы, которые неявно являются public, но могут быть явно private. Т. е. если указать модификатор доступа private, метод будет приватным, а если ничего не указывать, то публичным.
Например, код ниже скомпилируется:
public interface MyInterface_3 < default void defaultMethod() < // Реализация метода >public default void defaultMethod_2() < // Реализация метода >private static void privateStaticMethod() < // Реализация метода >public static void publicStaticMethod_1() < // Реализация метода >static void publicStaticMethod_2() < // Реализация метода >>
2.7 Наследование
Интерфейс не может реализовывать интерфейс, не может наследовать абстрактный класс, но может наследовать (используя ключевое слово extends) множество других интерфейсов.
Абстрактный класс может наследовать как обычный класс, так и абстрактный. В обоих случаях это будет только один класс (в Java нет множественного наследования классов).
В то же время абстрактный класс также может реализовать до 65 535 интерфейсов (это связано с ограничением константы interfaces_count в структуре ClassFile).
В этом месте давайте немного отойдем от темы сравнения и затронем множественное наследование. В некоторых источниках однозначно указывается, что множественного наследования в Java нет. В других источниках говорится, что множественного наследования как бы нет, но оно реализуется через интерфейсы.
- наследование состояния (Inheritance of State)
- наследование реализации (Inheritance of Implementation)
- наследование типа (Inheritance of Type)
Java не поддерживает множественное наследование состояния, но поддерживает множественное наследование реализации (как вариант, на основе методов по умолчанию от интерфейсов). Она также поддерживает множественное наследование типов (поскольку может реализовать более одного интерфейса).
Именно таким должен быть ответ на вопрос о множественном наследовании в Java.
3. Рекомендации к применению абстрактных классов и интерфейсов
В соответствии с рекомендациями Oracle абстрактный класс нужно использовать в следующих случаях:
- Необходимо выделить общий код между несколькими тесно связанными классами
Пояснение: это типовой рефакторинг, целью которого является устранение дублирования кода.
- Мы ожидаем, что классы, расширяющие абстрактный класс, имеют много общих методов, полей или требуют модификаторов доступа, отличных от public (protected и private)
Пояснение: ранее мы писали, что в интерфейсах методы, имеющие реализацию (помеченные ключевым словом default), являются неявно public. Если же метод, имеющий реализацию, помечен явно как private, то он не сможет быть использован в классах, реализующих этот интерфейс, а только в других методах интерфейса. Поэтому, если нам нужны методы с модификаторами доступа не public, мы должны использовать абстрактный класс.
- Мы хотим объявить не static или не final поля для изменения состояния объекта.
Пояснение: ранее мы писали, что все переменные в интерфейсах неявно являются public static final — из-за чего они не могут быть изменены.
Для использования интерфейсов существуют следующие причины:
- Планируется, что несвязанные между собой классы будут реализовывать интерфейс. Например, интерфейсы Comparable и Cloneable реализуются многими несвязанными между собой классами.
Пояснение: как мы выяснили ранее, концептуальное назначение интерфейса — описание «поведения», а не «состояния» (в отличие от абстрактного класса). Соответственно, реализовать это поведение могут любые классы, несвязанные между собой, которые просто должны что-то «уметь» делать или «как-то» себя вести.
- Требуется детализировать или определить поведение определенного типа данных, но при этом мы не хотим беспокоиться о том, кто реализует его поведение.
Пояснение: пояснение к прошлому пункту также актуально и для этого — нам все равно, какие классы будут реализовывать наш интерфейс и в каких пакетах находятся. Мы просто хотим, чтобы они вели себя в соответствии с «нашим контрактом», прописанным в интерфейсе в виде методов без реализации.
- Мы хотим воспользоваться преимуществами множественного наследования типов
Приведем наглядный пример выбора между абстрактным классом и интерфейсом.
Существует паттерн «Шаблонный метод», определение которого звучит так: «шаблонный метод определяет основу алгоритма и позволяет подклассам переопределить некоторые его шаги, не изменяя структуру, в целом».
Суть паттерна заключается в размещении в абстрактном классе метода с реализацией, в котором вызываются абстрактные методы. Далее подклассы абстрактного класса реализуют их каждый по-своему.
Так вот вопрос, а что нам мешает разместить абстрактные и шаблонные методы в интерфейсе? В качестве шаблонного метода мы могли бы использовать метод по умолчанию. Вроде звучит логично, согласитесь?
Но обратите еще раз внимание на определение шаблонного метода. Там есть такие слова: «… не изменяя структуру, в целом». Подклассы абстрактного класса не должны иметь возможность переопределить шаблонный метод с целью изменения логики (алгоритма) его работы. По этой причине шаблонные методы объявляются как final и не могут использовать интерфейсы с их методами по умолчанию (могут быть переопределены) вместо абстрактных классов. Вся идея паттерна в этом случае будет нарушена.
Для того чтобы рассмотреть разницу между интерфейсом и абстрактным классом немного глубже, рекомендуется обратиться к книге «Java. Эффективное программирование», Блох Д., 3-е издание.
В разделе 4.6 этой книги говорится о следующих причинах предпочтения интерфейсов абстрактным классам:
1. Существующие классы можно легко приспособить для реализации нового интерфейса.
Пояснение: для этого достаточно написать implements и реализовать необходимые методы. Но уже имеющиеся классы в общем случае не могут быть переделаны для расширения нового абстрактного класса. Если вы хотите, чтобы два класса расширяли один и тот же абстрактный класс, вам придется поднять этот класс в иерархии настолько высоко, чтобы он стал предком обоих этих классов. Это может привести к нарушению логики иерархии типов, заставляя всех потомков нового абстрактного класса расширять его независимо от того, насколько это целесообразно.
2. Интерфейсы идеально подходят для создания миксинов.
Пояснение: Миксин (mixin) — это тип, который класс может реализовать в дополнение к своему «первичному типу», объявляя о том, что этот класс предоставляет некоторое необязательное поведение. Например, Comparable является таким интерфейсом-миксином, т. к. добавляет (примешивает) к первоначальным возможностям типа дополнительную функциональность.
Использовать абстрактные классы для создания миксинов нельзя по той же причине, по которой их невозможно приспособить к уже имеющимся классам: класс не может иметь больше одного родителя, и в иерархии классов нет подходящего места, куда можно поместить миксин.
3. Интерфейсы позволяют создавать неиерархические каркасы типов.
Пояснение: иерархии типов прекрасно подходят для организации некоторых сущностей, но зато сущности других типов невозможно аккуратно уложить в строгую иерархию. Альтернативой им является раздутая иерархия классов.
4. Интерфейсы обеспечивают безопасное и мощное развитие функциональности с использованием шаблона «Декоратор». Паттерн «Декоратор» позволяет динамически (в ходе выполнения программы) добавлять объекту новые возможности (состояние и/или поведение) на основе композиции.
