Перевод статьи, написанной Peter Verhas от апреля 2014 года.
От переводчика: термин "default method" в Java только появился и я не уверен, есть ли для него устоявшийся перевод на русский. Я буду использовать термин "метод по умолчанию", хотя и не считаю его идеальным. Приглашаю к обсуждению относительно более удачного перевода.
В этом случае класс работает. Вы не можете его скомпилировать с обновленными интерфейсами, но он был скомпилирован со старыми версиями и потому работает. Теперь
Класс совместим. Он может быть загружен с модифицированным интерфейсом. Он может даже работать до тех пор, пока не будет вызван метод, имеющий реализацию по умолчанию в обоих интерфейсах.
Для того, что бы продемонстрировать вышесказанное, я создал тестовый каталог для класс C.java и 3 подкаталога для интерфейсов в файлах I1.java и I2.java. Корневой каталог для теста содержит исходный код класса C.java. Каталог base содержит версию интерфейсов, которые подходят для выполнения и компиляции: интерфейс I1 имеет метод по умолчанию m(); интерфейс I2 пока не имеет никаких методов.
В классе есть метод

Что такое метод по умолчанию
Теперь, с выходом Java 8, можно добавлять в интерфейсы новые методы так, что этот интерфейс остается совместимым с классами, которые его реализуют. Это очень важно, если вы разрабатываете библиотеку, которую используют множество программистов от Киева до Нью-Йорка. До выхода Java 8, если вы описали интерфейс в библиотеке, вы не могли добавлять в него методы не рискуя, что какое-нибудь приложение, работающее с вашим интерфейсом, не сломается при его обновлении. Так что, в Java 8 этого можно больше не бояться? Нет, нельзя. Добавление метода по умолчанию в интерфейс может привести к невозможности использования некоторых классов. Давайте сначала посмотрим на хорошие стороны методов по умолчанию. В Java 8 метод можно реализовать прямо в интерфейсе. (Статические методы в интерфейсе теперь тоже можно реализовывать, но это другая история.) Метод, реализованный в интерфейсе, называется методом по умолчанию и обозначается ключевым словом default. Если класс реализует интерфейс, он может, но не обязан, реализовать методы, реализованные в интерфейсе. Класс наследует реализацию по умолчанию. Вот почему не обязательно модифицировать классы при изменении интерфейса, который они реализуют.Множественное наследование?
Все усложняется, если некий класс реализует более одного (скажем, два) интерфейса, а они реализуют один и тот же самый метод по умолчанию. Какой из методов унаследует класс? Ответ — никакой. В таком случае класс должен реализовать метод самостоятельно (напрямую, либо унаследовав его от другого класса). Ситуация аналогична, если только один интерфейс имеет метод по умолчанию, а в другом этот же метод является абстрактным. Java 8 старается быть дисциплинированной и избегать неоднозначных ситуаций. Если методы объявлены более чем в одном интерфейсе, то никакой реализации по умолчанию классом не наследуется - вы получите ошибку компиляции. Хотя, ошибку компиляции можно и не получить, если ваш класс уже скомпилирован. В этом отношении Java 8 недостаточно стойка. На то есть свои причины, в обсуждение которых я не хочу вдаваться (например: релиз Java уже вышел и время для дискуссий уже давно прошло и вообще, здесь для них не место).- Скажем, у вас есть два интерфейса, и класс реализует их оба.
- Один из интерфейсов реализует метод по умолчанию m().
- Вы компилируете все интерфейсы и класс.
- Вы меняете интерфейс, в котором нет метода m(), объявляя его как абстрактный метод.
- Компилируете только модифицированный интерфейс.
- Запускаете класс.

- измените интерфейс с абстрактным методом m() и добавьте реализацию по умолчанию.
- Скомпилируйте модифицированный интерфейс.
- Запустите класс: ошибка.

Пример кода

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$
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
В целом познавательная статья. Спасибо за труды.