1. Обзор
Java 8 внесла в таблицу несколько совершенно новых функций, включая лямбда-выражения , функциональные интерфейсы , ссылки на методы , потоки , Необязательные и статические и методы по умолчанию в интерфейсах.
Некоторые из них уже были рассмотрены в этой статье . Тем не менее, методы static и default в интерфейсах заслуживают более глубокого рассмотрения сами по себе.
В этой статье мы подробно обсудим как использовать статические и стандартные методы в интерфейсах и рассмотрим некоторые случаи использования, когда они могут быть полезны.
2. Зачем нужны Методы по умолчанию в Интерфейсах
Как и обычные методы интерфейса, методы по умолчанию неявно являются общедоступными — нет необходимости указывать модификатор public .
В отличие от обычных методов интерфейса, они объявляются с ключевым словом по умолчанию в начале сигнатуры метода , и они обеспечивают реализацию .
Давайте рассмотрим простой пример:
public interface MyInterface {
// regular interface methods
default void defaultMethod() {
// default method implementation
}
}Причина, по которой методы default были включены в выпуск Java 8, довольно очевидна.
В типичном дизайне, основанном на абстракциях, где интерфейс имеет одну или несколько реализаций, если к интерфейсу добавляется один или несколько методов, все реализации также будут вынуждены их реализовать. В противном случае конструкция просто сломается.
Методы интерфейса по умолчанию являются эффективным способом решения этой проблемы. Они позволяют нам добавлять новые методы в интерфейс, которые автоматически доступны в реализациях . Таким образом, нет необходимости изменять реализующие классы.
Таким образом, обратная совместимость аккуратно сохраняется без необходимости рефакторинга разработчиков.
3. Методы интерфейса по умолчанию в действии
Чтобы лучше понять функциональность методов интерфейса default , давайте создадим простой пример.
Скажем, что у нас есть наивный Vehicle интерфейс и только одна реализация. Их может быть больше, но давайте оставим все так просто:
public interface Vehicle {
String getBrand();
String speedUp();
String slowDown();
default String turnAlarmOn() {
return "Turning the vehicle alarm on.";
}
default String turnAlarmOff() {
return "Turning the vehicle alarm off.";
}
}И давайте напишем реализующий класс:
public class Car implements Vehicle {
private String brand;
// constructors/getters
@Override
public String getBrand() {
return brand;
}
@Override
public String speedUp() {
return "The car is speeding up.";
}
@Override
public String slowDown() {
return "The car is slowing down.";
}
}
Наконец, давайте определим типичный класс main , который создает экземпляр Car и вызывает его методы:
public static void main(String[] args) {
Vehicle car = new Car("BMW");
System.out.println(car.getBrand());
System.out.println(car.speedUp());
System.out.println(car.slowDown());
System.out.println(car.turnAlarmOn());
System.out.println(car.turnAlarmOff());
}Пожалуйста, обратите внимание, как по умолчанию методы включения сигнализации() и выключения сигнализации() из нашего Транспортного средства интерфейса автоматически доступны в Автомобиле классе .
Кроме того, если в какой-то момент мы решим добавить больше методов default в интерфейс Vehicle , приложение все равно продолжит работать, и нам не придется заставлять класс предоставлять реализации для новых методов.
Наиболее типичным использованием методов по умолчанию в интерфейсах является постепенное предоставление дополнительной функциональности данному типу без разбиения реализующих классов.
Кроме того, они могут быть использованы для предоставления дополнительной функциональности вокруг существующего абстрактного метода :
public interface Vehicle {
// additional interface methods
double getSpeed();
default double getSpeedInKMH(double speed) {
// conversion
}
}4. Правила Наследования Нескольких Интерфейсов
Методы интерфейса по умолчанию-действительно довольно приятная функция, но с некоторыми оговорками, о которых стоит упомянуть. Поскольку Java позволяет классам реализовывать несколько интерфейсов, важно знать, что происходит, когда класс реализует несколько интерфейсов, определяющих одни и те же методы по умолчанию .
Чтобы лучше понять этот сценарий, давайте определим новый Сигнализация интерфейс и рефакторинг Автомобиль класс:
public interface Alarm {
default String turnAlarmOn() {
return "Turning the alarm on.";
}
default String turnAlarmOff() {
return "Turning the alarm off.";
}
}С этим новым интерфейсом, определяющим свой собственный набор методов default , класс Car будет реализовывать как Vehicle , так и Alarm :
public class Car implements Vehicle, Alarm {
// ...
}В этом случае код просто не будет компилироваться, так как существует конфликт, вызванный множественным наследованием интерфейса (он же проблема Diamond ). Класс Car унаследует оба набора методов default . Какие из них следует назвать тогда?
Чтобы устранить эту двусмысленность, мы должны явно предоставить реализацию методов:
@Override
public String turnAlarmOn() {
// custom implementation
}
@Override
public String turnAlarmOff() {
// custom implementation
}Мы также можем заставить наш класс использовать методы по умолчанию одного из интерфейсов .
Давайте рассмотрим пример, в котором используются методы default из интерфейса Vehicle :
@Override
public String turnAlarmOn() {
return Vehicle.super.turnAlarmOn();
}
@Override
public String turnAlarmOff() {
return Vehicle.super.turnAlarmOff();
}
Аналогично, мы можем заставить класс использовать методы default , определенные в интерфейсе Alarm :
@Override
public String turnAlarmOn() {
return Alarm.super.turnAlarmOn();
}
@Override
public String turnAlarmOff() {
return Alarm.super.turnAlarmOff();
}
Кроме того, можно даже заставить класс Car использовать оба набора методов по умолчанию :
@Override
public String turnAlarmOn() {
return Vehicle.super.turnAlarmOn() + " " + Alarm.super.turnAlarmOn();
}
@Override
public String turnAlarmOff() {
return Vehicle.super.turnAlarmOff() + " " + Alarm.super.turnAlarmOff();
}
5. Методы статического интерфейса
Помимо возможности объявлять методы по умолчанию в интерфейсах, Java 8 позволяет нам определять и реализовывать статические методы в интерфейсах .
Поскольку статические методы не принадлежат определенному объекту, они не являются частью API классов, реализующих интерфейс, и их необходимо вызывать, используя имя интерфейса, предшествующее имени метода .
Чтобы понять, как статические методы работают в интерфейсах, давайте проведем рефакторинг интерфейса Vehicle и добавим к нему статический служебный метод:
public interface Vehicle {
// regular / default interface methods
static int getHorsePower(int rpm, int torque) {
return (rpm * torque) / 5252;
}
}
Определение статического метода в интерфейсе идентично определению метода в классе. Кроме того, метод static может быть вызван в других методах static и default .
Теперь предположим, что мы хотим рассчитать мощность двигателя данного транспортного средства. Мы просто вызываем метод getHorsePower() :
Vehicle.getHorsePower(2500, 480));
Идея, лежащая в основе статических интерфейсных методов, состоит в том, чтобы обеспечить простой механизм, который позволяет нам повысить степень согласованности дизайна, объединив связанные методы в одном месте без необходимости создания объекта.
В значительной степени то же самое можно сделать с абстрактными классами. Основное различие заключается в том, что абстрактные классы могут иметь конструкторы, состояние и поведение .
Кроме того, статические методы в интерфейсах позволяют группировать связанные служебные методы без необходимости создавать искусственные служебные классы, которые являются просто заполнителями для статических методов.
6. Заключение
В этой статье мы подробно изучили использование методов static и default interface в Java 8. На первый взгляд эта функция может показаться немного неаккуратной, особенно с точки зрения объектно-ориентированного пуриста. В идеале интерфейсы не должны инкапсулировать поведение и должны использоваться только для определения общедоступного API определенного типа.
Однако, когда дело доходит до поддержания обратной совместимости с существующим кодом, методы static и default являются хорошим компромиссом.
И, как обычно, все примеры кода, показанные в этой статье, доступны на GitHub .