JavaRush /Java блог /Random UA /Методи за замовчуванням у Java 8: що можуть і чого не мож...
Spitfire
33 рівень

Методи за замовчуванням у Java 8: що можуть і чого не можуть?

Стаття з групи Random UA
Переклад статті , написаної Peter Verhas від квітня 2014 року. Методи за замовчуванням у Java 8: що можуть і чого не можуть?  - 1Від перекладача: термін " default method " в Java тільки з'явився і я не впевнений, чи є для нього усталений переклад російською. Я використовуватиму термін "метод за умовчанням", хоча й не вважаю його ідеальним. Запрошую до обговорення щодо вдалого перекладу.

Що таке метод за замовчуванням

Тепер, з виходом Java 8, можна додавати нові методи в інтерфейси так, що цей інтерфейс залишається сумісним з класами, які його реалізують. Це дуже важливо, якщо ви розробляєте бібліотеку, яку використовують багато програмістів від Києва до Нью-Йорка. До виходу Java 8, якщо ви описали інтерфейс у бібліотеці, ви не могли додавати до нього методи не ризикуючи, що будь-яка програма, що працює з вашим інтерфейсом, не зламається при його оновленні. Так що в Java 8 цього можна більше не боятися? Ні, не можна. Додавання методу за замовчуванням до інтерфейсу може призвести до неможливості використання деяких класів. Давайте спочатку подивимося на добрі сторони методів за промовчанням. У Java 8 метод можна реалізувати безпосередньо в інтерфейсі. (Статичні методи в інтерфейсі тепер теж можна реалізовувати, але це інша історія.) Метод, реалізований в інтерфейсі, називається за замовчуванням і позначається ключовим словом default . Якщо клас реалізує інтерфейс, може, але з зобов'язаний, реалізувати методи, реалізовані в интерфейсе. Клас успадковує реалізацію за умовчанням. Ось чому не обов'язково модифікувати класи за зміни інтерфейсу, який вони реалізують.

Множинне успадкування?

Все ускладнюється, якщо якийсь клас реалізує більше одного (скажімо, два) інтерфейсу, а вони реалізують один і той самий метод за умовчанням. Який із методів успадкує клас? Відповідь - ніяка. У такому разі клас повинен реалізувати метод самостійно (безпосередньо, або успадкувавши його від іншого класу). Ситуація аналогічна, якщо тільки один інтерфейс має метод за замовчуванням, а в іншому цей метод є абстрактним. Java 8 намагається бути дисциплінованою та уникати неоднозначних ситуацій. Якщо методи оголошені більш ніж в одному інтерфейсі, то жодної реалізації за умовчанням класом не успадковується – ви отримаєте помилку компіляції. Хоча помилку компіляції можна і не отримати, якщо ваш клас вже скомпільований. У цьому плані Java 8 недостатньо стійка. На те є свої причини, в обговорення яких я не хочу вдаватися (наприклад:
  • Скажімо, у вас є два інтерфейси, і клас реалізує їх обидва.
  • Один із інтерфейсів реалізує метод за замовчуванням m().
  • Ви компілюєте всі інтерфейси та клас.
  • Ви змінюєте інтерфейс, у якому немає методу m(), оголошуючи його як абстрактний метод.
  • Компілюєте лише модифікований інтерфейс.
  • Запускаєте клас.
Методи за замовчуванням у Java 8: що можуть і чого не можуть?  - 2І тут клас працює. Ви не можете скомпілювати його з оновленими інтерфейсами, але він був скомпільований зі старими версіями і тому працює. Тепер
  • змініть інтерфейс з абстрактним методом m() та додайте реалізацію за умовчанням.
  • Скомпілюйте модифікований інтерфейс.
  • Запустіть клас: помилка.
Коли є два інтерфейси, що надають реалізацію методу за замовчуванням, цей метод не може бути викликаний у класі, якщо він не реалізований самим класом (знов-таки, самостійно, або успадкований від іншого класу). Методи за замовчуванням у Java 8: що можуть і чого не можуть?  - 3Клас сумісний. Він може бути завантажений із модифікованим інтерфейсом. Він може навіть працювати доти, доки не буде викликаний метод, що має реалізацію за умовчанням в обох інтерфейсах.

Приклад коду

Методи за замовчуванням у Java 8: що можуть і чого не можуть?  - 4Для того, щоб продемонструвати вищесказане, я створив тестовий каталог для класу C.java і 3 підкаталоги для інтерфейсів у файлух I1.java та I2.java. Кореневий каталог тесту містить вихідний код класу C.java. Каталог base містить версію інтерфейсів, які підходять до виконання та компіляції: інтерфейс I1 має метод за замовчуванням m(); Інтерфейс I2 поки не має жодних методів. У класі є метод main, тому ми можемо виконати його для перевірки. Він перевіряє, чи є якісь аргументи командного рядка, тому ми легко можемо виконати його як з викликом, так і без виклику методу m().
~/github/test$ cat C.java
public class C implements I1, I2 {
  public static void main(String[] args) {
    C c = new C();
    if( args.length == 0 ){
      c.m();
    }
  }
}
~/github/test$ cat base/I1.java
public interface I1 {
  default void m(){
    System.out.println("hello interface 1");
  }
}
~/github/test$ cat base/I2.java
public interface I2 {
}
Можна скомпілювати та виконати клас із командного рядка.
~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
hello interface 1
Каталог compatible містить версію інтерфейсу I2, який оголошує метод m() абстрактним, а також з технічних причин не змінену копію I1.java.
~/github/test$ cat compatible/I2.java

public interface I2 {
  void m();
}
Такий набір не можна використовувати для компіляції класу C:
~/github/test$ javac -cp .:compatible C.java
C.java:1: error: C is not abstract and does not override abstract method m() in I2
public class C implements I1, I2 {
       ^
1 error
Повідомлення про помилку є дуже точним. Тим не менш, у нас є C.class з попередньої компіляції і, якщо ми скомпілюємо інтерфейси до каталогу compatible, у нас буде два інтерфейси , які все ще можна використовувати для запуску класу:
~/github/test$ javac compatible/I*.java
~/github/test$ java -cp .:compatible C
hello interface 1
Третій каталог — wrongмістить версію I2, в якому також оголошено метод m():
~/github/test$ cat wrong/I2.java
public interface I2 {
  default void m(){
    System.out.println("hello interface 2");
  }
}
Можна навіть не мучитися з компіляцією. Незважаючи на те, що метод оголошений двічі, клас все ще може використовуватися і працювати, доки не відбудеться виклик методу m(). Ось навіщо нам потрібен аргумент командного рядка:
~/github/test$ javac wrong/*.java
~/github/test$ java -cp .:wrong C
Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: I1.m I2.m
    at C.m(C.java)
    at C.main(C.java:5)
~/github/test$ java -cp .:wrong C x
~/github/test$

Висновок

Коли ви переносите вашу бібліотеку в Java 8 і змінюєте ваші інтерфейси, додаючи в них методи за промовчанням, швидше за все, проблем у вас не виникне. У всякому разі, на це сподіваються розробники бібліотек Java 8, додаючи функціональність. Програми, які використовують вашу бібліотеку, поки використовують її для Java 7, де немає методів за промовчанням. Якщо кілька бібліотек використовують разом, ймовірність конфлікту є. Як його уникнути? Проектуйте API вашої бібліотеки так само, як і раніше. Не розслабляйтеся, покладаючись на методи за замовчуванням. Вони – це крайній засіб. Ретельно вибирайте імена, щоб уникнути колізій з іншими інтерфейсами. Подивимося, як розвиватиметься розробка для Java з використанням цієї фічі.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ