Для чего нужны интерфейсы?

Цикл статей «Учебник Java 8».

Следующая статья — «Java 8 наследование».
Предыдущая статья — «Java 8 вложенные классы и лямбда-выражения».

Интерфейсы в Java — это некоторый контракт, описание методов, которые обязательно должны присутствовать в классе, реализующем этот интерфейс. Интерфейсы позволяют иметь несколько различных реализаций одного и того же действия, но выполняемого различными способами или с различными видами данных. Когда вы пишете какую-либо библиотеку, имеет смысл давать пользователям работать только с публичными интерфейсами. Тогда пользователи смогут заменить одну реализацию этих интерфейсов на другую без переписывания большей части кода, также вы сможете менять внутреннюю архитектуру библиотеки без необходимости переписывания зависящего клиентского кода.

Интерфейсы в Java являются ссылочными типами, как классы, но они могут содержать в себе только константы, сигнатуры методов, default методы (методы по умолчанию), static методы (статические методы) и вложенные типы. Тела методов могут быть только у статических методов и методов по умолчанию.

Нельзя создать экземпляр интерфейса. Интерфейс может быть только реализован каким-либо классом, либо наследоваться другим интерфейсом.

Пример интерфейса, описывающий общие методы для всех монстров:

Monstr.java Java

Ключевое слово public означает, что интерфейс будет доступен из всех пакетов. Можно не указывать модификатор доступа, и тогда интерфейс будет package-private.

Любой интерфейс является abstract , нет никакого смысла писать дополнительно это слово при объявлении интерфейса, хотя компилятор и проглотит фразу public abstract interface Monstr.

Любое объявление поля в интерфейсе является public static final . Нет никакой нужды дополнительно писать любое из этих ключевых слов. Обратите внимание на объявление MONSTR_OBSTACLE_ID в примере.

Любой нестатический и не default метод метод в интерфейсе public и abstract . Нет никакого смысла писать любое из этих ключевых слов.

Любой метод в интерфейсе public , нет нужды указывать этот модификатор.

Ключевое слово abstract для метода означает, что у метода нет реализации, а ключевое слово abstract у всего интерфейса означает, что все методы экземпляров не имеют реализации (кроме статических методов и методов по умолчанию). Для классов abstract имеет примерно такое же действие, оно будет объяснено в статье про наследование.

Чтобы использовать интерфейс нужно объявить класс, реализующий этот интерфейс, с помощью ключевого слова implements :

Goblin.java Java

Мы объявили класс public , чтобы он был доступен из всех пакетов, но можно объявить и без модификатора, если нужно.

В нашем классе Goblin мы объявили все методы из интерфейса и указали для них аннотацию @Override , чтобы показать компилятору, что мы действительно хотим реализовать методы из интерфейса. Аннотация @Override не обязательна, но она может помочь найти ошибку при неправильном объявлении сигнатуры. Поскольку методы из интерфейса неявно имеют модификатор доступа public , то мы тоже должны объявить эти методы как public , иначе будет ошибка компиляции.

Класс, реализующий интерфейс, должен реализовать все методы из этого интерфейса, либо быть abstract (будет описано в статье про наследование).

Интерфейс может расширять другие интерфейсы наследуясь от них с помощью ключевого слова extends :

Elemental.java Java

1 2 3 public interface Elemental extends Monstr, Obstacle, Ghost, Enemy { // Константы и методы… }

В этом примере интерфейс Elemental расширяет (наследуется от) интерфейсы Monstr , Obstacle , Ghost и Enemy . Класс, реализующий интерфейс Elemental , должен будет реализовать все методы из Elemental , Monstr , Obstacle , Ghost и Enemy .

Интерфейс может содержать в себе вложенные типы:

Elemental.java Java

Вложенные типы неявно public и static , нет смысла использовать эти модификаторы, хотя компилятор и допускает это.

Вложенный тип не может иметь модификатор доступа private или protected , иначе будет ошибка компиляции.

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

Java

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 методы)

Предположим, что у вас есть интерфейс:

Monstr.java Java

1 2 3 4 public interface Monstr { boolean isSensitiveToSilver(); void logic(VisibleWorld visibleWorld); }

И класс, реализующий этот интерфейс:

Goblin.java Java

Спустя какое-то время вам понадобилось добавить новый метод в интерфейс Monstr :

Monstr.java Java

Теперь класс Goblin не может скомпилироваться, так как он уже не реализует полностью интерфейс Monstr . Если они находятся в одном проекте, разрабатываемом вами, то новый метод легко можно туда добавить. Но мы не можем добавить новых методов в реализации этого интерфейса в других проектах, к которым у вас нет доступа.

Если вам понадобилось добавить новый метод в интерфейс, то вам и другим людям, использующим его, придётся добавить реализацию этого метода во все классы, которые реализуют этот интерфейс, поэтому старайтесь тщательно продумывать варианты использования вашего интерфейса с самого начала и описывать его полностью сразу.

Если вам понадобилось добавить новый метод в интерфейс, то вы можете создать новый интерфейс, расширяющий старый и добавляющий этот метод:

ExtendedMonstr.java Java

1 2 3 public interface ExtendedMonstr extends Monstr { void doSomething(); }

Теперь пользователи смогут выбрать, остаться ли им на старом интерфейсе, либо перейти на новый и получить дополнительные возможности.

Или вы можете использовать методы по умолчанию (default methods):

Monstr.java Java

Для методов по умолчанию нужно обязательно указать реализацию. Эта реализация может вызывать другие методы из этого интерфейса и интерфейсов, от которых он наследуется.

Теперь классы, реализующие интерфейс Monstr , и интерфейсы, расширяющие его, получат метод doSomething() , и им не нужно будет изменять либо перекомпилировать себя.

Когда вы расширяете своим интерфейсом другой интерфейс, который содержит default метод, то вы можете:

  • Не упоминать этот метод, и тогда ваш интерфейс унаследует его.
  • Переобъявить default метод, что сделает его abstract .
  • Объявить свой default метод с теми же параметрами и именем, что переопределит его.

Статические методы (static методы)

Интерфейс может содержать статические методы, как и класс. Статические методы относятся к самому типу и вызываются через него.

Пример:

Monstr.java Java

Что такое технология Java и каково ее применение?

Java представляет собой язык программирования и платформу вычислений, которая была впервые выпущена Sun Microsystems в 1995 г. Существует множество приложений и веб-сайтов, которые не работают при отсутствии установленной Java, и с каждым днем число таких веб-сайтов и приложений увеличивается. Java отличается быстротой, высоким уровнем защиты и надежностью. От портативных компьютеров до центров данных, от игровых консолей до суперкомпьютеров, используемых для научных разработок, от сотовых телефонов до сети Интернет — Java повсюду!

Можно ли загрузить Java бесплатно?

Да, Java можно загрузить бесплатно. Загрузите последнюю версию на веб-сайте java.com.

Если вы разрабатываете встроенное или бытовое устройство и хотите использовать в нем технологии Java, свяжитесь со специалистами Oracle и получите подробную информацию об интеграции Java в различные типы устройств.

Почему необходимо выполнять обновление до новейшей версии Java?

Новейшая версия Java содержит важные улучшения, позволяющие повысить производительность, стабильность и безопасность приложений Java, запускаемых на вашем компьютере. Установка этого бесплатного пакета обновления обеспечит безопасную и эффективную работу приложений Java, установленных на вашем компьютере.

ТЕХНИЧЕСКИЕ ПОДРОБНОСТИ

Что я получаю, загрузив программное обеспечения Java?

После загрузки Java пользователи получают Java Runtime Environment (JRE). JRE состоит из Java Virtual Machine (JVM), базовых классов платформы Java и вспомогательных библиотек платформы Java. JRE является областью программного обеспечения Java, используемой во время выполнения, т.е. единственным компонентом, который требуется для его запуска в используемом в веб-браузере.

Что собой представляет программное обеспечения подключаемого модуля Java?

Программное обеспечение подключаемого модуля Java является компонентом Java Runtime Environment (JRE). JRE обеспечивает возможность запуска апплетов, написанных на языке программирования Java, в различных браузерах. Подключаемый модуль Java не является автономной программой и не может быть установлен отдельно.

Я слышал термины «виртуальная машина Java» и JVM. Это и есть программное обеспечение Java?

Виртуальная машина Java представляет собой только один аспект программного обеспечения Java, который задействуется при взаимодействии через Интернет. Виртуальная машина Java встраивается непосредственно в загрузку программного обеспечения Java, и используется для поддержки запуска приложений Java.

» Узнайте больше о технологии Java

Дополнительные ресурсы:

  • Словарь используемых терминов и определений
  • Что такое обновление Java и как изменить расписание обновлений?

Интерфейсы

Последнее обновление: 21.04.2018

Механизм наследования очень удобен, но он имеет свои ограничения. В частности мы можем наследовать только от одного класса, в отличие, например, от языка С++, где имеется множественное наследование.

В языке Java подобную проблему частично позволяют решить интерфейсы. Интерфейсы определяют некоторый функционал, не имеющий конкретной реализации, который затем реализуют классы, применяющие эти интерфейсы. И один класс может применить множество интерфейсов.

Чтобы определить интерфейс, используется ключевое слово interface. Например:

interface Printable{ void print(); }

Данный интерфейс называется Printable. Интерфейс может определять константы и методы, которые могут иметь, а могут и не иметь реализации. Методы без реализации похожи на абстрактные методы абстрактных классов. Так, в данном случае объявлен один метод, который не имеет реализации.

Все методы интерфейса не имеют модификаторов доступа, но фактически по умолчанию доступ public, так как цель интерфейса — определение функционала для реализации его классом. Поэтому весь функционал должен быть открыт для реализации.

Чтобы класс применил интерфейс, надо использовать ключевое слово implements:

public class Program{ public static void main(String args) { Book b1 = new Book(«Java. Complete Referense.», «H. Shildt»); b1.print(); } } interface Printable{ void print(); } class Book implements Printable{ String name; String author; Book(String name, String author){ this.name = name; this.author = author; } public void print() { System.out.printf(«%s (%s) \n», name, author); } }

В данном случае класс Book реализует интерфейс Printable. При этом надо учитывать, что если класс применяет интерфейс, то он должен реализовать все методы интерфейса, как в случае выше реализован метод print. Потом в методе main мы можем объект класса Book и вызвать его метод print. Если класс не реализует какие-то методы интерфейса, то такой класс должен быть определен как абстрактный, а его неабстрактные классы-наследники затем должны будут эти методы.

В тоже время мы не можем напрямую создавать объекты интерфейсов, поэтому следующий код не будет работать:

Printable pr = new Printable(); pr.print();

Одним из преимуществ использования интерфейсов является то, что они позволяют добавить в приложение гибкости. Например, в дополнение к классу Book определим еще один класс, который будет реализовывать интерфейс Printable:

class Journal implements Printable { private String name; String getName(){ return name; } Journal(String name){ this.name = name; } public void print() { System.out.println(name); } }

Класс Book и класс Journal связаны тем, что они реализуют интерфейс Printable. Поэтому мы динамически в программе можем создавать объекты Printable как экземпляры обоих классов:

public class Program{ public static void main(String args) { Printable printable = new Book(«Java. Complete Reference», «H. Shildt»); printable.print(); // Java. Complete Reference (H. Shildt) printable = new Journal(«Foreign Policy»); printable.print(); // Foreign Policy } } interface Printable{ void print(); } class Book implements Printable{ String name; String author; Book(String name, String author){ this.name = name; this.author = author; } public void print() { System.out.printf(«%s (%s) \n», name, author); } } class Journal implements Printable { private String name; String getName(){ return name; } Journal(String name){ this.name = name; } public void print() { System.out.println(name); } }

Интерфейсы в преобразованиях типов

Все сказанное в отношении преобразования типов характерно и для интерфейсов. Например, так как класс Journal реализует интерфейс Printable, то переменная типа Printable может хранить ссылку на объект типа Journal:

Printable p =new Journal(«Foreign Affairs»); p.print(); // Интерфейс не имеет метода getName, необходимо явное приведение String name = ((Journal)p).getName(); System.out.println(name);

И если мы хотим обратиться к методам класса Journal, которые определены не в интерфейсе Printable, а в самом классе Journal, то нам надо явным образом выполнить преобразование типов: ((Journal)p).getName();

Методы по умолчанию

Ранее до JDK 8 при реализации интерфейса мы должны были обязательно реализовать все его методы в классе. А сам интерфейс мог содерать только определения методов без конкретной реализации. В JDK 8 была добавлена такая функциональность как методы по умолчанию. И теперь интерфейсы кроме определения методов могут иметь их реализацию по умолчанию, которая используется, если класс, реализующий данный интерфейс, не реализует метод. Например, создадим метод по умолчанию в интерфейсе Printable:

interface Printable { default void print(){ System.out.println(«Undefined printable»); } }

Метод по умолчанию — это обычный метод без модификаторов, который помечается ключевым словом default. Затем в классе Journal нам необязательно этот метод реализовать, хотя мы можем его и переопределить:

class Journal implements Printable { private String name; String getName(){ return name; } Journal(String name){ this.name = name; } }

Статические методы

Начиная с JDK 8 в интерфейсах доступны статические методы — они аналогичны методам класса:

interface Printable { void print(); static void read(){ System.out.println(«Read printable»); } }

Чтобы обратиться к статическому методу интерфейса также, как и в случае с классами, пишут название интерфейса и метод:

public static void main(String args) { Printable.read(); }

Приватные методы

По умолчанию все методы в интерфейсе фактически имеют модификатор public. Однако начиная с Java 9 мы также можем определять в интерфейсе методы с модификатором private. Они могут быть статическими и нестатическими, но они не могут иметь реализации по умолчанию.

Подобные методы могут использоваться только внутри самого интерфейса, в котором они определены. То есть к примеру нам надо выполнять в интерфейсе некоторые повторяющиеся действия, и в этом случае такие действия можно выделить в приватные методы:

public class Program{ public static void main(String args) { Calculatable c = new Calculation(); System.out.println(c.sum(1, 2)); System.out.println(c.sum(1, 2, 4)); } } class Calculation implements Calculatable{ } interface Calculatable{ default int sum(int a, int b){ return sumAll(a, b); } default int sum(int a, int b, int c){ return sumAll(a, b, c); } private int sumAll(int… values){ int result = 0; for(int n : values){ result += n; } return result; } }

Константы в интерфейсах

Кроме методов в интерфейсах могут быть определены статические константы:

interface Stateable{ int OPEN = 1; int CLOSED = 0; void printState(int n); }

Хотя такие константы также не имеют модификаторов, но по умолчанию они имеют модификатор доступа public static final, и поэтому их значение доступно из любого места программы.

Применение констант:

public class Program{ public static void main(String args) { WaterPipe pipe = new WaterPipe(); pipe.printState(1); } } class WaterPipe implements Stateable{ public void printState(int n){ if(n==OPEN) System.out.println(«Water is opened»); else if(n==CLOSED) System.out.println(«Water is closed»); else System.out.println(«State is invalid»); } } interface Stateable{ int OPEN = 1; int CLOSED = 0; void printState(int n); }

Множественная реализация интерфейсов

Если нам надо применить в классе несколько интерфейсов, то они все перечисляются через запятую после слова implements:

interface Printable { // методы интерфейса } interface Searchable { // методы интерфейса } class Book implements Printable, Searchable{ // реализация класса }

Наследование интерфейсов

Интерфейсы, как и классы, могут наследоваться:

interface BookPrintable extends Printable{ void paint(); }

При применении этого интерфейса класс Book должен будет реализовать как методы интерфейса BookPrintable, так и методы базового интерфейса Printable.

Вложенные интерфейсы

Как и классы, интерфейсы могут быть вложенными, то есть могут быть определены в классах или других интерфейсах. Например:

class Printer{ interface Printable { void print(); } }

При применении такого интерфейса нам надо указывать его полное имя вместе с именем класса:

public class Journal implements Printer.Printable { String name; Journal(String name){ this.name = name; } public void print() { System.out.println(name); } }

Использование интерфейса будет аналогично предыдущим случаям:

Printer.Printable p =new Journal(«Foreign Affairs»); p.print();

Интерфейсы как параметры и результаты методов

И также как и в случае с классами, интерфейсы могут использоваться в качестве типа параметров метода или в качестве возвращаемого типа:

public class Program{ public static void main(String args) { Printable printable = createPrintable(«Foreign Affairs»,false); printable.print(); read(new Book(«Java for impatients», «Cay Horstmann»)); read(new Journal(«Java Dayly News»)); } static void read(Printable p){ p.print(); } static Printable createPrintable(String name, boolean option){ if(option) return new Book(name, «Undefined»); else return new Journal(name); } } interface Printable{ void print(); } class Book implements Printable{ String name; String author; Book(String name, String author){ this.name = name; this.author = author; } public void print() { System.out.printf(«%s (%s) \n», name, author); } } class Journal implements Printable { private String name; String getName(){ return name; } Journal(String name){ this.name = name; } public void print() { System.out.println(name); } }

Метод read() в качестве параметра принимает объект интерфейса Printable, поэтому в этот метод мы можем передать как объект Book, так и объект Journal.

Метод createPrintable() возвращает объект Printable, поэтому также мы вожем возвратить как объект Book, так и Journal.

Консольный вывод:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *