Рубрики
Без рубрики

Статические и стандартные методы в интерфейсах Java

Узнайте, как писать и использовать статические методы и методы по умолчанию в интерфейсах Java.

Автор оригинала: baeldung.

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 .