Абстрактні класи. Абстрактні методи. Приклади. Ключове слово abstract

Абстрактні класи. Абстрактні методи. Приклади. Ключове слово abstract


Зміст



1. Що таке абстрактний клас? Призначення абстрактних класів. Загальна форма. Ключове слово abstract

Абстрактний клас – це клас, який містить методи, що не мають реалізації. Абстрактний клас створюється з метою створення спільного інтерфейсу між різними реалізаціями класів, які будуть породжені від абстрактного класу. Абстрактний клас створюється для визначення деяких спільних рис класів які будуть визначати конкретну реалізацію в породжених від нього класах.

Заборонено (немає сенсу) створювати об’єкт абстрактного класу.

Клас вважається абстрактним, якщо в класі оголошено хоча б один абстрактний метод. Перед оголошенням абстрактного класу розміщується ключове слово abstract.

Загальна форма оголошення абстрактного класу наступна:

abstract class ClassName {
    // class methods and variables

    ...

    abstract type AbstractMethod1(parameters1);
    abstract type AbstractMethod2(parameters2);
    ...
    abstract type AbstractMethodN(parametersN);
}

тут

  • ClassName – ім’я абстрактного класу, що оголошується;
  • AbstractMethod1, AbstractMethod2, AbstractMethodN – імена абстрактних методів, що оголошуються в абстрактному класі;
  • type – деякий тип;
  • parameters1, parameters2, parametersN – перелік параметрів, що отримують відповідні абстрактні методи з іменами AbstractMethod1, AbstractMethod2, AbstractMethodN.

 

2. Що таке абстрактний метод? Загальна форма

Абстрактний метод – це метод, реалізація якого в програмі не має ніякого змісту. Абстрактний метод – це тільки оголошення форми (інтерфейсу) а не реалізація. Як і у випадку з абстрактним класом, абстрактний метод починається з ключового слова abstract.

Якщо у класі оголошено абстрактний метод, то клас також вважається абстрактним. У цьому випадку перед іменем класу також ставиться ключове слово abstract.

Якщо деякий клас є успадкованим від абстрактного, то цей клас повинен перевизначити усі абстрактні методи базового абстрактного класу. В іншому випадку буде згенеровано помилку.

Загальна форма оголошення абстрактного методу в абстрактному класі має такий вигляд:

abstract class ClassName {

    // ...
    type AbstractMethod(parameters);
}

тут

  • ClassName – ім’я абстрактного класу, що містить абстрактний метод з іменем AbstractMethod;
  • AbstractMethod – ім’я абстрактного методу, який повертає значення типу type та отримує параметри parameters.

В ієрархії успадкування (розширення) класів, абстрактні методи є чимось загальним. Конкретні реалізації абстрактних методів містяться у класах, успадкованих від абстрактних класів.

 

3. Схематичне зображення оголошення та використання абстрактного методу в абстрактному класі. Приклад

На схемі зображено простий приклад оголошення абстрактного класу з іменем AbstractClass. Цей клас містить оголошення абстрактного методу з іменем ShareMethod().

З абстрактного класу AbstractClass успадковано два класи з іменами Class1, Class2. У Цих класах реалізується метод ShareMethod(), який у класі AbstractClass оголошено як абстрактний.

Java Схема взаємодії абстрактний клас спадковість

Рисунок. Схема взаємодії між абстрактним класом та породженими класами в Java

 

4. Приклад, що демонструє використання абстрактних класів

У прикладі оголошується абстрактний клас Figure, який описує загальну інформацію про деяку геометричну фігуру на площині. З класу Figure успадковується два класи Triangle та Circle, які перевизначають абстрактні методи класу Figure.

У класі Figure оголошуються:

  • прихована (protected) властивість name, яка визначає назву геометричної фігури;
  • прихована (protected) константа pi;
  • абстрактний метод ShowName(), який виводить назву фігури;
  • абстрактний метод Area(), який обчислює площу фігури;
  • метод, що повертає назву фігури (значення поля name).
// абстрактний клас, що описує деяку геометричну фігуру
abstract class Figure {
    protected String name = ""; // ім'я фігури
    protected double pi = 3.1415; // константа Пі

    // абстрактні методи, які будуть перевизначатись в породжених класах
    abstract void ShowName(); // вивести ім'я фігури
    abstract double Area(); // визначення площі

    // метод, що повертає назву фігури
    String GetName() {
        return name;
    }     
}

Також реалізовано два класи з іменами Triangle та Circle. Ці класи успадковують (розширюють) клас Figure.

У класі Triangle() реалізовано:

  • внутрішні змінні a, b, c, що є сторонами трикутника;
  • конструктор класу;
  • метод ShowName(), який перевизначає абстрактний метод ShowName() класу Figure. У цьому методі виводиться назва класу “Triangle”;
  • метод Area(), який перевизначає абстрактний метод Area() з класу Figure. У методі обчислюється площа трикутника.
// клас, що реалізує трикутник
class Triangle extends Figure {
    double a, b, c; // сторони трикутника

    // конструктор
    Triangle(double a, double b, double c) {
        name = "Triangle";
        this.a = a; 
        this.b = b; 
        this.c = c;        
    }

    // перевизначення абстрактного методу ShowName()
    void ShowName() {         
        System.out.println("Triangle");
    }

    // перевизначення абстрактного методу Area()
    // площа трикутника
    double Area() {           
        // перевірка, чи взагалі з відстаней a, b, c можна утворити трикутник
        if (((a+b)<c) || ((b+c)<a) || ((a+c)<b))
            return 0.0;        
        double p = (a+b+c)/2; // півпериметер
        double s;

        // Формула Герона
        s = Math.sqrt(p*(p-a)*(p-b)*(p-c));
        return s;          
    }     
}

Клас Circle реалізує коло, яке належить до геометричних фігур. Тому клас Circle успадковує (розширює) клас Figure. У класі Circle реалізовано:

  • внутрішню змінну r, яка визначає радіус кола;
  • конструктор;
  • метод Area(), який перевизначає однойменний абстрактний метод класу Figure. Метод обчислює площу кола;
  • метод ShowName(), який перевизначає однойменний абстрактний метод класу Figure. Метод виводить ім’я класу “Circle”.
// клас, що реалізує коло, успадковує клас Figure
class Circle extends Figure {
    double r;

    // конструктор
    Circle(double r) {
        name = "Circle";
        this.r = r;
    }

    // перевизначення абстрактного методу Area()
    double Area() {           
        return pi*r*r;
    }

    // перевизначення абстрактного методу ShowName()
    void ShowName() {
        System.out.println("Circle");
    }     
}

Для того, щоб продемонструвати використання абстрактних класів, створюється додатковий клас з іменем UseAbstractClass. Цей клас реалізує:

  • метод GetName(), повертає ім’я екземпляру f, що передається в якості вхідного параметру. Параметр f є посиланням на клас Figure;
  • метод GetArea(), що повертає площу екземпляра f класу Figure. Екземпляр (об’єкт) класу f є вхідним параметром методу. Цей метод наочно демонструє так зване пізнє зв’язування, суть якого описується нижче;
  • статичний метод main(), який є точкою входу в програму. Цей метод реалізує демонстрацію роботи абстрактного класу Figure.
// клас, що використовує абстрактний клас Figure
public class UseAbstractClass {
    // метод, що отримує посилання на базовий клас
    static String GetName(Figure f) {
        return f.GetName(); // виклик методу базового класу
    }

    // метод, що повертає площу фігури, f - посилання на базовий клас
    // використовується пізнє зв'язування,
    // метод для обчислення площі визначається на основі значення посилання f
    static double GetArea(Figure f) {
        return f.Area(); // виклик методу обчислення площі
    }

    public static void main(String[] args) {
        // демонстрація використання абстрактних методів Area() та ShowName()
        Figure f1 = new Triangle(3.5, 1.8, 2.2); // екземпляр класу Triangle
        Figure f2 = new Circle(3.0); // екземпляр класу Circle
        double area;

        // вивести назви екземплярів f1, f2
        f1.ShowName(); // Triangle
        f2.ShowName(); // Circle

        //
        String name;
        name = GetName(f1); // name = "Triangle"
        System.out.println(name);

        name = GetName(f2); // name = "Circle
        System.out.println(name);

        // обчисленння площі для трикутника
        // реалізація пізнього зв'язування
        area = GetArea(f1); // area = 1.6833281765597579
        System.out.println("area = " + area);

        // обчислення площі для кола,
        // реалізація пізнього зв'язування
        area = GetArea(f2); // area = 28.2735
        System.out.println("area = " + area);
    }
}

У результаті виконання функції main() класу UseAbstractClass буде виведено наступний результат:

Triangle
Circle
Triangle
Circle
area = 1.6833281765597579
area = 28.2735

 

5. Пояснення до прикладу з пункту 4

Пояснення до прикладу (див. попередній пункт) у вигляді запитань.

5.1. Чому в класі Figure методи Area() та ShowName() оголошуються абстрактними?

Клас Figure представляє собою узагальнення геометричної фігури. У цьому класі визначаються спільні властивості усього різномаїття геометричних фігур. Конкретні фігури (трикутник, коло) розширюють (extends) можливості класу Figure або, іншими словами, успадковують клас Figure. У нашому випадку, конкретними фігурами вибрано трикутник (клас Triangle) та коло (клас Circle). Класи Triangle та Circle є конкретними реалізаціями узагальненого класу Figure.

У класі Figure метод Area() оголошується абстрактними (abstract), тому що неможливо визначити площу узагальненої фігури, оскільки, поки що невідомо, яка це фігура (трикутник чи коло). Для трикутника (клас Triangle) площа визначається за формулою Герона. Для кола площа визначається іншою стандартною формулою: S = π·R2.

Звідси можна зробити висновок: немає сенсу викликати метод Area() з базового класу Figure. Цей метод оголошується тільки для організації ієрархічного виклику методів Area(), які обчислюють площі фігур конкретних реалізацій породжених від Figure класів. У нашому випадку цими породженими класами є класи Triangle та Circle.

 

5.2. Чому клас Figure оголошується абстрактним?

Якщо клас містить хоча б один абстрактний метод, то цей клас вважається абстрактним. Клас Figure містить два абстрактні методи, тому перед оголошенням класу ставиться ключове слово abstract.

5.3. Чому в класі Figure методи Area() та ShowName() не містять коду реалізації (тіла методу)?

Якщо в абстрактному класі метод оголошений як абстрактний (з ключовим словом abstract), то цей метод не повинен містити реалізації (згідно синтаксису Java). Це пояснюється тим, що виклик цього методу не має сенсу.

 

5.4. Чи можна в абстрактному класі Figure додавати інші не абстрактні методи?

 Так, можна. Абстрактний клас може містити не абстрактні методи (на відміну від інтерфейсу).

 

5.5. Чи можна створити екземпляр класу Figure у функції main() класу UseAbstractClass?

Ні, не можна. Тобто, наступний рядок

Figure f3 = new Figure();

є помилкою компілятора Java: “Cannot instantiate the type Figure”.

Однак, можна оголосити посилання на клас Figure. Оскільки клас Figure є базовим для класів Triangle та Circle, то, використовуючи це посилання, можна створити екземпляри (об’єкти) класів, похідних від Figure. Нижченаведений код демонструє використання посилання на базовий клас Figure:

// оголошення посилання на базовий клас
Figure f3;

// створення екземпляру класу Circle, похідного від Figure
f3 = new Circle(2.5);

// виклик методу класу Circle
f3.ShowName();

Однак, можна створювати екземпляри класів, породжених від класу Figure (наприклад, Triangle, Circle).

 

5.6. В чому полягає суть пізнього зв’язування у методі GetArea() класу UseAbstractClass?

Метод GetArea() отримує посилання з іменем f абстрактного класу Figure, який є базовим в ієрархії класів (з класу Figure породжуються два класи Triangle та Circle).

static double GetArea(Figure f) {
    return f.Area(); // виклик методу обчислення площі
}

Потім за посиланням викликається метод Area() у рядку

...

return f.Area();

...

На момент компіляції методу GetArea() не можна сказати, який екземпляру класу (Triangle чи Circle) буде переданий в метод. Отже, не можна сказати, який метод Area() буде викликаний. Тому, в метод передається посилання на базовий клас Figure.

У функції main() при виклику методу GetArea()

...

Figure f1 = new Triangle(3.5, 1.8, 2.2); // екземпляр класу Triangle
Figure f2 = new Circle(3.0); // екземпляр класу Circle

...

// обчисленння площі для трикутника
// реалізація пізнього зв'язування
area = GetArea(f1); // передається екземпляр класу Triangle

...

area = GetArea(f2); // передається екземпляр класу Circle

...

в цей метод передаються різні посилання (f1, f2) які є екземплярами класів, породжених від класу Figure. Ці посилання є екземплярами класів Triangle та Circle.

У першому випадку

area = GetArea(f1);

всередині методу GetArea() компілятор присвоює посиланню f значення посилання f1, яке вказує на клас, що містить метод Area() класу Triangle. Таким чином, відбувається зв’язування узагальненого посилання f з класом Triangle завдяки ієрархії успадкування. Таке зв’язування називається пізнім зв’язуванням. Деталі пізнього зв’язування – це вже інша тема.

У такий самий спосіб зв’язується екземпляр f2 класу Circle з узагальненим посиланням f в методі GetArea()

area = GetArea(f2);

 

6. Чи можна в абстрактному класі оголошувати методи, які мають реалізацію (тіло)?

Так, можна. Абстрактний клас допускає реалізацію не абстрактних методів.

 

7. Приклад створення ієрархії абстрактних класів

Якщо оголошується клас, похідний від абстрактного і в цьому класі не має реалізації абстрактних методів, тоді цей клас автоматично вважається абстрактним. Перед іменем цього класу потрібно вказати abstract, інакше компілятор згенерує помилку.

Приклад. Нижче наведено приклад ієрархії абстрактних класів

// ієрархія абстрактних класів
abstract class A {
    abstract void Show();
}

// у класі B немає реалізації абстрактного методу Show() з класу A,
// тому цей клас також є абстрактним
abstract class B extends A {
    abstract void Show(); // немає реалізації
}

// у класі C реалізовано абстрактний метод Show() класів A, B
class C extends B {
    // реалізація методу Show
    void Show() {
        System.out.println("Class C");
    }    
}

Як видно з прикладу, у класі B успадкований абстрактний метод Show() не має реалізації. Тому, обов’язково перед визначенням класу B потрібно вказати ключове слово abstract.

Використання класу C може бути, наприклад таким

A objC = new C(); // посилання на базовий клас A
objC.Show(); //
C objC2 = new C(); // посилання на клас C
objC2.Show();

 

8. Чи може абстрактний клас не містити абстрактних методів?

Так, може. Це необхідно у випадках, коли абстрактні методи в класі не потрібні, але потрібно заборонити створення екземплярів цього класу.

 

9. Які відмінності між використанням абстрактних класів та використанням інтерфейсів?

Між абстрактними класами та інтерфейсами існують наступні відмінності:

  • абстрактні класи можуть містити реалізації методів, інтерфейси не можуть;
  • в інтерфейсах усі методи, що оголошуються, є абстрактними. В абстрактних класах можна оголошувати як абстрактні, так і не абстрактні методи;
  • змінні, що оголошуються в інтерфейсах, обов’язково мають бути ініціалізовані. В абстрактних класах внутрішні змінні можна не ініціалізовувати;
  • в інтерфейсах усі оголошені змінні неявно вважаються константами (оголошені з ключовими словами final, static). В абстрактних класах оголошені змінні не вважаються константами.

 

10. Переваги використання абстрактних класів

Використання абстрактних класів дає наступні переваги:

  • використання ключового слова abstract перед оголошенням класу підкреслює абстрактність цього класу. Це, в свою чергу, повідомляє розробнику про те, як з цим класом потрібно поводитись;
  • абстрактні класи є корисними у випадку переробки програм. З допомогою абстрактних класів можна легко “переміщувати” спільні методи вверх по ієрархії.

 


Зв’язані теми