1. Знайомство з інтерфейсами
Сьогодні у вас справжній день знань. Ще одна нова й цікава тема — інтерфейси.
Інтерфейс — це дитя Абстракції та Поліморфізму. Інтерфейс дуже нагадує абстрактний клас, який має лише абстрактні методи. Він оголошується так само, як і клас, тільки використовується ключове слово interface
.
interface Котячі
{
void мурчати();
void нявкати();
void гарчати();
}
Декілька корисних фактів про інтерфейси:
1. Оголошення інтерфейсу
interface Drawable
{
void draw();
}
interface HasValue
{
int getValue();
}
- Замість слова
class
пишемоinterface
. - Містить лише абстрактні методи (слово
abstract
писати не потрібно) - Усі методи інтерфейсів —
public
Інтерфейс може успадковуватися тільки від інтерфейсів. Натомість може мати багато батьків. Про це кажуть, що в Java підтримується множинне успадкування інтерфейсів. Приклади:
interface Element extends Drawable, HasValue
{
int getX();
int getY();
}
3. Успадкування класів від інтерфейсів
Клас може успадковуватися від декількох інтерфейсів (але тільки від одного класу). Для цього використовується ключове слово implements
. Приклад:
class abstract ChessItem implements Drawable, HasValue
{
private int x, y, value;
public int getValue()
{
return value;
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
}
Клас ChessItem
оголошено як абстрактний: він реалізував усі успадковані методи, крім draw
. Тобто клас ChessItem
містить один абстрактний метод — draw()
.
Власне, між словами extends
та implements
різниці немає: обидва слова означають успадкування. Так було зроблено, щоб код був зрозумілішим. Англійською мовою кажуть, що класи успадковуються (extends
), а інтерфейси реалізуються (implements
).
4. Змінні
І найважливіше: в інтерфейсах не можна оголошувати змінні (проте статичні — можна).
А навіщо ж потрібні інтерфейси? Коли їх використовують? Інтерфейси порівняно з класами мають дві суттєві переваги, які розглянемо далі.
2. Відокремлення «опису методів» від їх реалізації.
Раніше ми вже розповідали, що коли ви хочете дозволити виклик методів свого класу з інших класів, то їх потрібно позначити ключовим словом public
. А коли ви хочете, щоб певні методи можна було викликати тільки з цього самого класу, їх потрібно позначити ключовим словом private
. Інакше кажучи, ми поділяємо методи класу на дві категорії: «для всіх» і «тільки для своїх».
За допомогою інтерфейсів цей поділ можна підсилити. Ми зробимо один спеціальний «клас для всіх» і другий «клас для своїх», який успадкуємо від першого. Це матиме такий вигляд:
Було | Стало |
---|---|
|
|
|
|
Ми розділили наш клас на дві частини: інтерфейс і клас, успадкований від інтерфейсу. Які ж переваги ми отримали?
Той самий інтерфейс можуть реалізовувати (успадковувати) різні класи, і кожен із них може мати свою поведінку. Так само, як ArrayList
і LinkedList
— це дві різні реалізації інтерфейсу List
.
У такий спосіб ми приховуємо не тільки різні реалізації, але й самі класи, що їх містять (у коді скрізь може фігурувати тільки інтерфейс). Це дозволяє дуже гнучко, безпосередньо в процесі виконання програми, підміняти одні об'єкти іншими, змінюючи поведінку об'єкта потай від усіх класів, які його використовують.
Це дуже потужна технологія в поєднанні з поліморфізмом. Зараз вам, мабуть, зовсім не очевидно, навіщо так робити. Щоб зрозуміти, як інтерфейси істотно спрощують життя, вам спочатку потрібно попрацювати з програмами, що складаються з десятків і сотень класів.
3. Множинне успадкування
У Java в кожного класу може бути тільки один батьківський клас. В інших мовах програмування класи часто можуть мати декілька батьківських класів. Це дуже зручно, але водночас створює чимало проблем.
У Java дійшли компромісу: заборонили множинне успадкування класів, але дозволили множинне успадкування інтерфейсів. Інтерфейс може мати декілька інтерфейсів-батьків. Клас може мати декілька інтерфейсів-батьків, але батьківський клас може бути тільки один.
Чому ж для класів множинне успадкування заборонили, а для інтерфейсів дозволили? Причиною є так зване пірамідальне успадкування:
Коли клас B успадковується від класу A, йому нічого не відомо про класи C і D. Тому він використовує змінні класу A так, як вважає за потрібне. Клас C робить те саме: використовує змінні класу A, але вже в інший спосіб. А в класі D це все призводить до конфлікту.
Розгляньмо простий приклад. Припустімо, у нас є 3 класи:
class Data
{
protected int value;
}
class XCoordinate extends Data
{
public void setX (int x) { value = x;}
public int getX () { return value;}
}
class YCoordinate extends Data
{
public void setY (int y) { value = y;}
public int getY () { return value;}
}
Клас Data зберігає змінну value
. Його клас-спадкоємець XCoordinate
використовує її для зберігання змінної X
, а клас-спадкоємець YCoordinate
— для зберігання змінної Y
.
І це працює — окремо. Але якщо ми захочемо успадкувати клас XYCoordinates
від обох класів — XCoordinate
і YCoordinate
— отримаємо непрацюючий код. У цього класу будуть методи його класів-предків, але працювати вони будуть неправильно, тому що змінна value
в них одна.
А оскільки інтерфейсам заборонено мати змінні, то вони не матимуть таких конфліктів. Тому множинне успадкування інтерфейсів дозволено.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ