Привіт! Сьогодні поговоримо про важливе поняття в Java — інтерфейси. Слово тобі напевно знайоме. Наприклад, інтерфейси є у більшості комп'ютерних програм і ігор. У широкому сенсі інтерфейс — це певний «пульт», що пов'язує дві взаємодійні сторони одна з одною. Простий приклад інтерфейсу з повсякденного життя — пульт від телевізора. Він пов'язує два об'єкти, людину і телевізор, і виконує різні завдання: додати або зменшити звук, переключити канали, увімкнути або вимкнути телевізор. Одній стороні (людині) потрібно звернутися до інтерфейсу (натиснути на кнопку пульта), щоб друга сторона виконала дію. Наприклад, щоб телевізор переключив канал на наступний. Причому користувачу не обов'язково знати пристрій телевізора і те, як усередині нього реалізований процес зміни каналу. Для чого в Java потрібні інтерфейси - 1Все, до чого користувач має доступ — це інтерфейс. Головне завдання — отримати потрібний результат. Як це має відношення до програмування та Java? Пряме :) Створення інтерфейсу дуже схоже на створення звичайного класу, тільки замість слова class ми вказуємо слово interface. Давай подивимося на найпростіший Java-інтерфейс і розберемося, як він працює і для чого потрібен:

public interface Swimmable {

     public void swim();
}
Ми створили інтерфейс Swimmable — « той, що вміє плавати». Це щось на кшталт нашого пульта, у якого є одна «кнопка»: метод swim() — «плисти». Як же нам цей «пульт» використовувати? Для цього метод, тобто кнопку нашого пульта, потрібно імплементувати. Щоб використовувати інтерфейс, його методи повинні реалізувати якісь класи нашої програми. Давай придумаємо клас, об'єкти якого підходять під опис «той, що вміє плавати». Наприклад, підійде клас качки — Duck:

public class Duck implements Swimmable {
    
    public void swim() {
        System.out.println("Качечка, пливи!");
    }
    
    public static void main(String[] args) {
        
        Duck duck = new Duck();
        duck.swim();
    }
}
Що ж ми тут бачимо? Клас Duck «зв'язується» з інтерфейсом Swimmable за допомогою ключового слова implements. Якщо пам'ятаєш, ми використовували схожий механізм для зв'язку двох класів у наслідуванні, тільки там було слово «extends». «public class Duck implements Swimmable» можна для зрозумілості перевести дослівно: «публічний клас Duck реалізує інтерфейс Swimmable». Це означає, що клас, пов'язаний з якимось інтерфейсом, повинен реалізувати всі його методи. Зверни увагу: у нашому класі Duck прямо як у інтерфейсі Swimmable є метод swim(), і всередині нього міститься якась логіка. Це обов'язкове вимога. Якби ми просто написали «public class Duck implements Swimmable» і не створили б метод swim() у класі Duck, компілятор видав би нам помилку: Duck is not abstract and does not override abstract method swim() in Swimmable Чому так відбувається? Якщо пояснити помилку на прикладі з телевізором, вийде, що ми даємо людині в руки пульт з кнопкою «переключити канал» від телевізора, який не вміє переключати канали. Тут вже натискай на кнопку скільки завгодно, нічого не запрацює. Пульт сам собою не переключає канали: він лише дає сигнал телевізору, всередині якого реалізований складний процес зміни каналу. Так і з нашою качкою: вона повинна вміти плавати, щоб до неї можна було звернутися за допомогою інтерфейсу Swimmable. Якщо вона цього не вміє, інтерфейс Swimmable не зв’яже дві сторони — людину і програму. Людина не зможе використовувати метод swim(), щоб змусити об’єкт Duck всередині програми плисти. Тепер ти побачив наглядніше, для чого потрібні інтерфейси. Інтерфейс описує поведінку, якою повинні володіти класи, що реалізують цей інтерфейс. «Поведінка» — це сукупність методів. Якщо ми хочемо створити кілька месенджерів, найпростіше зробити це, створивши інтерфейс Messenger. Що повинен уміти будь-який месенджер? У спрощеному вигляді, приймати і відправляти повідомлення.

public interface Messenger{

     public void sendMessage();
     
     public void getMessage();
}
І тепер ми можемо просто створювати наші класи-месенджери, імплементуючи цей інтерфейс. Компілятор сам «змусить» нас реалізувати їх всередині класів. Telegram:

public class Telegram implements Messenger {
    
    public void sendMessage() {
        
        System.out.println("Надсилаємо повідомлення в Telegram!");
    }
     
     public void getMessage() {
         System.out.println("Читаємо повідомлення в Telegram!");
     }
}
WhatsApp:

public class WhatsApp implements Messenger {
    
    public void sendMessage() {
        
        System.out.println("Надсилаємо повідомлення в WhatsApp!");
    }
     
     public void getMessage() {
         System.out.println("Читаємо повідомлення в WhatsApp!");
     }
}
Viber:

public class Viber implements Messenger {
    
    public void sendMessage() {
        
        System.out.println("Надсилаємо повідомлення в Viber!");
    }
     
     public void getMessage() {
         System.out.println("Читаємо повідомлення в Viber!");
     }
}
Які переваги це дає? Найголовніше з них — слабка зв’язаність. Уяви, що ми проектуємо програму, в якій у нас будуть зібрані дані клієнтів. У класі Client обов’язково потрібно поле, що вказує, яким саме месенджером користується клієнт. Без інтерфейсів це виглядало б дивно:

public class Client {
    
    private WhatsApp whatsApp;
    private Telegram telegram;
    private Viber viber;
}
Ми створили три поля, але у клієнта запросто може бути всього один месенджер. Просто ми не знаємо який. І щоб не залишитися без зв’язку з клієнтом, доводиться «втиснути» в клас всі можливі варіанти. Отже, один або два з них завжди будуть null, і вони взагалі не потрібні для роботи програми. Замість цього краще використовувати наш інтерфейс:

public class Client {
    
    private Messenger messenger;
}
Це і є приклад «слабкої зв’язаності»! Замість того, щоб вказувати конкретний клас месенджера в класі Client, ми просто згадуємо, що у клієнта є месенджер. Який саме — визначиться в ході роботи програми. Але навіщо нам для цього саме інтерфейси? Чому їх взагалі додали в мову? Запитання гарне і правильне! Того ж результату можна досягти за допомогою звичайного наслідування, так адже? Клас Messenger — батьківський, а Viber, Telegram і WhatsApp — нащадки. Дійсно, можна і так. Але є одна загвоздка. Як ти вже знаєш, множинного спадкування в Java немає. А ось множинна реалізація інтерфейсів — є. Клас може реалізовувати скільки завгодно інтерфейсів. Уяви, що у нас є клас Smartphone, у якого є поле Application — встановлене на смартфоні додаток.

public class Smartphone {
    
    private Application application;
}
Додаток та месенджер, звичайно, схожі, але все-таки це різні речі. Месенджер може бути і мобільним, і десктопним, в той час як Application — це саме мобільний додаток. Так ось, якби ми використовували спадкування, не змогли б додати об'єкт Telegram у клас Smartphone. Адже клас Telegram не може спадкувати одночасно від Application і від Messenger! А ми вже встигли успадкувати його від Messenger, і в такому вигляді додати у клас Client. Але ось реалізувати обидва інтерфейси клас Telegram запросто може! Тому в класі Client ми зможемо впровадити об'єкт Telegram як Messenger, а до класу Smartphone — як Application. Ось як це робиться:

public class Telegram implements Application, Messenger {
    
    //...методи
}

public class Client {
    
    private Messenger messenger;
    
    public Client() {
        this.messenger = new Telegram();
    }
}


public class Smartphone {
    
    private Application application;
    
    public Smartphone() {
        this.application = new Telegram();
    }
}
Тепер ми використовуємо клас Telegram як хочемо. Десь він буде виступати в ролі Application, десь — у ролі Messenger. Напевно ти вже звернув увагу, що методи в інтерфейсах завжди «порожні», тобто вони не мають реалізації. Причина цього проста: інтерфейс описує поведінку, а не реалізує його. «Усі об'єкти класів, що імплементують інтерфейс Swimmable, повинні вміти плавати»: ось і все, що говорить нам інтерфейс. Як там конкретно плаватиме риба, качка чи кінь — питання до класів Fish, Duck і Horse, а не до інтерфейсу. Так само як перемикання каналу — завдання телевізора. Пульт просто надає тобі кнопку для цього. Щоправда, у Java8 з'явилося цікаве доповнення — методи за замовчуванням (default method). Наприклад, у твоєму інтерфейсі є 10 методів. 9 з них реалізовані по-різному в різних класах, але один реалізований однаково у всіх. Раніше, до виходу Java8, методи всередині інтерфейсів взагалі не мали реалізації: компілятор одразу видавав помилку. Тепер же можна зробити ось так:

public interface Swimmable {

   public default void swim() {
       System.out.println("Пливи!");
   }
  
   public void eat();
  
   public void run();
}
Використовуючи ключове слово default, ми створили в інтерфейсі метод з реалізацією за замовчуванням. Два других методи, eat() та run(), нам необхідно буде реалізувати самим у всіх класах, які будуть імплементувати Swimmable. З методом swim() цього робити не потрібно: реалізація буде у всіх класах однаковою. До речі, ти вже не раз стикався із інтерфейсами в попередніх завданнях, хоч і не помічав цього сам :) Ось очевидний приклад: Для чого в Java потрібні інтерфейси - 2Ти працював із інтерфейсами List і Set! Точніше, з їх реалізаціями — ArrayList, LinkedList, HashSet та іншими. На цій же схемі видно приклад, коли один клас реалізує відразу кілька інтерфейсів. Наприклад, LinkedList реалізує інтерфейси List і Deque (двостороння черга). Ти знайомий і з інтерфейсом Map, а точніше, з його реалізаціями — HashMap. До речі, на цій схемі ти можеш побачити одну особливість: інтерфейси можуть бути унаследовані один від одного. Інтерфейс SortedMap унаследований від Map, а Deque успадковується від черги Queue. Це потрібно, якщо ти хочеш показати зв'язок інтерфейсів між собою, але при цьому один інтерфейс є розширеною версією іншого. Давай розглянемо приклад з інтерфейсом Queue — черга. Ми поки що не проходили колекції Queue, але вони досить прості і влаштовані як звичайна черга в магазині. Додавати елементи можна тільки в кінець черги, а забирати — тільки з початку. На певному етапі розробникам став потрібний розширений варіант черги, щоб додавати і отримувати елементи можна було з обох сторін. Так створили інтерфейс Deque — двостороння черга. У ньому присутні всі методи звичайної черги, адже вона є «батьком» двосторонньої, але при цьому додано нові методи.