Абстрактные классы в Java на конкретных примерах

🔥 🚀 Важно для всех, кто работает с Java! 🔥
На JavaRocks ты найдешь уникальные туториалы, практические задачи и редкие книги, которых не найти в свободном доступе. Присоединяйся к нашему Telegram-каналу JavaRocks — стань частью профессионального сообщества!

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

Почему классы называются “абстрактными”

Абстракция – это принцип ООП, который заключается в том, что при проектировании классов и создании объектов мы должны определять только ключевые свойства сущности и отбрасывать второстепенные.

Например, если мы проектируем класс SchoolTeacher, нам вряд ли понадобится свойство ‘height’ (рост), поскольку для учителя оно несущественно. При создании же класса BasketballPlayer рост будет важной характеристикой.

Итак, абстрактный класс — это, по сути, “заготовка” для группы будущих классов. Саму заготовку нельзя использовать напрямую — она слишком “сырая”. Однако она описывает базовое состояние и общее поведение, которые будут унаследованы дочерними классами.

Примеры абстрактных классов Java

Рассмотрим простой пример с автомобилями:

public abstract class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public abstract void gas();

   public abstract void brake();

   public String getModel() {
       return model;
   }

   public void setModel(String model) {
       this.model = model;
   }

   public String getColor() {
       return color;
   }

   public void setColor(String color) {
       this.color = color;
   }

   public int getMaxSpeed() {
       return maxSpeed;
   }

   public void setMaxSpeed(int maxSpeed) {
       this.maxSpeed = maxSpeed;
   }
}
Run Code

Вот как выглядит самый простой абстрактный класс. В приведенном примере он описывает сущность Car (автомобиль) максимально абстрактно, потому что в реальном мире не существует “абстрактных” автомобилей: есть грузовики, гоночные машины, седаны, купе и внедорожники.

Наш абстрактный класс – это просто “чертёж”, который мы будем использовать в дальнейшем для создания классов автомобилей.

public class Sedan extends Car {

   @Override
   public void gas() {
       System.out.println("The sedan is accelerating!");
   }

   @Override
   public void brake() {
       System.out.println("The sedan is slowing down!");
   }

}
Run Code

Вот пример конкретного класса Sedan, который наследуется от абстрактного класса Car.

Преимущество рассматриваемого метода состоит в том, что мы не можем создать копию абстрактного класса:

public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Error! The Car class is abstract!
   }
}
Run Code

Абстрактный класс – “чертеж” для дальнейшего создания классов. А копии “чертежа” создавать бессмысленно, и создатели Java это предусмотрели.

Если бы класс Car был не абстрактным, мы могли бы спокойно создавать его экземпляры:

public class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public void gas() {
       // Some logic
   }

    public void brake() {
       // Some logic
   }
}


public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Everything is fine. A car is created.
   }
}
Run Code

Таким образом, мы создали совершенно непонятный “абстрактный” автомобиль, которого не существует в природе – это и не грузовик, и не гоночная машина, и не седан.

Абстрактные классы не позволяют создать абстрактные объекты. Они задают базовое состояние и поведение. Например, все автомобили должны иметь модель, цвет и максимальную скорость, а также уметь ускоряться и тормозить. Это общий абстрактный план. На его основе мы можем проектировать конкретные классы.

Обратите внимание! В созданном нами абстрактном классе Car есть два абстрактных метода ( gas() и brake()), они не имеют реализации. Причина состоит в том, что абстрактные классы не задают по умолчанию поведение для абстрактного автомобиля, а лишь указывают, что должна уметь делать каждая машина.

При необходимости можно реализовать методы и в абстрактном классе:

public abstract class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public void gas() {
       System.out.println("Gas!");
   }

   public abstract void brake();

   // Getters and setters
}


public class Sedan extends Car {

   @Override
   public void brake() {
       System.out.println("The sedan is slowing down!");
   }

}

public class Main {

   public static void main(String[] args) {

       Sedan sedan = new Sedan();
       sedan.gas();
   }
}
Run Code

Консольный вывод:

“Gas!”

Таким образом, мы реализовали только первый метод, оставив второй нереализованным. 

В результате поведение нашего класса Sedan разделилось на две части: при вызове gas() происходит обращение к родительскому абстрактному классу Car, а метод brake() мы переопределили в классе Sedan. Это довольно удобный и гибкий подход.

Если половина методов реализована, значит, наш класс не такой уж и абстрактный?

Вовсе нет. Класс является абстрактным, если хотя бы один из его методов является абстрактным.

Мы даже можем реализовать все методы и не оставить ни одного абстрактного. Тогда это будет абстрактный класс без абстрактных методов. В принципе, это возможно, и компилятор не будет выдавать ошибок, но лучше этого избегать: слово “абстрактный” теряет смысл, а ваши коллеги-программисты будут очень удивлены 🙂

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

Конечно, каждый класс может наследовать только один абстрактный класс, поэтому с точки зрения наследования нет никакой разницы между абстрактными и обычными классами. Неважно, наследуем мы абстрактный класс или обычный, родительский класс может быть только один.

Почему в Java нет множественного наследования классов

Если бы в Java поддерживалось множественное наследование, дочерние классы не смогли бы решить, какое именно поведение им выбрать.

Предположим, у нас есть два класса – Toaster и NuclearBomb:

public class Toaster {


 public void on() {

       System.out.println("The toaster is on. Toast is being prepared!");
   }

   public void off() {

       System.out.println("The toaster is off!");
   }
}


public class NuclearBomb {

   public void on() {

       System.out.println("Boom!");
   }
}
Run Code

В обоих классах представлен метод on(). Только для тостера он запускает процесс приготовления, а для ядерной бомбы инициирует взрыв.

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

Этот код не работает, но мы приводим его только в качестве примера:

public class MysteriousDevice extends Toaster, NuclearBomb {

   public static void main(String[] args) {

       MysteriousDevice mysteriousDevice = new MysteriousDevice();
       mysteriousDevice.on(); // So what should happen here? Do we get toast or a nuclear apocalypse?
   }
}
Run Code

MysteriousDevice наследует Toaster и NuclearBomb. Если мы вызовем метод on(), то будет непонятно, какая из его реализаций должна сработать.

Кроме того, у NuclearBomb нет метода off(), поэтому, если мы сделали неправильный выбор, “выключить” устройство будет невозможно. Из-за такой путаницы создатели Java и отказались от множественного наследования.

Тем не менее, классы могут реализовывать несколько интерфейсов. Например:

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>

Это абстрактный класс Calendar, он имеет несколько дочерних классов, один из них – GregorianCalendar.

Перевод статьи «Abstract classes in Java with specific examples».

Читайте также: «Отношения между классами: наследование, композиция и агрегация»

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх