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 .