JavaRush /Курси /JAVA 25 SELF /Default-методи в інтерфейсах

Default-методи в інтерфейсах

JAVA 25 SELF
Рівень 21 , Лекція 2
Відкрита

1. Вступ

Колись давно (до Java 8) інтерфейс був дуже суворим: у ньому можна було оголошувати лише абстрактні методи (без реалізації) та константи (public static final). Це було зручно, доки не постала одна велика проблема — розвиток бібліотек.

Уявіть ситуацію

Ви розробили популярну бібліотеку, у якій є інтерфейс:

public interface Movable {
    void move(int x, int y);
}

Тисячі програмістів у всьому світі пишуть свої класи, що реалізовують цей інтерфейс. За кілька років ви розумієте, що всім бракує методу reset(), який повертає об’єкт у початкову позицію. Ви додаєте до інтерфейсу:

public interface Movable {
    void move(int x, int y);
    void reset();
}

І тут починається апокаліпсис: усі проєкти, які використовують ваш інтерфейс, перестають компілюватися! Адже тепер вони зобов’язані реалізувати новий метод, а про нього ніхто не знав. Міграція перетворюється на біль.

Default-методи — рішення!

Java 8 запровадила default-методи: тепер можна додати метод із реалізацією просто до інтерфейсу. Усі наявні класи автоматично отримують типовий варіант реалізації, і їхній код не ламається. А якщо хочете — можете перевизначити метод по‑своєму.

2. Синтаксис default-методів

Default-метод — це звичайний метод із реалізацією всередині інтерфейсу, позначений ключовим словом default.

public interface Movable {
    void move(int x, int y);

    default void reset() {
        // Типова реалізація: повертаємося на початок координат
        move(0, 0);
    }
}

Пояснення:

  • Усі методи інтерфейсу за замовчуванням public і abstract, але default-методи — не абстрактні, а мають тіло.
  • Ключове слово default завжди пишеться перед типом, що повертає метод.

Як це виглядає в класі?

public class Robot implements Movable {
    private int x, y;

    @Override
    public void move(int x, int y) {
        this.x = x;
        this.y = y;
        System.out.println("Робот перемістився в (" + x + ", " + y + ")");
    }

    // reset() реалізовувати необов’язково — працюватиме default-версія!
}

Тепер, якщо ми викличемо reset() в об’єкта Robot, спрацює реалізація з інтерфейсу Movable:

public class Main {
    public static void main(String[] args) {
        Movable robot = new Robot();
        robot.move(10, 20); // Робот перемістився в (10, 20)
        robot.reset();      // Робот перемістився в (0, 0)
    }
}

3. Default-методи у стандартній бібліотеці

Default-методи додали не просто так, а щоб дати змогу розвивати великі стандартні інтерфейси Java без ламання старого коду.

Приклад: інтерфейс List (Java 8+)

У Java 8 до інтерфейсу List додали методи з реалізацією, наприклад, forEach, replaceAll, sort:

default void forEach(Consumer<Entity> action) {
    for (Entity e : this) {
        action.accept(e);
    }
}

Якщо ви реалізуєте свій список і не перевизначили forEach, він усе одно працюватиме — завдяки default-методу.

Детальніше про дженерики (Consumer<Entity>) ви дізнаєтеся на 26-му рівні :P.

4. Навіщо потрібні default-методи?

  • Розвиток API без ламання коду: можна додавати нові методи в інтерфейс без необхідності реалізовувати їх у всіх наявних класах.
  • Універсальні шаблони поведінки: можна оголосити поведінку за замовчуванням, щоб класи могли її використовувати або перевизначати.
  • Зменшення дублювання: якщо поведінка однакова для більшості реалізацій — не потрібно копіювати код у кожен клас.

Аналогія

Уявіть, що у вас є договір оренди квартири (інтерфейс). У ньому раніше було написано: «Орендар зобов’язаний платити за воду». Потім додали: «Орендар зобов’язаний платити за електроенергію». Якби не default-методи, вам довелося б переписати всі договори з усіма орендарями! А з default-методами — просто додали пункт, і якщо комусь потрібно — вони можуть домовитися по‑своєму.

5. Обмеження та особливості default-методів

Default-методи не можуть перевизначати методи класу Object

Ви не можете оголосити в інтерфейсі default-метод із сигнатурою, що збігається з equals, hashCode або toString з класу Object. Це захист від плутанини: адже будь-який об’єкт у Java вже має ці методи.

// Помилка компіляції!
interface Broken {
    default boolean equals(Object obj) { return false; }
}

Конфлікти default-методів

Що, якщо клас реалізує два інтерфейси, у кожному з яких є default-метод з однаковою сигнатурою? Компілятор Java чесно скаже: «Вирішіть самі, я не знаю, що робити!»

interface A {
    default void hello() { System.out.println("Hello from A"); }
}

interface B {
    default void hello() { System.out.println("Hello from B"); }
}

class C implements A, B {
    // Обов’язково розв’язати конфлікт:
    @Override
    public void hello() {
        // Можна обрати, чий метод викликати, або реалізувати свій
        A.super.hello(); // або B.super.hello();
    }
}

Якщо не реалізувати hello() у класі C, буде помилка компіляції.

Default-методи можуть викликати інші методи інтерфейсу

Default-метод може викликати інші методи інтерфейсу, навіть абстрактні. Головне — щоб реалізація була в класі.

interface Printer {
    void print(String text);

    default void printTwice(String text) {
        print(text);
        print(text);
    }
}

6. Приклад: розвиваємо застосунок із default-методом

Подивімося на приклад використання default-методів в інтерфейсі Movable:

public interface Movable {
    void move(int x, int y);

    default void reset() {
        move(0, 0);
    }
}

Є клас Robot, що реалізує цей інтерфейс:

public class Robot implements Movable {
    private int x = 5;
    private int y = 7;

    @Override
    public void move(int x, int y) {
        this.x = x;
        this.y = y;
        System.out.println("Робот перемістився в (" + x + ", " + y + ")");
    }

    // reset() не реалізуємо — використовуємо default-метод!
}

Тепер спробуймо викликати обидва методи:

public class Main {
    public static void main(String[] args) {
        Movable robot = new Robot();
        robot.move(10, 20); // Робот перемістився в (10, 20)
        robot.reset();      // Робот перемістився в (0, 0)
    }
}

Якщо захочемо, щоб Robot скидався якось по-особливому — просто перевизначимо reset() у класі:

@Override
public void reset() {
    System.out.println("Робот вимикається й повертається на базу!");
    move(0, 0);
}

7. Default-методи й множинна реалізація інтерфейсів

Default-методи особливо корисні, коли клас реалізує кілька інтерфейсів. Але є нюанс: якщо обидва інтерфейси мають default-метод з однаковою сигнатурою, компілятор вимагатиме явного розв’язання конфлікту.

Приклад конфлікту

interface A {
    default void show() { System.out.println("A"); }
}
interface B {
    default void show() { System.out.println("B"); }
}
class C implements A, B {
    @Override
    public void show() {
        // Явно обираємо, чий default-метод використовувати
        A.super.show(); // або B.super.show();
    }
}

8. Схема: як працює виклик default-методу


+-------------------+
|   Movable         |
|-------------------|
| +move(int, int)   | <- абстрактний метод
| +reset()          | <- default-метод
+-------------------+
         ^
         |
+-------------------+
|   Robot           |
|-------------------|
| +move(int, int)   | <- реалізує
|                   | (reset() не реалізує)
+-------------------+
         |
     Виклик reset()
         |
   Використовується реалізація
   з інтерфейсу Movable
Виклик default-методу: реалізація за замовчуванням з інтерфейсу

9. Типові помилки під час роботи з default-методами

Помилка № 1: спроба зробити default-метод без реалізації.
Default-метод зобов’язаний мати тіло! Якщо ви напишете default void foo();, компілятор одразу скаже: «Ви що, забули фігурні дужки?»

Помилка № 2: конфлікт default-методів із різних інтерфейсів.
Якщо клас реалізує два інтерфейси з однаковим default-методом, ви зобов’язані розв’язати конфлікт явно — інакше компілятор не дасть скомпілювати код.

Помилка № 3: спроба оголосити default-метод із сигнатурою методу з Object.
Не можна зробити default-метод equals, hashCode або toString в інтерфейсі — лише абстрактні методи з такими іменами.

Помилка № 4: забули, що default-методи — це не «магія», а просто зручний інструмент.
Default-методи не скасовують принцип, що інтерфейс — це контракт. Якщо поведінка за замовчуванням не підходить — завжди перевизначайте default-метод у класі.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ