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

1. Общ преглед

Java 8 представи на масата няколко чисто нови функции, включително ламбда изрази, функционални интерфейси, референции на методи, потоци, незадължителни и статични и стандартни методи в интерфейсите.

Някои от тях вече са разгледани в тази статия. Независимо от това, статичните и стандартните методи в интерфейсите заслужават по-задълбочен поглед сами.

В тази статия ще обсъдим задълбочено как да използваме статични и стандартни методи в интерфейсите и ще разгледаме някои случаи на използване, където те могат да бъдат полезни.

2. Защо са необходими методи по подразбиране в интерфейсите

Подобно на обикновените методи на интерфейса, методите по подразбиране са имплицитно публични - няма нужда да указвате публичния модификатор.

За разлика от обикновените интерфейсни методи, те се декларират с ключовата дума по подразбиране в началото на сигнатурата на метода и осигуряват изпълнение .

Нека да видим прост пример:

public interface MyInterface { // regular interface methods default void defaultMethod() { // default method implementation } }

Причината, поради която методите по подразбиране са включени в изданието на Java 8, е доста очевидна.

В типичен дизайн, базиран на абстракции, където интерфейсът има една или няколко реализации, ако един или повече методи са добавени към интерфейса, всички реализации ще бъдат принудени да ги прилагат също. В противен случай дизайнът просто ще се разпадне.

Методите за интерфейс по подразбиране са ефективен начин за справяне с този проблем. Те ни позволяват да добавяме нови методи към интерфейс, които са автоматично достъпни в реализациите . По този начин няма нужда да модифицирате изпълняващите класове.

По този начин обратната съвместимост е добре запазена, без да се налага да рефакторирате реализаторите.

3. Методи на интерфейса по подразбиране в действие

За да разберем по-добре функционалността на методите на интерфейса по подразбиране , нека създадем прост пример.

Кажете, че имаме наивен интерфейс на превозно средство и само едно изпълнение. Може да има и повече, но нека бъдем толкова прости:

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."; } } 

И накрая, нека дефинираме типичен основен клас, който създава екземпляр на 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()); }

Моля, обърнете внимание как методите по подразбиране turnAlarmOn () и turnAlarmOff () от нашия интерфейс Vehicle са автоматично достъпни в класа Car .

Освен това, ако в даден момент решим да добавим още методи по подразбиране към интерфейса на превозното средство , приложението ще продължи да работи и няма да се налага да принуждаваме класа да предоставя реализации за новите методи.

Най-типичното използване на методите по подразбиране в интерфейсите е постепенното предоставяне на допълнителна функционалност на даден тип, без да се разбиват изпълняващите класове.

В допълнение, те могат да се използват за предоставяне на допълнителна функционалност около съществуващ абстрактен метод :

public interface Vehicle { // additional interface methods double getSpeed(); default double getSpeedInKMH(double speed) { // conversion } }

4. Правила за наследяване на множество интерфейси

Методите по подразбиране на интерфейса наистина са доста приятна функция, но с някои забележки, които си струва да се споменат. Тъй като Java позволява на класовете да реализират множество интерфейси, важно е да знаете какво се случва, когато клас реализира няколко интерфейса, които дефинират едни и същи методи по подразбиране .

За да разберем по-добре този сценарий, нека дефинираме нов интерфейс за аларма и рефакторираме класа Car :

public interface Alarm { default String turnAlarmOn() { return "Turning the alarm on."; } default String turnAlarmOff() { return "Turning the alarm off."; } }

С този нов интерфейс, дефиниращ собствения си набор от методи по подразбиране , класът Car ще внедри както Vehicle, така и Alarm :

public class Car implements Vehicle, Alarm { // ... }

В този случай кодът просто няма да се компилира, тъй като има конфликт, причинен от наследяване на множество интерфейси (известен още като Diamond Problem). Класът Car ще наследи и двата набора от методи по подразбиране . Кои трябва да се извикат тогава?

За да разрешим тази неяснота, трябва изрично да предоставим изпълнение за методите:

@Override public String turnAlarmOn() { // custom implementation } @Override public String turnAlarmOff() { // custom implementation }

Също така можем да накараме нашия клас да използва методите по подразбиране на един от интерфейсите .

Нека да видим пример, който използва методите по подразбиране от интерфейса на превозното средство :

@Override public String turnAlarmOn() { return Vehicle.super.turnAlarmOn(); } @Override public String turnAlarmOff() { return Vehicle.super.turnAlarmOff(); } 

По същия начин можем да накараме класа да използва методите по подразбиране , дефинирани в интерфейса за аларма :

@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 ни позволява да дефинираме и реализираме статични методи в интерфейсите .

Since static methods don't belong to a particular object, they are not part of the API of the classes implementing the interface, and they have to be called by using the interface name preceding the method name.

To understand how static methods work in interfaces, let's refactor the Vehicle interface and add to it a static utility method:

public interface Vehicle { // regular / default interface methods static int getHorsePower(int rpm, int torque) { return (rpm * torque) / 5252; } } 

Defining a static method within an interface is identical to defining one in a class. Moreover, a static method can be invoked within other static and default methods.

Now, say that we want to calculate the horsepower of a given vehicle's engine. We just call the getHorsePower() method:

Vehicle.getHorsePower(2500, 480)); 

The idea behind static interface methods is to provide a simple mechanism that allows us to increase the degree of cohesion of a design by putting together related methods in one single place without having to create an object.

Pretty much the same can be done with abstract classes. The main difference lies in the fact that abstract classes can have constructors, state, and behavior.

Furthermore, static methods in interfaces make possible to group related utility methods, without having to create artificial utility classes that are simply placeholders for static methods.

6. Conclusion

В тази статия разгледахме задълбочено използването на статични и интерфейсни методи по подразбиране в Java 8. На пръв поглед тази функция може да изглежда малко небрежна, особено от обектно-ориентирана пуристическа перспектива. В идеалния случай интерфейсите не трябва да капсулират поведението и трябва да се използват само за дефиниране на публичния API от определен тип.

Що се отнася до поддържането на обратна съвместимост със съществуващия код, статичните и стандартните методи са добър компромис.

Както обикновено, всички примерни кодове, показани в тази статия, са достъпни в GitHub.