J. Блох в своей Эффективной Java, написанный для Java 6
, упомянул следующее (Пункт 17):
Если вы считаете, что вы должны разрешить наследование от такого класса, разумный подход заключается в том, чтобы гарантировать, что класс никогда не вызывает какой-либо из его переопределяемые методы и документировать этот факт. Другими словами, полностью исключает самосохранение классов переориентированными методами.
Пункт 18:
Если вы используете абстрактные классы для определения типов, вы оставляете программиста который хочет добавить функциональность без альтернативы, но использовать наследование. Полученные классы менее мощные и более хрупкие чем классы-оболочки.
Пока интерфейсам не разрешено содержать реализации методов, использование интерфейсов для определения типов не мешает вам предоставлять помощь в реализации для программистов.
Теперь в Java 8
с его реализацией по умолчанию (с использованием других методов в интерфейсе) интерфейсы опасны для наследования.
Например:
public inteface MyInterface{
public void compute(Object o);
public default void computeAll(List<Object> oo){
for(Object o: oo)
compute(o); //self-use
}
}
Итак, по словам Дж. Блоха, это может вызвать некоторые проблемы, когда мы пытаемся реализовать интерфейс, потому что:
-
Переопределение методов, подобных этому (похоже на то, что предоставил J.Bloch):
public class MyInterfaceCounter implements MyInterface{ private int count = 0; @Override public void compute(Object o) { count++; } @Override public void computeAll(List<Object> oo){ count += oo.size(); //Damn!! MyInterface.super.computeAll(oo); } }
-
Клиент получает доступ к внутренним интерфейсам, т.е. должен знать о реализации по умолчанию.
Что с ним делать в Java 8? Применяются ли правила от Эффективной Java?
Кроме того, мы не можем объявить метод по умолчанию как final
(как это можно сделать для классов, это сделало бы самоиспользование не слишком опасным для переопределений).