Перейти к содержимому

Зачем нужен интерфейс java

  • автор:

Интерфейсы против реализаций

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

Думаю, что здесь будет не лишним привести определение.

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

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

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

Что такое интерфейс?

Большинство современных языков вроде C#, TypeScript или Java поддерживают интерфейсы. Сами же интерфейсы достаточно сложно описать, тем не менее я попытаюсь. Словарь даёт следующее определение:

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

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

Звучит это всё заумно, но так оно и есть.

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

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

Интерфейсы повсюду

В нашей повседневной жизни мы встречаем интерфейсы везде. Даже розетки — это интерфейс. Например, в США они имеют два вертикальных прямоугольных паза для фазы и нейтрального контакта, а ниже круглое отверстие для заземления.

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

Можно подключить фен или монитор — интерфейсу всё равно. В итоге эти предметы можно охарактеризовать как “подключаемые”.

Основная причина использования интерфейсов, как уже было сказано, — уменьшение связанности. Если вы программируете через интерфейсы, то можете писать код так, чтобы он никогда не связывался (не соединялся) ни с чем, кроме интерфейса. Чем менее связанным будет код, тем менее вероятно, что изменение в одной его части отразится на другой.

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

Написать код для интерфейса довольно просто. Достаточно объявить методы и свойства, не прибегая к реализации.

При этом стоит отметить, что такая простота может усложнить понимание. Когда я впервые столкнулся с нотацией интерфейсов, то подумал: “Хмм… Зачем вообще нужны эти интерфейсы?”

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

Простой пример

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

interface IName 
<
firstName: string;
lastName: string;
>

Все примеры в статье будут на TypeScript.

Интерфейсы состоят из имени (в данном случае IName ) и объявления методов/свойств.

По общему соглашению начинаются они с буквы I . Вы можете использовать любое имя, но буква I облегчает их обнаружение в коде.

Интерфейс не содержит “реального” кода, реализующего функциональность. Он не может объявлять поля, переменные и константы. Он также не может определять область видимости (приватная, публичная и пр.) Он только объявляет возможности. Таким образом, каждый член интерфейса по сути является публичным.

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

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

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

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

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

Суть в том, что не имеет значения, чем являются реализующие объекты и что они делают — они просто производят имя, когда рассматриваются как IName .

Реализация интерфейса

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

Некоторые языки позволяют реализовать класс, используя объявление интерфейса частично или задействовав необязательные его части. В нашем же случае мы предположим, что требуется реализация всего интерфейса. Для реализации IName вы можете объявить класс следующим образом:

class Name implements IName private _lastName: string;
private _firstName: string;
public constructor(public aFirstName: string, public aLastName: string) this._firstName = aFirstName;
this._lastName = aLastName;
>
get firstName(): string <
return this._firstName;
>

get lastName(): string return this._lastName;
>

get fullName(): string return this._firstName + ' ' + this._lastName;
>
>

Обратите внимание, что:

  • Класс Name объявлен как реализующий интерфейс IName .
  • Мы реализовали интерфейс немного в обход. Наш класс объявляет конструктор public , получающий имя и фамилию в виде параметров. Затем он хранит эту информацию в переменных private , которые представлены как публичные свойства только для чтения. В результате класс неизменяем.
  • При этом класс не только реализует интерфейс, но и предоставляет свойство fullName , сочетающее в себе значения имени и фамилии. Это показывает, что класс может делать что угодно помимо реализации интерфейса. Тем не менее мы не можем использовать эту функциональность, когда объявляем класс как переменную интерфейса, поскольку он не является его частью.

Использование интерфейса

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

var person: IName;person = new Name('Harvey', 'Wallbanger');
console.log('Hello', person.firstName + '!');

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

Заключение

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

  • Что может помешать разработчику самостоятельно создать успешное приложение
  • 10 программистов-самоучек, ставших преуспевающими разработчиками
  • 5 мудрых цитат известнейших программистов

Зачем нужен интерфейс java

Абстрактные классы vs интерфейсы в Java
Различия между абстрактными классами и интерфейсами в Java

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

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

Почему же на этот вопрос отвечают неправильно? Дело в том, что Java развивается, и границы отличий между абстрактным классом и интерфейсом постепенно стираются. А новички, читая устаревшие книги по Java (или не читая их вовсе) или даже современные статьи-перепечатки, где рассказывается об интерфейсах до Java 8, не имеют представления о том, что в своем развитии они ушли далеко вперед.

Например, до появления Java 8, при возникновении вопроса на собеседовании типа: «Назовите разницу между абстрактным классом и интерфейсом», вы смело могли сказать, что интерфейс представляет собой контракт, содержащий методы без реализации, которые должны быть реализованы в классах, имплементирующих этот интерфейс. А абстрактный класс — это класс, который содержит (или не содержит) абстрактные методы и общий для его потомков код.

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

В данной статье будут рассмотрены как фундаментальные, так и технические различия между интерфейсом и абстрактным классом.

1. Фундаментальное отличие

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

Например, такое поведение как «движение» может быть применимо к разным типам объектов: машина, кот, котировки курса и т. д. Эти объекты не имеют ничего общего, кроме того, что они могут двигаться.

Перефразируем немного иначе — если существует «движущийся» объект, то глядя на реализованный им интерфейс, мы не сможем понять, какой именно тип объекта имеется ввиду. Это может быть машина, кот, котировка курса валют и много других вариантов.

Если перенести это поведение в код, написанный на языке Java, то получится следующее:

 interface Movable

Абстрактный же класс описывает некий абстрактный объект (автомобиль, человека, кота и т. д.), а не только поведение.

Если мы рассуждаем про абстрактный «автомобиль», то в нашей голове сразу формируется картинка с объектом. Мы сразу понимаем, что автомобиль содержит двигатель, колеса и может двигаться, поворачивать, ускоряться или тормозить. Он также будет иметь поля для хранения внутренних деталей, таких как мотор, колеса и тормоза. Вы получите все это просто сказав слово «автомобиль».

При этом кот и котировка курса валют не могут быть автомобилем, даже если они также могут двигаться.
На основе вышесказанного создадим класс Automobile:

 abstract class Automobile

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

А теперь поговорим про различия в технической реализации.
2. Технические отличия

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

2.1. Синтаксис создания

Итак, при создании абстрактного класса указывается ключевое слово abstract, а при определении интерфейса — interface.

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

 public abstract class MyAbstractClass < // поля и конструкторы // абстрактные методы // методы с реализацией >

Пример интерфейса:

 public interface MyInterface < // объявление констант // методы без реализации // статические методы // методы по умолчанию (default) // приватные методы >

2.2 Синтаксис использования

При наследовании от абстрактного класса используется ключевое слово extends (с англ. «расширяет»), а при реализации интерфейса — implements (с англ. «реализует»).

В приведенном ниже коде класс MyClass расширяет MyAbstractClass:

 public class MyClass extends MyAbstractClass < // реализация абстрактных методов // иной код >

А тут класс MyClass реализует интерфейс MyInterface:

 public class MyClass implements MyInterface < // реализация методов интерфейса // иной код >

Класс может одновременно и наследоваться от абстрактного класса (только одного) и реализовать один или множество интерфейсов.

Например, класс MyClass реализует интерфейс MyInterface, MyInterface_2 и MyInterface_3:

 class MyClass extends MyAbstractClass implements MyInterface, MyInterface_2, MyInterface_3 < // реализация абстрактных методов абстрактного класса // реализация методов из интерфейсов // иной код >

2.3 Наличие конструктора

Как вы знаете, невозможно создать экземпляр абстрактного класса. Но, объявить и определить в нем конструктор мы можем. В противном случае за нас это сделает компилятор, создав конструктор по умолчанию. Без него код просто не скомпилируется, поскольку при создании конкретного класса первым оператором будет неявный вызов super() конструктора суперкласса, в данном случае абстрактного.

Добавим в абстрактный класс MyAbstractClass конструктор:

 public abstract class MyAbstractClass < public MyAbstractClass() < System.out.println("Конструктор из MyAbstractClass"); >> 

Также добавим конструктор в MyClass:

 public class MyClass extends MyAbstractClass < public MyClass() < System.out.println("Конструктор из MyClass"); >> 

В классе Main создадим объект и запустим программу:

 public class Main < public static void main(String[] args) < MyAbstractClass myObject = new MyClass(); >> 

Результат выполнения программы:

Для интерфейсов понятия «конструктор» не существует.
2.4 Типы переменных

Все переменные в интерфейсах неявно являются public static final (т.е. константами). «final» подразумевает, что переменной обязательно должно быть присвоено значение во время инициализации.

Рассмотрим следующий код:

 public interface MyInterface < // эта строка не скомпилируется int value_1; int value_2 = 1; public final int value_3 = 1; static int value_4 = 1; public final static int value_5 = 1; static final int value_6 = 1; >

Поскольку value _1 не присвоено конкретное значение, а она является неявно final, код с такой строкой не скомпилируется. Остальные строки не вызовут ошибок, т.к. public static final можно не указывать (если указать, то IDE выделит их серым цветом, подчеркивая «избыточность»).

В абстрактных классах переменные могут быть любыми — абстрактность класса не накладывает ограничений.

Например, следующий код является корректным:

 public abstract class MyAbstractClass

2.5 Модификаторы доступа методов

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

А вот в случае с интерфейсами модификаторы доступа могут быть только двух типов, public и private (последний — начиная с Java 9).

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

Рассмотрим следующий код:

 public interface MyInterface_2 < void publicAbstractMethod_1(); public void publicAbstractMethod_2(); void publicAbstractMethod_3(); private void privateMethod() < // Реализация метода >> 

Методы publicAbstractMethod_1 и publicAbstractMethod_3 являются неявно public.
2.6 Методы с реализацией
Для абстрактного класса все методы, кроме абстрактных, должны иметь реализацию.

Для интерфейсов, начиная с Java 8, вводится понятие метода по умолчанию. Такие методы, во-первых, должны иметь реализацию в интерфейсе, а во-вторых, помечены ключевым словом default. Они также являются неявно public. При этом они не должны в обязательном порядке иметь реализацию в реализующем интерфейс классе, но могут быть в нем переопределены.

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

Например, код ниже скомпилируется:

 public interface MyInterface_3 < default void defaultMethod() < // Реализация метода >public default void defaultMethod_2() < // Реализация метода >private static void privateStaticMethod() < // Реализация метода >public static void publicStaticMethod_1() < // Реализация метода >static void publicStaticMethod_2() < // Реализация метода >> 

2.7 Наследование

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

Абстрактный класс может наследовать как обычный класс, так и абстрактный. В обоих случаях это будет только один класс (в Java нет множественного наследования классов).

В то же время абстрактный класс также может реализовать до 65 535 интерфейсов (это связано с ограничением константы interfaces_count в структуре ClassFile).

В этом месте давайте немного отойдем от темы сравнения и затронем множественное наследование. В некоторых источниках однозначно указывается, что множественного наследования в Java нет. В других источниках говорится, что множественного наследования как бы нет, но оно реализуется через интерфейсы.

  • наследование состояния (Inheritance of State)
  • наследование реализации (Inheritance of Implementation)
  • наследование типа (Inheritance of Type)

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

Именно таким должен быть ответ на вопрос о множественном наследовании в Java.
3. Рекомендации к применению абстрактных классов и интерфейсов
В соответствии с рекомендациями Oracle абстрактный класс нужно использовать в следующих случаях:

  • Необходимо выделить общий код между несколькими тесно связанными классами
    Пояснение: это типовой рефакторинг, целью которого является устранение дублирования кода.
  • Мы ожидаем, что классы, расширяющие абстрактный класс, имеют много общих методов, полей или требуют модификаторов доступа, отличных от public (protected и private)
    Пояснение: ранее мы писали, что в интерфейсах методы, имеющие реализацию (помеченные ключевым словом default), являются неявно public. Если же метод, имеющий реализацию, помечен явно как private, то он не сможет быть использован в классах, реализующих этот интерфейс, а только в других методах интерфейса. Поэтому, если нам нужны методы с модификаторами доступа не public, мы должны использовать абстрактный класс.
  • Мы хотим объявить не static или не final поля для изменения состояния объекта.
    Пояснение: ранее мы писали, что все переменные в интерфейсах неявно являются public static final — из-за чего они не могут быть изменены.

Для использования интерфейсов существуют следующие причины:

  • Планируется, что несвязанные между собой классы будут реализовывать интерфейс. Например, интерфейсы Comparable и Cloneable реализуются многими несвязанными между собой классами.
    Пояснение: как мы выяснили ранее, концептуальное назначение интерфейса — описание «поведения», а не «состояния» (в отличие от абстрактного класса). Соответственно, реализовать это поведение могут любые классы, несвязанные между собой, которые просто должны что-то «уметь» делать или «как-то» себя вести.
  • Требуется детализировать или определить поведение определенного типа данных, но при этом мы не хотим беспокоиться о том, кто реализует его поведение.
    Пояснение: пояснение к прошлому пункту также актуально и для этого — нам все равно, какие классы будут реализовывать наш интерфейс и в каких пакетах находятся. Мы просто хотим, чтобы они вели себя в соответствии с «нашим контрактом», прописанным в интерфейсе в виде методов без реализации.
  • Мы хотим воспользоваться преимуществами множественного наследования типов

Приведем наглядный пример выбора между абстрактным классом и интерфейсом.

Существует паттерн «Шаблонный метод», определение которого звучит так: «шаблонный метод определяет основу алгоритма и позволяет подклассам переопределить некоторые его шаги, не изменяя структуру, в целом».

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

Так вот вопрос, а что нам мешает разместить абстрактные и шаблонные методы в интерфейсе? В качестве шаблонного метода мы могли бы использовать метод по умолчанию. Вроде звучит логично, согласитесь?

Но обратите еще раз внимание на определение шаблонного метода. Там есть такие слова: «… не изменяя структуру, в целом». Подклассы абстрактного класса не должны иметь возможность переопределить шаблонный метод с целью изменения логики (алгоритма) его работы. По этой причине шаблонные методы объявляются как final и не могут использовать интерфейсы с их методами по умолчанию (могут быть переопределены) вместо абстрактных классов. Вся идея паттерна в этом случае будет нарушена.

Для того чтобы рассмотреть разницу между интерфейсом и абстрактным классом немного глубже, рекомендуется обратиться к книге «Java. Эффективное программирование», Блох Д., 3-е издание.

В разделе 4.6 этой книги говорится о следующих причинах предпочтения интерфейсов абстрактным классам:

1. Существующие классы можно легко приспособить для реализации но­вого интерфейса.
Пояснение: для этого достаточно написать implements и реализовать необходимые методы. Но уже имеющиеся классы в общем случае не могут быть переделаны для расширения нового абстрактного класса. Если вы хотите, чтобы два класса расширяли один и тот же абстрактный класс, вам придется поднять этот класс в иерархии настолько высоко, чтобы он стал предком обоих этих классов. Это может привести к нарушению логики иерархии типов, заставляя всех потомков нового абстрактного класса расширять его независимо от того, насколько это целесообразно.

2. Интерфейсы идеально подходят для создания миксинов.
Пояснение: Миксин (mixin) — это тип, который класс может реализовать в дополнение к своему «первичному типу», объявляя о том, что этот класс предоставляет некоторое необязательное поведение. Например, Comparable является таким интерфейсом-миксином, т. к. добавляет (примешивает) к первоначальным возможностям типа дополнительную функциональность.
Использовать абстракт­ные классы для создания миксинов нельзя по той же причине, по которой их невозможно приспособить к уже имеющимся классам: класс не может иметь больше одного родителя, и в иерархии классов нет подходящего места, куда можно поместить миксин.

3. Интерфейсы позволяют создавать неиерархические каркасы типов.
Пояснение: иерархии типов прекрасно подходят для организации некоторых сущностей, но зато сущности других типов невозможно аккуратно уложить в строгую ие­рархию. Альтернативой им является раздутая иерархия классов.

4. Интерфейсы обеспечивают безопасное и мощное развитие функци­ональности с использованием шаблона «Декоратор». Паттерн «Декоратор» позволяет динамически (в ходе выполнения программы) добавлять объекту новые возможности (состояние и/или поведение) на основе композиции.

Эволюция интерфейсов в Java. Нововведения в Java 9.

Как менялись возможности интерфейсов в Java. Текущие итоги.

Interface

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

До Java 8, Java интерфейс — это конструкция, похожая на класс в котором отсутствуют реализации методов:

 public interface MyStrongInterface

Начнём с требований к интерфейсу, которые были заложены в Java, в момент создания языка. С большего они верны и сейчас. Хотя позже появились и расширения. Мы их также рассмотрим.

Перед словом interface может стоять любой модификатор видимости (private, protected, public, default — так будем называть отсутствие модификатора), но не всегда и не везде:

  • Если interface объявлен в отдельном файле (имя_интерфейса.java), то допускаются только модификаторы public (полная видимость интерфейса везде) или default (видимость интерфейса внутри пакета).
  • Если interface объявлен внутри другого класса (не интерфейса!), то допускаются все модификаторы видимости public, default, private и protected. Но, такой же вложенный интерфейс, объявленный внутри другого интерфейса (не класса!), может иметь только модификатор public или default. В случае использования модификаторов public или default, встроенный интерфейс будет виден с префиксом имени окружающего его класса или интерфейса.
 public interface SomeInterface extends Main.Printable < final int max = 5; public abstract void method(); public interface SomeAnother < . >> // Где-то в коде interface Showable extends SomeInterface.SomeAnother
  • Interface объявленный внутри другого класса или интерфейса также может иметь слово static в модификаторе доступа. Static ничего не меняет, но ошибкой не является.

Итак: изначально (до Java 8) интерфейсы могли содержать в себе только объявления констант и методов без реализации — сигнатуры (имя метода, типы входных параметров и тип возвращаемого значения).

 public interface Printable

Interface

  • перед переменной интерфейса может стоять модификатор доступа public static final (или одно из этих слов). Но может и ничего не стоять. Вне зависимости от этого, любая переменная, объявленная в интерфейсе — всегда трактуется компилятором как public static final. Другими словами — для компилятора это всегда константа. И состояния она хранить не может. Таким образом — интерфейс не может иметь своего состояния. Это есть принцип.
  • перед сигнатурой метода может стоять модификатор public abstract (или одно из этих слов). Но может и ничего не стоять. Вне зависимости от этого, любой метод, объявленный в интерфейсе — всегда трактуется компилятором как public abstract. Приведу тут незамысловатую, но удачную картинку с сайта javapoint.com:

Один интерфейс может расширять другой интерфейс:

 interface Showable extends Printable
  • Интерфейс может быть реализован неким классом. Это значит, что класс обязан предоставить реализацию (код) для всех методов, перечисленных в интерфейсе.
  • Один класс может реализовать несколько интерфейсов:
 class NewPower implements Printable, Comparable, Serializable < // Тут обязаны быть реализации всех методов всех интерфейсов . >

И вот пришла Java 8.

В Java 8 стало возможным добавлять реализацию методов в интерфейсах! Что бы это сделать, перед методом нужно поставить ключевое слово default или static. То есть, если вы хотите иметь в интерфейсе реализацию нестатического метода, то перед ним ставится модификатор default и теперь можно вставить код метода. А если хотите иметь реализацию статического метода, то пишете ключевое слово static. И снова можно вписать код.

Таким образом, если вы поставили default или static, то вы обязаны предоставить реализацию метода. Статический метод интерфейса можно вызвать так же, как и статический метод класса, указав впереди имя интерфейса. Оба новых модификатора всегда предполагают модификатор public. Его можно указывать, а можно и не указывать. Но он всегда подразумевается. Другими словами, эти интерфейсные методы, содержащие реализацию, видны всем.

 public interface SomeInterface < default int addOne( int i ) < return i + 1; >static int addTwo( int i ) < return i + 2; >public default int addThree( int i ) < return i + 3; >public static int addFour( int i ) < return i + 4; >> . SomeInterface.addTwo( 22 ); 

Методы, помеченные как default, можно переопределять в потомках (если переопределяете в классе, то модификатор default — не нужен). Если вы хотите что бы реализация такого метода перестала быть видна и в обязательном порядке требовалась новая реализация наследником, то можете создать промежуточный интерфейс или абстрактный класс и в нём снова объявить этот метод как абстрактный.

 public interface SomeInterface1 extends SomeInterface < int addOne( int i ); >// Или вот так public abstract class SomeAClass implements SomeInterface

Проделать подобное со статическим методом — не получится. Вы можете описать статический метод с такой же сигнатурой как у предка, но, тем самым, вы лишь скроете статический метод предка. Это будет другой метод. Вызываться будет строго тот статический метод, который определён в том типе, который вы используете для его вызова. Если вы не объявили идентичный статический метод в потомке, то через тип потомка вызовется статический метод его предка. А если объявили, то вызовется статический метод потомка. Это работает только для классов. Вызвать статический метод интерфейса предка, через интерфейс потомка — не получится. Детальнее в вызов статических методов вдаваться не будем, так как это лежит за рамками данной статьи.

А скоро придёт Java 9. И сделает реализацию методов интерфейсов ещё более удобной.

В Java 9 можно создавать private и private static методы. И писать их реализацию. Эти методы используются другими методами интерфейса, позволяя выносить туда дублирующийся код. Методы private и default могут обращаться к любым другим методам интерфейса. Методы private static и static только к статическим. Что понятно. Небольшое синтаксическое улучшение. Концептуально это ничего не меняет, но хороший стиль поддерживать проще.

 public interface SomeUseful < default int doWork() < return process( calculateTime() ); >private int calculateTime() < . >private int process() < int value = calculateSomething(); . >private static int calculateSomething() < . >> 

В сухом остатке можно сказать, что методы, которые перечисляются в интерфейсах, могут быть public и private. И могут иметь весь список модификаторов, хотя это и не обязательно. Те методы, которые помечены как private, не могут быть переопределены наследниками, ибо они им не видны. А значит, обязательно должны быть определены в интерфейсе. То есть, они в принципе не могут быть abstract. Хорошей практикой, кстати, считается указывать формально полный список комбинаций модификаторов перед методом или переменной.

Попробуем их перечислить, выделив те из них, которые синтаксически корректны:

  • public static — корректен. Требует реализации метода.
  • public abstract — корректен. Не требует реализации метода. Должен быть определён в потомке.
  • public default — корректен. Требует реализации метода. Может быть переопределён в потомке.
  • private static — корректен. Требует реализации метода.
  • private abstract — невозможен. private не виден потомкам, а значит должен быть определён тут же, в интерфейсе. И abstract, постулирующий реализацию в потомке, не может быть назначен.
  • private default — невозможен. Ибо default виден всем и может быть переопределён в потомке. А private — не виден потомкам. Противоречие.
  • private — корректен.

Особенности реализации методов интерфейсов в Java 8 и Java 9.

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

Суть её в том, что интерфейс по прежнему не имеет полей (fields). Ни полей класса (статических), ни полей объекта (экземпляров класса). Исключительно и только константы. А значит — никакой метод интерфейса не может что-то сохранить между своими вызовами. Говоря формально — у интерфейса по прежнему нет состояний. Методы интерфейса могут хранить лишь логику работы алгоритма, используя вызовы других своих методов для более аккуратного её представления. Всё. Интерфейс по прежнему является концептуально другой сущностью языка. И, в силу описанных выше ограничений, не способен сломать ранее реализованную логику. Не взирая на новые возможности. Что хорошо и важно.

Когда вы в классе наследнике переопределяете метод, обозначенный в интерфейсе как default, вы уже не должны употреблять модификатор default. И этот, переопределённый в классе метод, является полноценным методом класса, имеющим право изменять значения переменных класса/объекта. В отличие от своего интерфейсного варианта.

Подытожим. Итак, когда мы реализуем/расширяем интерфейс, содержащий методы с модификатором default у нас есть следующие варианты обращения с каждым из таких методов:

  • Использовать метод предка как есть
  • Переопределить (override — реализовать заново) метод
  • Переобъявить метод как абстрактный, тем самым затребовав его реализацию потомком

Множественное наследование.

Ещё один интересный момент, возникающий с появлением возможности методов интерфейса содержать программный код.

Любой Java класс может наследовать другой Java класс. Но всего один! А вот унаследовать и реализовать интерфейсов Java класс может сколько угодно. Интерфейс также может расширять много интерфейсов. Это всегда было и есть сейчас. Поскольку ранее интерфейсы не содержали кода, то никаких проблем не возникало. Теперь, как мы уже знаем, начиная с Java 8 интерфейсы могут содержать методы с кодом (реализованные методы). Другими словами — появилось то, что уже можно назвать множественным наследованием. И это значит, что ситуация приобрела и проблемы, привносимые этим множественным наследованием.

Множественное наследование — это возможность, присутствующая в некоторых других объектно-ориентированных языках (скажем в C++), суть которой состоит в том, что любой класс может наследовать сколько угодно других классов. Не интерфейсов, со своими интерфейсными ограничениями, а обычных, полноценных классов. Хотя интерфейсы и имеют ограничения, которые мы указали выше, тем не менее тот факт, что теперь они могут содержать код, уже привносит проблемы, присущие языкам с множественным наследованием.

Описать основную проблему несложно. Предположим ваш класс намерен реализовать два интерфейса, каждый из которых имеет какой-то default метод с сигнатурой, полностью идентичной методу из другого реализуемого интерфейса. То есть, ваш новый класс наследует две разные реализации одного и того же метода. Из одного и из другого интерфейса. И сам ваш класс/интерфейс пока не переопределяет этот метод. Ну и какой из них должен быть вызван если это понадобится? Из какого интерфейса?

Ответ — прост. Ни из какого. В Java вы получите незамысловатую ошибку: class … inherits unrelated defaults for …. Другими словами — не знаю кого вызвать. Определись. Для разрешения ситуации нужно действовать без фантазий — реализовать свой вариант метода. А в нём или написать свой код или внятно указать из какого предка код вызываем. Можно и то и другое. Выглядит это так:

 public interface InterfaceA < default void defaultMethod() < . >> public interface InterfaceB < default void defaultMethod() < . >> public class Impl implements InterfaceA, InterfaceB < public void defaultMethod() < // Тут своя реализация . >// Как вариант, можно использовать реализацию одного из предков. public void defaultMethod() < InterfaceB.super.defaultMethod(); . >> 

Отличие абстрактного класса от интерфейса

В конце, для логической точки, хотелось бы коротко ответить на сакральный вопрос отличия интерфейса от абстрактного класса с учётом последних веяний от Java 8 и Java 9:

  • У абстрактного класса могут быть конструкторы.
  • У абстрактного класса помимо констант могут быть поля (уровня класса или объекта).
  • У абстрактного класса методы могут изменять значения полей класса/объекта. По умному — класс может иметь состояния.

Урок 12. Основы Java. Интерфейсы (interface) в java

Рассмотрим, что такое интерфейсы в языке программирования java, а также поговорим о множественном наследовании интерфейсов.

Больше об интерфейсах в java

Вам також може сподобатися

Урок 12. Основы Java. Интерфейсы (interface) в java

Уроки по основам Java 3 44 721

Механизм обратного вызова(callbacks) широко распространен в программировании. При обратном вызове программист задает действия, которые должны

Урок 9. Основы языка JAVA. Наследование в java

Уроки по основам Java 3 5 629

Продолжаем курс по основам языка программирования java, и сегодня речь пойдет об абстрактных классах

Урок 9. Основы языка JAVA. Наследование в java

Уроки по основам Java 4 10 219

<<Предыдущий урок Список уроков Следующий урок>> Что такое исключения (Exception) в языке

Урок 9. Основы языка JAVA. Наследование в java

Уроки по основам Java 11 20 774

<<Предыдущий урок Список уроков Следующий урок>> Наследование в java Наследование в Java

startandroid, стартандроид, конструкторы в Java, объекты в java, java, eclipse, IntellijIdea, java программирование, видео уроки java, программирование на java, java android, как стать java программистом, приложения на java, программирование java для чайников, разработка java, java для начинающих, java для чайников

Уроки по основам Java 18 87 574

<<Предыдущий урок Список уроков Следующий урок>> Компиляция и выполнение java программы Продолжаем

#7. Вводный курс по JAVA. Объекты

Уроки по основам Java 24 17 836

<<Предыдущий урок Список уроков Следующий урок>> Объекты Почти весь код, написанный на языке

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

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