Для чего нужны интерфейсы в java
27. Java – Интерфейсы
Интерфейс — это ссылочный тип в Java. Он схож с классом. Это совокупность абстрактных методов. Класс реализует интерфейс, таким образом наследуя абстрактные методы интерфейса.
Вместе с абстрактными методами интерфейс в Java может содержать константы, обычные методы, статические методы и вложенные типы. Тела методов существуют только для обычных методов и статических методов.
Далее разберём зачем нужны интерфейсы в Java и для чего используются, разницу абстрактного класса и интерфейса.
Содержание
Написание интерфейса схоже с написанием класса. Но класс описывает атрибуты и поведения объекта. И интерфейс содержит поведения, которые класс реализует.
Если класс, реализующий интерфейс, не является абстрактным, все методы интерфейса должны быть определены в классе.
Чем похожи класс и интерфейс?
Интерфейс схож с классом следующим образом:
Чем отличается класс от интерфейса?
Однако, интерфейс всё же отличается от класса. Отличие интерфейса от класса в Java:
Объявление интерфейсов
Ключевое слово interface используется для объявления интерфейса. Вот пример того, как можно создать интерфейс:
Пример 1
Интерфейсы имеют следующие свойства:
Пример 2
Реализация интерфейса
Когда класс реализует интерфейс, вы можете представить себе, что класс словно подписывает контракт с интерфейсом, соглашаясь совершить конкретные его поведения. Если класс не исполняет все поведения интерфейса, то класс должен объявить себя абстрактным.
Класс использует ключевое слово implements для реализации интерфейса. Ключевое слово implements появляется при объявлении класса в его расширенной части.
Пример
При переопределении методов в интерфейсе, нужно следовать некоторым правилам:
При реализации интерфейсов есть некоторые правила:
Расширение интерфейсов
Интерфейс может расширять другой интерфейс так же, как класс другой класс. Ключевое слово extends используется для расширения интерфейса, и дочерний интерфейс наследует методы родительского интерфейса.
Приведённый интерфейс Sports расширен интерфейсами Hockey и Football.
Пример
Интерфейс Hockey имеет четыре метода, но он наследует два из Sports; таким образом, класс, который реализует Hockey, должен реализовать все шесть методов. Подобно этому, класс, который реализует Football, должен определить три метода из Football и два метода из Sports.
Расширение множества интерфейсов
Класс в Java может расширить только один родительский класс. Множественное наследование невозможно. Однако интерфейсы не классы, и интерфейс может расширить более чем один родительский интерфейс.
Ключевое слово extends используется лишь раз, а родительские интерфейсы объявляются через запятую.
Например, если интерфейс Hockey расширил и Sports, и Event, то объявление выглядело бы так:
Интерфейсы тегов
Самое распространённое использование расширения интерфейсов происходит тогда, когда родительский интерфейс не содержит каких-либо методов. Например, интерфейс MouseListener в пакете java.awt.event расширил java.util.EventListener, который определяется так:
Интерфейс без методов в нём называется интерфейсом тегов. Есть две простые дизайнерские цели для интерфейсов тегов:
Создаёт общего родителя – как в случае с интерфейсом EventListener, который расширяется множеством других в Java API, вы можете использовать интерфейс тегов, чтобы создать общего родителя среди группы интерфейсов. Например, когда интерфейс расширяет EventListener, то JVM знает, что этот конкретный интерфейс будет использоваться в сценарии делегирования событий.
Добавляет тип данных в класс – эта ситуация является источником термина «тегирование». Класс, который реализует интерфейс тегов, не должен определять какие-либо методы (т.к. интерфейс не имеет таковых), но класс становится типом интерфейса через полиморфизм.
Интерфейсы Java – что это такое?
Интерфейс Java немного похож на класс, за исключением того, что интерфейс может содержать только сигнатуры методов и поля. Интерфейс не может содержать реализацию методов, только подпись(имя, параметры и исключения) метода.
Вы можете использовать их как способ достижения полиморфизма.
Пример интерфейса Java
Вот простой пример интерфейса Java:
Как видите, интерфейс объявляется с использованием ключевого слова. Как и в случае с классами, интерфейс может быть объявлен как общедоступный или пакетный(без модификатора доступа).
Приведенный выше пример содержит одну переменную и один метод. Доступ к переменной можно получить непосредственно из интерфейса, например так:
Как видите, доступ к переменной очень похож на доступ к статической переменной в классе.
Однако этот метод должен быть реализован некоторым классом, прежде чем вы сможете получить к нему доступ. Следующий раздел объяснит, как это сделать.
Реализация интерфейса
Вот класс, который реализует интерфейс MyInterface, показанный выше:
Обратите внимание, реализует часть MyInterface вышеупомянутого объявления класса. Это сообщает компилятору Java, что класс MyInterfaceImpl реализует интерфейс MyInterface.
Класс, реализующий интерфейс, должен реализовывать все методы, объявленные в нем. Методы должны иметь точно такую же сигнатуру(имя + параметры), как объявлено. Класс не должен реализовывать(объявлять) переменные интерфейса. Только методы.
Экземпляры интерфейса
Как только класс Java реализует интерфейс Java, вы можете использовать экземпляр этого класса в качестве экземпляра. Вот пример:
Обратите внимание, как объявлена переменная типа интерфейса MyInterface, в то время как созданный объект имеет тип MyInterfaceImpl. Java позволяет это, потому что класс MyInterfaceImpl реализует MyInterface. Затем вы можете ссылаться на экземпляры класса MyInterfaceImpl как на экземпляры MyInterface.
Вы не можете создавать экземпляры интерфейса самостоятельно. Вы должны всегда создавать экземпляр некоторого класса, который реализует интерфейс, и ссылаться на этот экземпляр как на экземпляр интерфейса.
Реализация нескольких интерфейсов
Класс Java может реализовывать несколько интерфейсов. В этом случае класс должен реализовать все методы, объявленные во всех реализованных интерфейсах. Вот пример:
Этот класс реализует два, называемых MyInterface и MyOtherInterface. Вы перечисляете имена для реализации после ключевого слова Implements, разделенных запятой.
Если интерфейсы не находятся в тех же пакетах, что и реализующий класс, вам также необходимо импортировать их. Они импортируются с помощью инструкции импорта. Например:
реализованные классом выше:
Как видите, каждый интерфейс содержит один метод. Эти методы реализуются классом MyInterfaceImpl.
Перекрывающиеся подписи метода
Если класс реализует несколько интерфейсов, существует риск, что некоторые могут содержать методы с одинаковой сигнатурой(имя + параметры). Поскольку класс может реализовывать метод только с заданной сигнатурой только один раз, это может привести к проблемам.
Спецификация не дает никакого решения этой проблемы. Вам решать, что делать в этой ситуации.
Какие типы Java могут реализовывать интерфейсы?
Следующие типы могут реализовывать интерфейсы:
Переменные интерфейса
Интерфейс может содержать как переменные, так и константы. Однако часто не имеет смысла помещать переменные. В некоторых случаях может иметь смысл определять константы.
Особенно, если эти константы должны использоваться классами, например, в вычислениях, или в качестве параметров для некоторых методов. Я советую вам избегать размещения переменных.
Все переменные в интерфейсе являются открытыми, даже если вы опустите ключевое слово public в объявлении переменной.
Методы интерфейса
Интерфейс может содержать одно или несколько объявлений метода. Как упоминалось ранее, он не может указывать какую-либо реализацию для этих методов. Это зависит от классов, чтобы определить реализацию.
Все методы являются открытыми, даже если вы опустите ключевое слово public в объявлении метода.
Методы интерфейса по умолчанию
До Java 8 интерфейсы не могли содержать реализацию методов, а содержали только сигнатуры методов. Однако это приводит к некоторым проблемам, когда API необходимо добавить метод.
Если API просто добавляет метод, все классы, которые реализуют интерфейс, должны реализовать этот новый метод. Это хорошо, если все реализующие классы расположены в API. Но если некоторые реализующие классы находятся в клиентском коде API(код, который использует API), то этот код нарушается.
Посмотрите на этот интерфейс и представьте, что он является частью, например, API с открытым исходным кодом, который многие приложения используют внутри:
Теперь представьте, что проект использует этот API и реализовал ResourceLoader следующим образом:
Если разработчик API хочет добавить еще один метод в ResourceLoader, то класс FileLoader будет нарушен при обновлении этого проекта до новой версии API.
Чтобы облегчить эту проблему, Java 8 добавила концепцию. Метод интерфейса по умолчанию может содержать реализацию этого метода по умолчанию. Классы, которые реализуют интерфейс, но не содержат реализации по умолчанию, автоматически получат реализацию метода по умолчанию.
Вы помечаете метод как метод по умолчанию, используя ключевое слово. Вот пример:
В этом примере добавляется метод загрузки по умолчанию(Path). В примере не учитывается фактическая реализация(внутри тела метода), потому что это не очень интересно. Важно то, как вы объявляете метод по умолчанию.
Класс может переопределить реализацию метода по умолчанию просто путем явной реализации этого метода, как это обычно делается.
Статические методы интерфейса
Статические методы в Java должны иметь реализацию.
Вызов статического метода выглядит и работает так же, как вызов статического метода в классе. Вот пример вызова статического метода print() из вышеуказанного MyInterface:
Статические методы в интерфейсах могут быть полезны, когда у вас есть несколько служебных методов, которые вы хотели бы сделать доступными. Например, Vehicle может иметь статический метод printVehicle(Vehicle v).
Интерфейсы и наследование
Интерфейс Java может наследоваться от другого, как классы могут наследовать от других классов. Вы указываете наследование, используя ключевое слово extends.
MySubInterface расширяет MySuperInterface. Это означает, что MySubInterface наследует все поля и методы от MySuperInterface. Это означает, что если класс реализует MySubInterface, этот класс должен реализовать все методы, определенные как в MySubInterface, так и в MySuperInterface.
Можно определить методы в подинтерфейсе с той же сигнатурой(имя + параметры), что и методы, определенные в суперинтерфейсе, если вы считаете, что это будет лучше в вашем проекте.
В отличие от классов, интерфейсы могут наследоваться от нескольких суперинтерфейсов. Вы указываете это, перечисляя имена всех интерфейсов для наследования, разделенных запятой. Класс, реализующий интерфейс, который наследуется от нескольких, должен реализовывать все методы интерфейса и его суперинтерфейсов.
Вот пример интерфейса Java, который наследуется от нескольких интерфейсов:
Как и при реализации нескольких интерфейсов, не существует правил для того, как вы обрабатываете ситуацию, когда несколько суперинтерфейсов имеют методы с одинаковой сигнатурой(имя + параметры).
Наследование и методы по умолчанию
Методы интерфейса по умолчанию немного усложняют правила наследования интерфейса. Хотя обычно класс может реализовать несколько интерфейсов, даже если интерфейсы содержат методы с одинаковой сигнатурой, это невозможно, если один или несколько из этих методов являются методами по умолчанию. Другими словами, если два интерфейса содержат одну и ту же сигнатуру метода(имя + параметры) и один из интерфейсов объявляет этот метод как метод по умолчанию, класс не может автоматически реализовать оба интерфейса.
Ситуация такая же, если интерфейс расширяет(наследует) несколько интерфейсов, и один или несколько из этих интерфейсов содержат методы с одинаковой сигнатурой, а один из суперинтерфейсов объявляет перекрывающийся метод как метод по умолчанию.
В обеих вышеупомянутых ситуациях компилятор Java требует, чтобы класс, реализующий интерфейс(ы), явно реализовал метод, который вызывает проблему. Таким образом, нет сомнений в том, какую реализацию будет иметь класс. Реализация в классе имеет приоритет над любыми реализациями по умолчанию.
Интерфейсы и полиморфизм
Java-интерфейсы – это способ достижения полиморфизма. Полиморфизм – это концепция, которая требует некоторой практики и мысли, чтобы овладеть ею. По сути, полиморфизм означает, что экземпляр класса(объекта) можно использовать так, как если бы он был разных типов. Здесь тип означает либо класс, либо интерфейс.
Посмотрите на эту простую диаграмму классов:
Приведенные выше классы являются частями модели, представляющей различные типы транспортных средств и водителей, с полями и методами. Это ответственность этих классов – моделировать эти сущности из реальной жизни.
Теперь представьте, что вам нужно иметь возможность хранить эти объекты в базе данных, а также сериализовать их в XML, JSON или другие форматы. Вы хотите, чтобы это было реализовано с использованием одного метода для каждой операции, доступного для каждого объекта Car, Truck или Vehicle. Метод store(), метод serializeToXML() и метод serializeToJSON().
Пожалуйста, забудьте на некоторое время, что реализация этой функциональности как методов непосредственно на объектах может привести к грязной иерархии классов. Просто представьте, что именно так вы хотите выполнить операции.
Где в приведенной выше схеме вы бы поместили эти три метода, чтобы они были доступны для всех классов?
Одним из способов решения этой проблемы было бы создание общего суперкласса для класса Vehicle и Driver, который имеет методы хранения и сериализации. Однако это приведет к концептуальному беспорядку. Иерархия классов больше не будет моделировать транспортные средства и водителей, но также будет привязана к механизмам хранения и сериализации, используемым в вашем приложении.
Лучшим решением было бы создать некоторые интерфейсы с включенными методами хранения и сериализации и позволить классам реализовать эти интерфейсы. Вот примеры таких интерфейсов:
Когда каждый класс реализует эти два интерфейса и их методы, вы можете получить доступ к методам этих интерфейсов, приведя объекты к экземплярам типов интерфейса. Вам не нужно точно знать, к какому классу относится данный объект, если вы знаете, какой интерфейс он реализует.
Как вы уже, наверное, можете себе представить, интерфейсы предоставляют более понятный способ реализации сквозных функций в классах, чем наследование.
Универсальные интерфейсы
Этот интерфейс представляет интерфейс, который содержит единственный метод, который называется производством(), который может произвести единственный объект. Так как возвращаемое значение yield() – Object, он может возвращать любой объект Java.
Как вы можете видеть, поскольку универсальный тип для экземпляра CarProducer установлен на Car, больше нет необходимости приводить объект, возвращенный из метода yield(), поскольку в объявлении исходного метода в интерфейсе MyProducer указано, что этот метод возвращает тот же тип, который указан в универсальном типе при использовании.
Как вы можете видеть, по-прежнему нет необходимости приводить объект, возвращаемый функциейручки(), поскольку реализация CarProducer объявляет, что это экземпляр Car.
Обобщения Java более подробно описаны в моем руководстве по обобщению Java.
Функциональные интерфейсы
Интерфейсы в Java и немного о полиморфизме
Интерфейс – это контракт, в рамках которого части программы, зачастую написанные разными людьми, взаимодействуют между собой и со внешними приложениями. Интерфейсы работают со слоями сервисов, безопасности, DAO и т.д. Это позволяет создавать модульные конструкции, в которых для изменения одного элемента не нужно трогать остальные.
Новички часто спрашивают, чем интерфейс отличается от абстрактного класса. Интерфейсы в Java компенсируют отсутствие множественного наследования классов. У класса-потомка может быть только один абстрактный класс-родитель, а вот интерфейсов класс может применять (имплементировать) сколько угодно.
Интерфейс на Java объявляют примерно так же, как и класс:
В имплементирующем интерфейс классе должны быть реализованы все предусмотренные интерфейсом методы, за исключением методов по умолчанию.
Методы по умолчанию впервые появились в Java 8. Их обозначают модификатором default. В нашем примере это метод say_goodbye, реализация которого прописана прямо в интерфейсе. Дефолтные методы изначально готовы к использованию, но при необходимости их можно переопределять в применяющих интерфейс классах.
Функциональный интерфейс Java
Если у интерфейса только один абстрактный метод, перед нами функциональный интерфейс. Его принято помечать аннотацией @FunctionalInterface, которая указывает компилятору, что при обнаружении второго абстрактного метода в этом интерфейсе нужно сообщить об ошибке. Стандартных (default) методов у интерфейса может быть множество – в том числе принадлежащих классу java.lang.Object.
Как выглядит функциональный интерфейс на Java:
Функциональные интерфейсы появились в Java 8. Они обеспечили поддержку лямбда-выражений, использование которых делает код лаконичным и понятным:
В той же версии появились пакеты встроенных интерфейсов: java.util.function и java.util.stream.
Реализация интерфейсов классами Java
Допустим, есть интерфейс Edible, которым пользуются классы Fruit, Vegetable, Fish. Экземпляры этих классов можно создавать так:
Обратите внимание на разницу в конструкторах: для фруктов задаём название и сорт, для рыбы – название, район вылова и вес порции в граммах. Но ссылки на оба объекта храним в переменных одного типа – «Съестное».
Интерфейсы и полиморфизм
Пример выше иллюстрирует один из трех основополагающих принципов ООП — полиморфизм. Мы раскрыли одно и то же явление — съедобность — через несколько классов, свойства и методы которых частично отличаются. Представление разных форм одного явления — это и есть полиморфизм. Если нужно, такую систему всегда можно расширить и скорректировать. В нашем случае — добавить новые виды съестного и методы их приготовления.
В Java полиморфизм можно реализовать через:
Интерфейс выручает в ситуации, когда при создании переменной мы не знаем, объект какого класса ей будет присвоен.
Интерфейс – это контракт, в рамках которого части программы, зачастую написанные разными людьми, взаимодействуют между собой и со внешними приложениями. Интерфейсы работают со слоями сервисов, безопасности, DAO и т.д. Это позволяет создавать модульные конструкции, в которых для изменения одного элемента не нужно трогать остальные.
Новички часто спрашивают, чем интерфейс отличается от абстрактного класса. Интерфейсы в Java компенсируют отсутствие множественного наследования классов. У класса-потомка может быть только один абстрактный класс-родитель, а вот интерфейсов класс может применять (имплементировать) сколько угодно.
Интерфейс на Java объявляют примерно так же, как и класс:
В имплементирующем интерфейс классе должны быть реализованы все предусмотренные интерфейсом методы, за исключением методов по умолчанию.
Методы по умолчанию впервые появились в Java 8. Их обозначают модификатором default. В нашем примере это метод say_goodbye, реализация которого прописана прямо в интерфейсе. Дефолтные методы изначально готовы к использованию, но при необходимости их можно переопределять в применяющих интерфейс классах.
Функциональный интерфейс Java
Если у интерфейса только один абстрактный метод, перед нами функциональный интерфейс. Его принято помечать аннотацией @FunctionalInterface, которая указывает компилятору, что при обнаружении второго абстрактного метода в этом интерфейсе нужно сообщить об ошибке. Стандартных (default) методов у интерфейса может быть множество – в том числе принадлежащих классу java.lang.Object.
Как выглядит функциональный интерфейс на Java:
Функциональные интерфейсы появились в Java 8. Они обеспечили поддержку лямбда-выражений, использование которых делает код лаконичным и понятным:
В той же версии появились пакеты встроенных интерфейсов: java.util.function и java.util.stream.
Реализация интерфейсов классами Java
Допустим, есть интерфейс Edible, которым пользуются классы Fruit, Vegetable, Fish. Экземпляры этих классов можно создавать так:
Обратите внимание на разницу в конструкторах: для фруктов задаём название и сорт, для рыбы – название, район вылова и вес порции в граммах. Но ссылки на оба объекта храним в переменных одного типа – «Съестное».
Интерфейсы и полиморфизм
Пример выше иллюстрирует один из трех основополагающих принципов ООП — полиморфизм. Мы раскрыли одно и то же явление — съедобность — через несколько классов, свойства и методы которых частично отличаются. Представление разных форм одного явления — это и есть полиморфизм. Если нужно, такую систему всегда можно расширить и скорректировать. В нашем случае — добавить новые виды съестного и методы их приготовления.
В Java полиморфизм можно реализовать через:
Интерфейс выручает в ситуации, когда при создании переменной мы не знаем, объект какого класса ей будет присвоен.
Интерфейсы
— Привет, Амиго! Сегодня у тебя день открытий. Новая и интересная тема – это интерфейсы.
— Ага. День настолько чудесный, что я приду домой и приму ванну полную воды.
2) Содержит только абстрактные методы (слово abstract писать не нужно).
3) На самом деле у интерфейсов все методы — public.
Класс ChessItem объявлен абстрактным: он реализовал все унаследованные методы, кроме draw.
— Интересно. А зачем нужны интерфейсы? Когда их используют?
— У интерфейсов есть два сильных преимущества по сравнению с классами:
1) Отделение «описания методов» от их реализации.
С помощью интерфейсов, это деление можно усилить еще больше. Мы сделаем специальный «класс для всех», и второй «класс для своих», который унаследуем от первого. Вот как это примерно будет:
Мы разбили наш класс на два: интерфейс и класс, унаследованный от интерфейса.
— И в чем тут преимущество?
— Один и тот же интерфейс могут реализовывать (наследовать) различные классы. И у каждого может быть свое собственное поведение. Так же как ArrayList и LinkedList – это две различные реализации интерфейса List.
Таким образом, мы скрываем не только различные реализации, но и даже сам класс, который ее содержит (везде в коде может фигурировать только интерфейс). Это позволяет очень гибко, прямо в процессе исполнения программы, подменять одни объекты на другие, меняя поведение объекта скрытно от всех классов, которые его используют.
Это очень мощная технология в сочетании с полиморфизмом. Сейчас далеко не очевидно, зачем так нужно делать. Ты должен сначала столкнуться с программами, состоящими из десятков или сотен классов, чтобы понять, что интерфейсы способны существенно упростить тебе жизнь.
2) Множественное наследование.
В Java все классы могут иметь только одного класса-родителя. В других языках программирования, классы часто могут иметь несколько классов-родителей. Это очень удобно, но приносит так же много проблем.
В Java пришли к компромиссу – запретили множественное наследование классов, но разрешили множественное наследование интерфейсов. Интерфейс может иметь несколько интерфейсов-родителей. Класс может иметь несколько интерфейсов-родителей и только один класс-родитель.