Цикл статей «Учебник Java 8».
Следующая статья — «Java 8 наследование».
Предыдущая статья — «Java 8 вложенные классы и лямбда-выражения».
Интерфейсы в Java — это некоторый контракт, описание методов, которые обязательно должны присутствовать в классе, реализующем этот интерфейс. Интерфейсы позволяют иметь несколько различных реализаций одного и того же действия, но выполняемого различными способами или с различными видами данных. Когда вы пишете какую-либо библиотеку, имеет смысл давать пользователям работать только с публичными интерфейсами. Тогда пользователи смогут заменить одну реализацию этих интерфейсов на другую без переписывания большей части кода, также вы сможете менять внутреннюю архитектуру библиотеки без необходимости переписывания зависящего клиентского кода.
Интерфейсы в Java являются ссылочными типами, как классы, но они могут содержать в себе только константы, сигнатуры методов, default методы (методы по умолчанию), static методы (статические методы) и вложенные типы. Тела методов могут быть только у статических методов и методов по умолчанию.
Нельзя создать экземпляр интерфейса. Интерфейс может быть только реализован каким-либо классом, либо наследоваться другим интерфейсом.
Пример интерфейса, описывающий общие методы для всех монстров:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public interface Monstr { // Объявляем константу int MONSTR_OBSTACLE_ID = 23; // методы boolean isSensitiveToSilver(); void logic(VisibleWorld visibleWorld); void setPosition(double x, double y); boolean isAggressive(); } |
Ключевое слово public означает, что интерфейс будет доступен из всех пакетов. Можно не указывать модификатор доступа, и тогда интерфейс будет package-private.
Любой интерфейс является abstract , нет никакого смысла писать дополнительно это слово при объявлении интерфейса, хотя компилятор и проглотит фразу public abstract interface Monstr.
Любое объявление поля в интерфейсе является public static final . Нет никакой нужды дополнительно писать любое из этих ключевых слов. Обратите внимание на объявление MONSTR_OBSTACLE_ID в примере.
Любой нестатический и не default метод метод в интерфейсе public и abstract . Нет никакого смысла писать любое из этих ключевых слов.
Любой метод в интерфейсе public , нет нужды указывать этот модификатор.
Ключевое слово abstract для метода означает, что у метода нет реализации, а ключевое слово abstract у всего интерфейса означает, что все методы экземпляров не имеют реализации (кроме статических методов и методов по умолчанию). Для классов abstract имеет примерно такое же действие, оно будет объяснено в статье про наследование.
Чтобы использовать интерфейс нужно объявить класс, реализующий этот интерфейс, с помощью ключевого слова implements :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public class Goblin implements Monstr { private double x; private double y; @Override public boolean isSensitiveToSilver() { return false; } @Override public void logic(VisibleWorld visibleWorld) { // некая логика. } @Override public void setPosition(double x, double y) { this.x = x; this.y = y; } @Override public boolean isAggressive() { return false; } } |
Мы объявили класс public , чтобы он был доступен из всех пакетов, но можно объявить и без модификатора, если нужно.
В нашем классе Goblin мы объявили все методы из интерфейса и указали для них аннотацию @Override , чтобы показать компилятору, что мы действительно хотим реализовать методы из интерфейса. Аннотация @Override не обязательна, но она может помочь найти ошибку при неправильном объявлении сигнатуры. Поскольку методы из интерфейса неявно имеют модификатор доступа public , то мы тоже должны объявить эти методы как public , иначе будет ошибка компиляции.
Класс, реализующий интерфейс, должен реализовать все методы из этого интерфейса, либо быть abstract (будет описано в статье про наследование).
Интерфейс может расширять другие интерфейсы наследуясь от них с помощью ключевого слова extends :
1 2 3 |
public interface Elemental extends Monstr, Obstacle, Ghost, Enemy { // Константы и методы... } |
В этом примере интерфейс Elemental расширяет (наследуется от) интерфейсы Monstr , Obstacle , Ghost и Enemy . Класс, реализующий интерфейс Elemental , должен будет реализовать все методы из Elemental , Monstr , Obstacle , Ghost и Enemy .
Интерфейс может содержать в себе вложенные типы:
1 2 3 4 5 6 7 8 9 10 11 |
public interface Elemental extends Monstr, Obstacle, Ghost, Enemy { // Константы и методы... class ElementalForce { private double x; private double y; public void someMethod1() { } } } |
Вложенные типы неявно public и static , нет смысла использовать эти модификаторы, хотя компилятор и допускает это.
Вложенный тип не может иметь модификатор доступа private или protected , иначе будет ошибка компиляции.
Интерфейс можно использовать как обычный тип: его можно указывать в качестве типа объявляемой переменной, его можно указывать в качестве параметра метода, и можно приводить переменную к типу интерфейса.
1 2 3 4 5 6 7 8 9 10 11 |
void someMethod(Obstacle obstacle, Enemy enemy) { if (obstacle instanceof Enemy) { // Приводим к интерфейсу Enemy Enemy obstacleEnemy = (Enemy) obstace; // остальные действия. } // Объявляем переменную с типом интерфейса Monstr. Monstr monstr = null; //... } |
Методы по умолчанию (default методы)
Предположим, что у вас есть интерфейс:
1 2 3 4 |
public interface Monstr { boolean isSensitiveToSilver(); void logic(VisibleWorld visibleWorld); } |
И класс, реализующий этот интерфейс:
1 2 3 4 5 6 7 8 9 10 11 |
public class Goblin implements Monstr { @Override public boolean isSensitiveToSilver() { return false; } @Override public void logic(VisibleWorld visibleWorld) { // некая логика. } } |
Спустя какое-то время вам понадобилось добавить новый метод в интерфейс Monstr :
1 2 3 4 5 6 7 |
public interface Monstr { boolean isSensitiveToSilver(); void logic(VisibleWorld visibleWorld); // Новый метод void doSomething(); } |
Теперь класс Goblin не может скомпилироваться, так как он уже не реализует полностью интерфейс Monstr . Если они находятся в одном проекте, разрабатываемом вами, то новый метод легко можно туда добавить. Но мы не можем добавить новых методов в реализации этого интерфейса в других проектах, к которым у вас нет доступа.
Если вам понадобилось добавить новый метод в интерфейс, то вам и другим людям, использующим его, придётся добавить реализацию этого метода во все классы, которые реализуют этот интерфейс, поэтому старайтесь тщательно продумывать варианты использования вашего интерфейса с самого начала и описывать его полностью сразу.
Если вам понадобилось добавить новый метод в интерфейс, то вы можете создать новый интерфейс, расширяющий старый и добавляющий этот метод:
1 2 3 |
public interface ExtendedMonstr extends Monstr { void doSomething(); } |
Теперь пользователи смогут выбрать, остаться ли им на старом интерфейсе, либо перейти на новый и получить дополнительные возможности.
Или вы можете использовать методы по умолчанию (default methods):
1 2 3 4 5 6 7 8 9 |
public interface Monstr { boolean isSensitiveToSilver(); void logic(VisibleWorld visibleWorld); // Новый метод default void doSomething() { // Некий код } } |
Для методов по умолчанию нужно обязательно указать реализацию. Эта реализация может вызывать другие методы из этого интерфейса и интерфейсов, от которых он наследуется.
Теперь классы, реализующие интерфейс Monstr , и интерфейсы, расширяющие его, получат метод doSomething() , и им не нужно будет изменять либо перекомпилировать себя.
Когда вы расширяете своим интерфейсом другой интерфейс, который содержит default метод, то вы можете:
- Не упоминать этот метод, и тогда ваш интерфейс унаследует его.
- Переобъявить default метод, что сделает его abstract .
- Объявить свой default метод с теми же параметрами и именем, что переопределит его.
Статические методы (static методы)
Интерфейс может содержать статические методы, как и класс. Статические методы относятся к самому типу и вызываются через него.
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public interface Monstr { boolean isSensitiveToSilver(); void logic(VisibleWorld visibleWorld); static void logicForSensitiveToSilver(Monstr[] monstrs, VisibleWorld visibleWorld) { for (Monstr monstr : monstrs) { if (monstr.isSensitiveToSilver()) { monstr.logic(visibleWorld); } } } } |
Цикл статей «Учебник Java 8».
Следующая статья — «Java 8 наследование».
Предыдущая статья — «Java 8 вложенные классы и лямбда-выражения».
Откуда такие названия классов-интерфейсов?
И примера вызова статического метода в конце явно не хватает
Названия все выдуманные. Примера вызова статического метода действительно нет. Может быть и стоит добавить, хотя там ничего отличающегося от вызова статического метода класса нет.