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

от admin

Интерфейсы в Java. Полное руководство

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

Например, нам нужно создать рисунок, который состоит из нескольких фигур. Для этого мы можем создать интерфейс Shape и определить методы для работы с объектами Shape. Для простоты, давайте определим только два метода — draw() — чтобы нарисовать фигуру и метод getArea() , который будет возвращать площадь фигуры.

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

На основании вышеуказанных требований, наш интерфейс Shape будет выглядеть следующим образом:

Java Language
Интерфейсы

Объявление интерфейса с использованием ключевого слова interface :

Переопределить аннотацию

Это заставляет компилятор проверить, что мы переопределяем и не позволяет программе определять новый метод или испортить подпись метода.

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

В этом примере классы Cat и Dog должны определять метод getSound() поскольку методы интерфейса являются абстрактно абстрактными (за исключением методов по умолчанию).

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

Реализация нескольких интерфейсов

Класс Java может реализовывать несколько интерфейсов.

Обратите внимание, как класс Cat должен реализовывать наследованные abstract методы в обоих интерфейсах. Кроме того, обратите внимание, как класс может практически реализовать столько интерфейсов, сколько необходимо (предел 65535 из-за ограничения JVM ).

  1. Все переменные, объявленные в интерфейсе, являются public static final
  2. Все методы, объявленные в методах интерфейса, являются public abstract (этот оператор действителен только через Java 7. Из Java 8 вам разрешено иметь методы в интерфейсе, которые не обязательно должны быть абстрактными, такие методы известны как методы по умолчанию )
  3. Интерфейсы не могут быть объявлены final
  4. Если несколько интерфейсов объявляют метод, имеющий идентичную подпись, то он эффективно обрабатывается только как один метод, и вы не можете отличить, от какого метода интерфейса
  5. Соответствующий файл InterfaceName.class будет создан для каждого интерфейса, после компиляции

Расширение интерфейса

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

Теперь класс, реализующий ExtendedResourceService , должен будет реализовать как getResource() и updateResource() .

Расширение нескольких интерфейсов

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

В этом случае для класса, реализующего ExtendedResourceService , потребуется реализовать getResource() , getAlternateResource() и updateResource() .

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

Предположим, вы хотите определить интерфейс, который позволяет публиковать / потреблять данные для разных типов каналов (например, AMQP, JMS и т. Д.), Но вы хотите, чтобы их можно было отключить .

Давайте определим базовый интерфейс ввода-вывода, который можно повторно использовать в нескольких реализациях:

Теперь я могу создать экземпляр этого интерфейса, но поскольку у нас нет стандартных реализаций для этих методов, ему понадобится реализация, когда мы его создадим:

Мы также можем сделать что-то более полезное с этим интерфейсом, допустим, мы хотим использовать его для переноса некоторых основных функций RabbitMQ:

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

Теперь давайте использовать VisitCounter:

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

Полезность интерфейсов

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

Один из способов сделать это — использовать интерфейсы. Это позволило бы вызвать тот же метод для всех классов

Любой класс, который implements Animal также должен иметь метод getSound() , но все они могут иметь разные реализации

Теперь у нас есть три разных класса, каждый из которых имеет метод getSound() . Поскольку все эти классы implement интерфейс Animal , который объявляет метод getSound() , любой экземпляр Animal может иметь на нем getSound()

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

Поскольку порядок массива — Dog , Cat , а затем Bird , «Woof Meow Chirp» будет напечатан на консоли.

Интерфейсы также могут использоваться как возвращаемое значение для функций. Например, возвращение Dog если вход «собака» , Cat если вход «cat» , и « Bird если это «птица» , а затем печать звука этого животного может быть выполнена с использованием

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

Внедрение интерфейсов в абстрактном классе

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

Начиная с Java 8, interface может объявлять по default реализации методов, что означает, что метод не будет abstract , поэтому любые конкретные подклассы не будут вынуждены реализовать этот метод, но наследуют реализацию по default если не переопределены.

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

Представленные в Java 8 методы по умолчанию — это способ указания реализации внутри интерфейса. Это можно использовать, чтобы избежать типичного класса «Base» или «Abstract», предоставляя частичную реализацию интерфейса и ограничивая иерархию подклассов.

Реализация шаблона наблюдателя

Например, можно реализовать шаблон Observer-Listener непосредственно в интерфейсе, предоставляя большую гибкость реализующим классам.

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

Проблема с алмазами

Компилятор в Java 8 знает о проблеме с алмазом, которая возникает, когда класс реализует интерфейсы, содержащие метод с той же сигнатурой.

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

По-прежнему существует проблема с методами с тем же именем и параметрами с разными типами возвращаемых данных, которые не будут компилироваться.

Использовать методы по умолчанию для решения проблем совместимости

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

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

Дополнительные сведения см. В разделе « Способы по умолчанию» .

Модификаторы в интерфейсах

Руководство по стилю Java Java:

Модификаторы не должны выписываться, если они неявные.

(См. Модификаторы в стандартном стандартном коде Oracle для контекста и ссылку на фактический документ Oracle.)

Это руководство по стилю применяется, в частности, к интерфейсам. Рассмотрим следующий фрагмент кода:

переменные

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

методы

  1. Все методы, которые не обеспечивают реализацию, являются неявно public и abstract .
  1. Все методы со static или модификатором по default должны обеспечивать реализацию и неявно public .

После того, как все вышеуказанные изменения будут применены, мы получим следующее:

Усиление параметров ограниченного типа

Параметры ограниченного типа позволяют вам устанавливать ограничения на аргументы общего типа:

Но параметр типа может привязываться только к одному типу класса.

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

Это усиливает привязку, что потенциально требует аргументов типа для получения нескольких типов.

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

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

История эволюции интерфейсов в Java

Интерфейсы в Java 1.0 были достаточно простыми по сравнению с тем, какие они сейчас. Они могли содержать лишь два типа элементов: константы и публичные абстрактные методы.

Поля-константы

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

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

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

Абстрактные методы

Наиболее важными элементами интерфейса являются его методы. Методы интерфейса также отличаются от методов обычного класса:

  • У методов нет тела
  • Реализация методов предоставляется классами, реализующими данный интерфейс
  • Методы считаются публичными и абстрактными даже, если это не задано явно
  • Методы не могут быть финальными, поскольку в Java комбинация модификаторов abstract и final запрещена

Вложенность

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

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

Перечисления и аннотации

В Java 5 были введены ещё два типа: Перечисления и Аннотации. Они также могут быть помещены внутрь интерфейсов.

Обобщенные типы

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

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

Интерфейс Box работает независимо от того, используете ли вы его для хранения объектов типа String, Integer, List, Shoe или каких-либо других.

Читать:
Как перенести закладки опера с телефона на телефон

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

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

При использовании статических методов в интерфейсах вам нужно также предоставить реализацию тела метода. Чтобы задействовать в интерфейсе такой метод, просто используйте ключевое слово static. Статические методы считаются публичными по умолчанию.

Наследование статических методов

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

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

Почему это полезно

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

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

Вы можете найти примеры использования данного подхода прямо в JDK: интерфейс java.util.Collection и сопутствующий ему утилитный класс java.util.Collections.

Со статическими методами в интерфейсах этот подход больше не актуален, не нужен и не рекомендован. Теперь вы можете иметь все в одном месте.

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

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

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

Хотя есть одно исключение. В интерфейсе не может быть методов по умолчанию с такой же сигнатурой, как у методов toString, equals и hashCode класса Object. Взгляните на ответ Брайана Гетца, чтобы понять обоснованность такого решения: Allow default methods to override Object’s methods.

Почему это полезно

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

У интерфейсов есть одна проблема. Как только вы предоставите свой API другим людям, он навсегда «окаменеет» (его нельзя будет изменить безболезненно).

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

Конфликты

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

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

Итак, как наш класс узнает, какую из двух различных реализаций использовать?

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

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

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

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

К сожалению в Java 8 интерфейс не может содержать приватные методы. Это означает, что вы можете использовать:

Интерфейсы в Java

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

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

Создание и особенности

Создаётся интерфейс, как и любой класс:

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

В интерфейс удобно выносить некие абстрактные свойства и функции объектов. Например, метод eat(). Кушать могут все создания на земле. И при реализации отдельных видов, например, животных, можно использовать интерфейс.

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

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

public class Dog implements Eating <
public void eat() <
System.out.println(“Собака грызёт кость”);
>
public static void main(String[] args) <
Dog dog = new Dog();
Dog.eat();
>
>

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

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

public interface Walking <
public default walk() <
System.out.println(“Ходит на 4 ногах”);
>
>

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

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

public class Dog implements Eating, Walking, Jumping <
>

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

public interface EatingMeat extends Eating <
>

Применение интерфейсов

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

Очень часто интерфейсы используют для исполнения механизма обратного вызова или callback. Особенно в среде разработки под Андроид. Такой подход позволяет связывать между собой участки кода в разных классах и даже передавать данные.

public class DogEating <
interface CallbackDogFinishedEat <
void dogFinishedEat();
>
CallbackDogFinishedEatcallbackDogFinishedEat;
public void registerCall(CallbackDogFinishedEatcallbackDogFinishedEat) <
this.callbackDogFinishedEat = callbackDogFinishedEat;
>
void doDogEating() <
//выполнение долгой задачи, собака ест
сallbackDogFinishedEat.dogFinishedEat();
>
>

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

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

public class DogIsFed implements DogEating.CallbackDogFinishedEat <
public void dogFinishedEat() <
System.out.println(“Собака накормлена”);
>
>

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

public class Main <
public static void main(String[] args) <
DogEatingdogEating = new DogEating();
DogIsFeddogIsFed = new DogIsFed;
dogEating.registerCall(dogIsFed);
dogEating.doDogEating();
>
>

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

Заключение

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

Похожие статьи