Абстрагирование – это процесс, в ходе которого от пользователя скрываются многие детали реализации, а предоставляются только те детали, которые действительно важны. Так удаётся сфокусироваться на том, что делает объект, а не как он это делает.

В 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, нужно держать в уме некоторые наилучшие практики. Вот некоторые рекомендации, которые помогут вам эффективно пользоваться этими мощными инструментами:

  1. Пользуйтесь абстрактными классами, чтобы моделировать иерархии взаимосвязанных классов: при помощи абстрактных классов очень удобно представить группу таких взаимосвязанных классов, которые совместно используют некий общий функционал. Создавая абстрактный класс, который определяет общие методы и свойства, вы тем самым избегаете дублирования, повышаете модульность вашего кода и облегчаете его поддержку.
  2. Пользуйтесь абстрактными методами, чтобы определять общее поведение: абстрактные методы полезны в тех случаях, когда требуется навязать конкретное поведение сразу для множества субклассов. Определяя абстрактный метод в абстрактном классе, вы можете гарантировать, что все субклассы (каждый по-своему) будут реализовывать одно и то же поведение.
  3. Не переборщите с абстрактными классами и методами: притом, что абстрактные классы и методы могут быть довольно мощными, важно не прибегать к ним сверх меры. Вообще, следует создавать абстрактный класс или метод только в том случае, когда на то есть явная причина. Если слишком активно ими пользоваться, то код может излишне усложниться, и его станет тяжелее поддерживать.
  4. Придерживайтесь соглашений об именованиях: подбирая имена для абстрактных классов и методов, важно придерживаться стандартных соглашений об именованиях, действующих в Java. Именем абстрактного класса должно быть абстрактное существительное (напр., “Shape”), а имена абстрактных методов должны быть глаголами (напр. “draw”).
  5. Документируйте ваш код: как и при работе с любым кодом, абстрактные классы и методы важно документировать, чтобы они были понятнее другим разработчикам. Снабжайте код чёткими и краткими комментариями, которые поясняли бы смысл поведения каждого класса и метода, а также их назначение.

Следуя этим наилучшим практикам, можно гарантировать эффективность ваших абстрактных классов и методов при реализации абстракций и создании модульного кода, который удобно поддерживать.

Заключение


Итак, абстрактные классы и методы – это мощные инструменты для реализации абстракций в Java. Умело пользуясь ими, вы сможете создавать более модульный и удобный в поддержке код, который проще понимать и расширять. Если вы моделируете иерархии взаимосвязанных классов или определяете общее поведение, которое должно реализовываться сразу во множестве субклассов, то вам в этом могут особенно помочь абстрактные классы и методы. Пользуясь рекомендованными практиками, рассмотренными выше, вы сможете освоить этот важный аспект программирования на Java.

Часто задаваемые вопросы


• Могут ли у абстрактных классов быть конструкторы?

Да, у абстрактных классов могут быть конструкторы. Правда, их нельзя напрямую инстанцировать, поэтому их конструкторы обычно вызываются конструкторами их субклассов.

• Могут ли абстрактные классы реализовывать интерфейсы?
Да, абстрактные классы могут реализовывать интерфейсы, это могут делать вообще любые классы. Эта возможность может пригодиться, если вы хотите предоставить некое общее поведение сразу для нескольких классов, так, чтобы они реализовывали один и тот же интерфейс.

• Могут ли абстрактные методы быть статическими?
Нет, абстрактные методы не могут быть статическими, так как для их работы требуется вызвать экземпляр класса. Правда, статические методы могут вызывать абстрактные методы, если они реализованы в конкретных субклассах.

• Могут ли абстрактные классы быть финальными?
Нет, абстрактные классы не могут быть финальными, так как они предназначены именно для расширения субклассами. Если сделать абстрактный класс финальным, то такое расширение будет исключено.

• Когда следует использовать абстрактные классы вместо интерфейсов?
Абстрактные классы хорошо вам подойдут, если вы хотите предоставить некоторый общий функционал для множества классов, входящих в общую иерархию. Интерфейсы лучше подходят для определения такого набора, методы из которого разные классы могут реализовывать независимо. Правило такое: пользуйтесь абстрактными классами, когда хотите предоставить базовую реализацию для группы взаимосвязанных классов. Интерфейсы уместнее, когда нужно определить группу методов для реализации разрозненными классами, в каждом по-своему.

p.s. книга по теме: Программируем на Java. 5-е межд. изд.

Комментарии (3)


  1. Naf2000
    30.05.2023 12:57
    +1

    И ни слова про интерфейсы. А ведь они более абстрактны в системе типов, чем абстрактные классы. Хотя сейчас грань различия стирается.


  1. nronnie
    30.05.2023 12:57
    +2

    Абстрагирование – это процесс, в ходе которого от пользователя скрываются многие детали реализации, а предоставляются только те детали, которые действительно важны.

    Прошу прощения, вы с инкапсуляцией не путаете?

    Правда, статические методы могут вызывать абстрактные методы, если они реализованы в конкретных субклассах.

    Хммм.... Это как?


  1. dmt_ovs
    30.05.2023 12:57
    -2

    Я конечно понимаю, что это перевод статьи (тут больше вопросы к автору оригинала), но зачем повторять один и тот же пример кода с одинаковым объяснением?)

    Выглядит, как будто копирайтеру за количество символов заплатили...