Привіт! Давай поговоримо про абстрактні класи в Java. Абстрактні класи в Java на конкретних прикладах - 1

Чому класи називають «абстрактними»

Ти, напевно, пам'ятаєш, що таке «абстракція» — ми це вже проходили :) Якщо раптом забулося — не страшно, згадаємо: це принцип ООП, згідно з яким при проєктуванні класів та створенні об'єктів необхідно виокремлювати лише головні властивості сутності, та відкидати другорядні. Наприклад, якщо ми проєктуємо клас SchoolTeacher — шкільний вчитель — навряд чи знадобиться характеристика «зріст». Справді: для викладача ця характеристика не є важливою. Але якщо ми будемо створювати в програмі клас 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;
   }
}
Ось так виглядає найпростіший абстрактний клас. Як бачиш, нічого особливого :) Навіщо він може нам знадобитися? Насамперед він максимально абстрактно описує потрібну нам сутність — автомобіль. Слово abstract тут недарма. У світі немає «просто машин». Є вантажівки, гоночні автомобілі, седани, купе, позашляховики. Наш абстрактний клас — це просто «креслення», з яким ми пізніше будемо створювати класи-автомобілі.
public class Sedan extends Car {

   @Override
   public void gas() {
       System.out.println("Седан газує!");
   }

   @Override
   public void brake() {
       System.out.println("Седан гальмує!");
   }

}
Це схоже на те, про що ми говорили в лекціях про спадкування. Тільки там у нас клас Car та його методи були абстрактними. Але таке рішення має цілу низку мінусів, які в абстрактних класах виправлені. Перше і головне — екземпляр абстрактного класу створити не можна:
public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Помилка! Клас Car є абстрактним!
   }
}
Ця «фішка» була реалізована творцями Java спеціально. Ще раз, для запам'ятовування: абстрактний клас - це просто креслення для майбутніх «нормальних» класів. Тобі ж не потрібні екземпляри креслення, правильно? Ось і екземпляри абстрактного класу створювати не треба :) А якщо б клас Car не був абстрактним, ми легко могли б створювати його об'єкти:
public class Car {

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

   public void gas() {
       // якась логіка
   }

   public  void brake() {
       // якась логіка
   }
}


public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Все ок, авто створено
   }
}
Тепер у нас у програмі з'явилася якась незрозуміла машина — не вантажівка, не гоночна, не седан, а не зрозуміло що. Та сама «просто машина», яких у природі немає. Той самий приклад можна навести з тваринами. Уяви, якби у твоїй програмі з'явилися об'єкти Animal — «просто тварина». Якого воно виду, до якого сімейства належить, які в неї характеристики — неясно. Було б дивно побачити його у програмі. Жодних «просто тварин» у природі не існує. Тільки собаки, кішки, лисиці, кроти та інші. Абстрактні класи позбавляють нас від «просто об'єктів». Вони дають нам базовий стан та поведінку. Наприклад, у всіх машин має бути модель, колір і максимальна швидкість, а ще вони повинні вміти газувати та гальмувати. Ось і все. Це — загальна абстрактна схема, далі ти сам проектуєш потрібні тобі класи. Зверни увагу: два методи в абстрактному класі теж позначені як abstract, і вони взагалі не реалізовані. Причина та ж: абстрактні класи не створюють «за замовчуванням» для «просто машин». Вони просто кажуть, що мають вміти робити усі машини. Втім, якщо поведінка за замовчуванням тобі все ж таки потрібна, методи в абстрактному класі можна реалізувати. Java цього не забороняє:
public abstract class Car {

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

   public void gas() {
       System.out.println("Газуємо!");
   }

   public abstract void brake();

   //геттери і сеттери
}


public class Sedan extends Car {

   @Override
   public void brake() {
       System.out.println("Седан гальмує!");
   }

}

public class Main {

   public static void main(String[] args) {

       Sedan sedan = new Sedan();
       sedan.gas();
   }
}
Виведення в консолі: “Газуємо!” Як бачиш, ми реалізували в абстрактному класі один метод, а другий не стали. У результаті поведінка нашого класу Sedan розділилася на дві частини: якщо викликати в нього метод gas(), він «підтягнеться» з батьківського абстрактного класу Car, а метод brake() ми перевизначили у класі Sedan. Вийшло дуже зручно та гнучко. Але тепер наш клас не такий вже й абстрактний? Адже в нього, за фактом, половина методів реалізована. Насправді, і це дуже важлива особливість — клас є абстрактним, якщо хоча б один із його методів є абстрактним. Хоч один із двох, хоч один із тисячі методів — не має значення. Ми можемо навіть реалізувати всі методи та не залишити жодного абстрактного. Буде абстрактний клас без абстрактних методів. В принципі, це можливо, і компілятор не видасть помилок, але краще так не робити: слово abstract втратить сенс, а твої колеги-програмісти дуже здивуються, побачивши таке :/ При цьому, якщо метод позначений словом abstract, кожен клас-спадкоємець має його реалізувати або бути оголошеним як абстрактний. Інакше компілятор викине помилку. Зрозуміло, кожен клас може успадкуватися лише від одного абстрактного класу, отже в плані спадкування різниці між абстрактними та звичайними класами немає. Не має значення, успадковуємося ми від абстрактного класу або від звичайного, клас-батько може бути лише один.

Чому в Java немає множинного успадкування класів

Ми вже говорили, що в Java немає множинного успадкування, але належним чином не розібралися, чому саме. Давайте спробуємо зробити це зараз. Справа в тому, що якби в Java було множинне успадкування, дочірні класи не могли б визначитися, яку саме поведінку обрати. Припустимо, у нас є два класи — Toster и NuclearBomb:
public class Toster {


 public void on() {

       System.out.println("Тостер увімкнений, тост готується!");
   }

   public void off() {

       System.out.println("Тостер вимкнено!");
   }
}


public class NuclearBomb {

   public void on() {

       System.out.println("Вибух!");
   }
}
Як бачиш, у обох є метод on(). У випадку з тостером він запускає приготування тосту, а у випадку з ядерною бомбою влаштовує вибух. Ой :/ А тепер уяви, що ти вирішив (не знаю, з чого раптом!) створити щось середнє між ними. І ось він твій клас — MysteriousDevice! Цей код, зрозуміло, не робочий, і ми наводимо його просто як приклад «а як воно могло б бути»:
public class MysteriousDevice extends Toster, NuclearBomb {

   public static void main(String[] args) {

       MysteriousDevice mysteriousDevice = new MysteriousDevice();
       mysteriousDevice.on(); // І що тут має статися? Ми отримаємо тост чи ядерний апокаліпсис?
   }
}
Погляньмо, що в нас вийшло. Загадковий пристрій походить одночасно і від Тостера і від Ядерної Бомби. В обох є метод on(), і в результаті незрозуміло, який із методів on() повинен спрацьовувати в об'єкта MysteriousDevice, якщо ми його викличемо. Об'єкт ніяк не зможе цього зрозуміти. Ну і як вишенька на торті: у Ядерної Бомби немає методу off(), так що якщо ми не вгадали, відключити пристрій буде неможливо. Абстрактні класи в Java на конкретних прикладах - 2 Саме через таку «незрозумілість», коли об'єкту неясно, яку поведінку він має обрати, творці Java відмовилися від множинного успадкування. Втім, ти дізнаєшся, що Java класи реалізують безліч інтерфейсів.