JavaRush /Java блог /Random UA /Абстрактні класи в Java на конкретних прикладах

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

Стаття з групи Random UA
Вітання! У минулих лекціях ми познайомабося з інтерфейсами та розібралися, для чого вони потрібні. Сьогоднішня тема перегукуватиметься з попередньою. Поговоримо про абстрактні класи в Java. Абстрактні класи в Java на конкретних прикладах.

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

Ти напевно пам'ятаєш, що таке «абстракція» — ми це вже проходабо :) Якщо раптом призабув — не страшно, згадаємо: це принцип ОВП , згідно з яким при проектуванні класів та створенні об'єктів необхідно виділяти лише головні властивості сутності та відкидати другорядні. Наприклад, якщо проектуватимемо клас 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 на конкретних прикладах. Саме через таку «незрозумілість», коли об'єкту неясно, яку поведінку він має вибрати, творці Java відмовабося від множинного успадкування. Втім, ти пам'ятаєш, що Java класи реалізують безліч інтерфейсів. До речі, ти вже зустрічався у навчанні як мінімум із одним абстрактним класом! Хоча, може, й не помітив цього :)
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>
Це твій старий знайомий – клас Calendar. Він абстрактний, і він має кілька спадкоємців. Одним із них — GregorianCalendar. Ти вже користувався ним в уроках про дати :) Начебто все зрозуміло, залишився лише один момент: у чому ж принципова різниця між абстрактними класами та інтерфейсами ? Навіщо в Java додали і те, й інше, а чи не обмежабося чимось одним? Адже цього цілком могло вистачити. Про це поговоримо у наступній лекції! До зустрічі:)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ