1. Введение
После введения методов по умолчанию
в интерфейсы Java казалось, что больше нет никакой разницы между интерфейсом и абстрактным классом. Но это не так — между ними есть некоторые принципиальные различия.
В этом уроке мы более подробно рассмотрим интерфейс и абстрактный класс, чтобы увидеть, чем они отличаются.
2. Зачем использовать метод по умолчанию?
Цель метода по умолчанию
— предоставить внешние функции без нарушения существующих реализаций. Первоначальной причиной введения метода по умолчанию
было обеспечение обратной совместимости с Collection Framework с новыми лямбда-функциями.
3. Интерфейс с методом по умолчанию
против абстрактного класса
Рассмотрим основные принципиальные отличия.
3.1. Состояние
Абстрактный класс может иметь состояние, а его методы могут обращаться к состоянию реализации . Хотя методы по умолчанию
разрешены в интерфейсе, они не могут получить доступ к состоянию реализации.
Любая логика, которую мы пишем в методе по умолчанию
, должна относиться к другим методам интерфейса — эти методы не будут зависеть от состояния объекта .
Допустим, мы создали абстрактный класс CircleClass
, который содержит String
, color
, для представления состояния объекта CircleClass
:
public abstract class CircleClass {
private String color;
private List<String> allowedColors = Arrays.asList("RED", "GREEN", "BLUE");
public boolean isValid() {
if (allowedColors.contains(getColor())) {
return true;
} else {
return false;
}
}
//standard getters and setters
}
В приведенном выше абстрактном
классе у нас есть неабстрактный метод isValid()
для проверки объекта CircleClass
на основе его состояния. Метод isValid()
может получить доступ к состоянию объекта CircleClass
и проверить экземпляр CircleClass
на основе разрешенных цветов. Благодаря такому поведению мы можем написать любую логику в методе абстрактного класса на основе состояния объекта .
Давайте создадим простой класс реализации CircleClass
:
public class ChildCircleClass extends CircleClass {
}
Теперь давайте создадим экземпляр и проверим цвет:
CircleClass redCircle = new ChildCircleClass();
redCircle.setColor("RED");
assertTrue(redCircle.isValid());
Здесь мы видим, что когда мы помещаем допустимый цвет в объект CircleClass
и вызываем метод isValid()
, внутри метод isValid()
может получить доступ к состоянию объекта CircleClass
и проверить, содержит ли экземпляр допустимый цвет или нет. .
Попробуем сделать нечто подобное, используя интерфейс с методом по умолчанию
:
public interface CircleInterface {
List<String> allowedColors = Arrays.asList("RED", "GREEN", "BLUE");
String getColor();
public default boolean isValid() {
if (allowedColors.contains(getColor())) {
return true;
} else {
return false;
}
}
}
Как мы знаем, у интерфейса не может быть состояния, поэтому метод по умолчанию
не может получить доступ к состоянию.
Здесь мы определили метод getColor()
для предоставления информации о состоянии. Дочерний класс переопределит метод getColor()
, чтобы предоставить состояние экземпляра во время выполнения:
public class ChidlCircleInterfaceImpl implements CircleInterface {
private String color;
@Override
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
Давайте создадим экземпляр и проверим цвет:
ChidlCircleInterfaceImpl redCircleWithoutState = new ChidlCircleInterfaceImpl();
redCircleWithoutState.setColor("RED");
assertTrue(redCircleWithoutState.isValid());
Как мы видим здесь, мы переопределяем метод getColor()
в дочернем классе, чтобы метод по умолчанию
проверял состояние во время выполнения.
3.2. Конструкторы
Абстрактные классы могут иметь конструкторы , что позволяет нам инициализировать состояние при создании . Интерфейсы, конечно же, не имеют конструкторов.
3.3. Синтаксические различия
Кроме того, есть несколько различий в синтаксисе. Абстрактный класс может переопределить методы класса Object
, а интерфейс — нет.
Абстрактный класс может объявлять переменные экземпляра со всеми возможными модификаторами доступа, и к ним можно получить доступ в дочерних классах. Интерфейс может иметь только переменные public,
static
и final
и не может иметь никаких переменных экземпляра.
Кроме того, абстрактный класс может объявлять экземпляры и статические блоки , тогда как интерфейс не может иметь ни того, ни другого.
Наконец, абстрактный класс не может ссылаться на лямбда-выражение , а интерфейс может иметь единственный абстрактный метод, который может ссылаться на лямбда-выражение.
4. Вывод
В этой статье показана разница между абстрактным классом и интерфейсом с методом по умолчанию .
Мы также увидели, какой из них лучше всего подходит для нашего сценария.
Когда это возможно, мы всегда должны выбирать интерфейс с методом по умолчанию
, потому что это позволяет нам расширить класс, а также реализовать интерфейс .
Как обычно, все примеры кода, показанные в этой статье, доступны на GitHub .