Переклад статті , написаної Peter Verhas від квітня 2014 року. Від перекладача: термін " default method " в Java тільки з'явився і я не впевнений, чи є для нього усталений переклад російською. Я використовуватиму термін "метод за умовчанням", хоча й не вважаю його ідеальним. Запрошую до обговорення щодо вдалого перекладу.
Що таке метод за замовчуванням
Тепер, з виходом Java 8, можна додавати нові методи в інтерфейси так, що цей інтерфейс залишається сумісним з класами, які його реалізують. Це дуже важливо, якщо ви розробляєте бібліотеку, яку використовують багато програмістів від Києва до Нью-Йорка. До виходу Java 8, якщо ви описали інтерфейс у бібліотеці, ви не могли додавати до нього методи не ризикуючи, що будь-яка програма, що працює з вашим інтерфейсом, не зламається при його оновленні. Так що в Java 8 цього можна більше не боятися? Ні, не можна. Додавання методу за замовчуванням до інтерфейсу може призвести до неможливості використання деяких класів. Давайте спочатку подивимося на добрі сторони методів за промовчанням. У Java 8 метод можна реалізувати безпосередньо в інтерфейсі. (Статичні методи в інтерфейсі тепер теж можна реалізовувати, але це інша історія.) Метод, реалізований в інтерфейсі, називається за замовчуванням і позначається ключовим словом default . Якщо клас реалізує інтерфейс, може, але з зобов'язаний, реалізувати методи, реалізовані в интерфейсе. Клас успадковує реалізацію за умовчанням. Ось чому не обов'язково модифікувати класи за зміни інтерфейсу, який вони реалізують.Множинне успадкування?
Все ускладнюється, якщо якийсь клас реалізує більше одного (скажімо, два) інтерфейсу, а вони реалізують один і той самий метод за умовчанням. Який із методів успадкує клас? Відповідь - ніяка. У такому разі клас повинен реалізувати метод самостійно (безпосередньо, або успадкувавши його від іншого класу). Ситуація аналогічна, якщо тільки один інтерфейс має метод за замовчуванням, а в іншому цей метод є абстрактним. Java 8 намагається бути дисциплінованою та уникати неоднозначних ситуацій. Якщо методи оголошені більш ніж в одному інтерфейсі, то жодної реалізації за умовчанням класом не успадковується – ви отримаєте помилку компіляції. Хоча помилку компіляції можна і не отримати, якщо ваш клас вже скомпільований. У цьому плані Java 8 недостатньо стійка. На те є свої причини, в обговорення яких я не хочу вдаватися (наприклад:- Скажімо, у вас є два інтерфейси, і клас реалізує їх обидва.
- Один із інтерфейсів реалізує метод за замовчуванням m().
- Ви компілюєте всі інтерфейси та клас.
- Ви змінюєте інтерфейс, у якому немає методу m(), оголошуючи його як абстрактний метод.
- Компілюєте лише модифікований інтерфейс.
- Запускаєте клас.
- змініть інтерфейс з абстрактним методом m() та додайте реалізацію за умовчанням.
- Скомпілюйте модифікований інтерфейс.
- Запустіть клас: помилка.
Приклад коду
Для того, щоб продемонструвати вищесказане, я створив тестовий каталог для класу 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$
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ