можно ли в объявлениях интерфейсов создать что то кроме методов
Интерфейсы
Ключевое слово interface используется для создания полностью абстрактных классов. Создатель интерфейса определяет имена методов, списки аргументов и типы возвращаемых значений, но не тела методов.
Наличие слова interface означает, что именно так должны выглядеть все классы, которые реализуют данный интерфейс. Таким образом, любой код, использующий конкретный интерфейс, знает только то, какие методы вызываются для этого интерфейса, но не более того.
Чтобы создать интерфейс, используйте ключевое слово interface вместо class. Как и в случае с классами, вы можете добавить перед словом interface спецификатор доступа public (но только если интерфейс определен в файле, имеющем то же имя) или оставить для него дружественный доступ, если он будет использоваться только в пределах своего пакета. Интерфейс может содержать поля, но они автоматически являются статическими (static) и неизменными (final). Все методы и переменные неявно объявляются как public.
Класс, который собирается использовать определённый интерфейс, использует ключевое слово implements. Оно указывает, что интерфейс лишь определяет форму, а вам нужно наполнить кодом. Методы, которые реализуют интерфейс, должны быть объявлены как public.
Интерфейсов у класса может быть несколько, тогда они перечисляются за ключевым словом implements и разделяются запятыми.
Интерфейсы могут вкладываться в классы и в другие интерфейсы.
Если класс содержит интерфейс, но не полностью реализует определённые им методы, он должен быть объявлен как abstract.
Интерфейсы — это не классы. С помощью ключевого слова new нельзя создать экземпляр интерфейса:
Но можно объявлять интерфейсные переменные:
При этом интерфейсная переменная должна ссылаться на объект класса, реализующего данный интерфейс.
Рассмотрим быстрый пример создания интерфейса. Выберите в меню File | New | Interface и придумайте имя для нового интерфейса. В полученной заготовке добавьте два имени метода (только имена, без кода).
Создайте или откройте какой-нибудь класс, к которому нужно применить интерфейс, и добавьте к нему implements SimpleInterface. Среда разработки подчеркнёт красной линией имя класса и предложит добавить методы, которые требуются интерфейсом. Соглашаемся и получаем результат:
Среда разработки сгенерировала два метода и использовала в качестве возвращаемых результатов значения по умолчанию. Это могут быть и нулевые значения и null. Осталось подправить шаблоны созданных методов под свои задачи. Например, так:
Здесь важно понять роль интерфейса. Мы лишь придумываем имена, а класс уже реализует нужную задачу. Для примера можно создать в интерфейсе метод play() для класса Пианино и класса Гитара, так как играть можно на обеих инструментах. Но код в методах будет отличаться, так как принцип игры на инструментах совершенно разный.
Константы в интерфейсах
Интерфейсы можно использовать для импорта констант в несколько классов. Вы просто объявляете интерфейс, содержащий переменные с нужными значениями. При реализации интерфейса в классе имена переменных будут помещены в область констант. Поля для констант становятся открытыми и являются статическими и конечными (модификаторы public static final). При этом, если интерфейс не будет содержать никаких методов, то класс не будет ничего реализовывать. Хотя данный подход не рекомендуют использовать.
Расширение интерфейсов
Интерфейс может наследоваться от другого интерфейса через ключевое слово extends.
Методы обратного вызова
Интерфейсы часто используются для создания методов обратного вызова (callback). Рассмотрим такой пример. Создадим новый класс SubClass с интерфейсом MyCallback:
У интерфейса мы определили один метод callBackReturn(). Далее в классе мы создали объект интерфейса и инициализировали его в конструкторе класса. В классе также был создан метод doSomething(), в котором может содержаться какой-то сложный код. В конце метода вызывается метод интерфейса. В данном случае мы сами создали метод и знаем его код. Но во многих случаях, вы будете использовать готовый метод какого-то класса и вы не будете знать, что именно содержится в этом методе. Вам надо только знать, что такой метод существует, например, из документации и он выполняет конкретную задачу.
Переходим в код активности и подключаем интерфейс через ключевое слово implements:
Среда разработки поможет вставить шаблон метода интерфейса.
Теперь мы можем использовать метод обратного вызова callBackReturn() для решения своих задач. Допустим у нас есть текстовая метка и кнопка. При щелчке выполняется какой-то сложный код из класса SubClass. Когда он закончит работу, то сработает метод обратного вызова callBackReturn(), в котором пропишем нужные действия.
Слушатели
Очень часто для интерфейса используют слово Listener, например, у кнопки есть интерфейс OnClickListener.
Мы можем создавать подобные слушатели для собственных классов.
Также интерфейсы часто используются при работе с фрагментами.
Требуется определённая практика и опыт, чтобы быстро разбираться в коде с использованием интерфейсов, так как приходится отслеживать цепочку вызовов из разных классов. Но бояться их не нужно.
Интерфейсы
Содержание
Интерфейс — это ссылочный тип в 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 знает, что этот конкретный интерфейс будет использоваться в сценарии делегирования событий.
Добавляет тип данных в класс – эта ситуация является источником термина «тегирование». Класс, который реализует интерфейс тегов, не должен определять какие-либо методы (т.к. интерфейс не имеет таковых), но класс становится типом интерфейса через полиморфизм.
Продолжаем изучение темы «интерфейсы», которую начали на предыдущем занятии и вначале посмотрим как можно прописывать в интерфейсах не только методы, но и константы. Давайте в интерфейсе GeomInterface (из предыдущего занятия) пропишем две вот такие переменные:
Почему эти переменные я называю константами? Дело в том, что в Java к этим определениям автоматически добавляются ключевые слова:
public static final
и любые переменные превращаются в общедоступные статические константы. То есть, в интерфейсах попросту нельзя объявлять переменные – только константы. Далее, мы можем использовать MIN_COORD и MAX_COORD в классах, где применен интерфейс GeomInterface. Например, в классе Line:
Смотрите, мы здесь объявили сеттер setCoord и в нем проверяем соответствие переданных координат диапазону [MIN_COORD; MAX_COORD] с помощью вспомогательного приватного метода isCheck. Наличие констант как раз и объясняется их объявлением в интерфейсе GeomInterface.
Статические методы в интерфейсах
Но если в интерфейсе можно объявлять статические константы, то можно ли задавать и статические методы? Да, это стало возможно, начиная с версии JDK 8, и делается очевидным образом:
Здесь объявлен статический метод showInterval, который должен иметь реализацию. То есть, объявлять такие методы без реализации уже нельзя. И они не могут быть переопределены в классах.
Мы уже говорили с вами, что такое статические переменные и методы и как они себя ведут (https://www.youtube.com/watch?v=jEUXJRsHwmY). Я не стану здесь повторяться. Отмечу лишь, что это метод, располагающийся в строго определенной области памяти на всем протяжении работы программы. Следовательно, к нему можно обратиться и вызвать непосредственно из интерфейса, в котором он определен. Например, так:
Точно также к нему следует обращаться и из экземпляров классов, например:
Фактически, мы получаем неизменяемые методы, объявленные внутри интерфейса.
Вложенные интерфейсы и их расширение
Далее, интерфейсы можно объявлять внутри классов. Делается это очевидным образом, и я здесь приведу лишь отвлеченный пример. Пусть имеется класс InterfaceGroup, в котором определены два интерфейса: Interface_1 и Interface_2:
То есть, класс как бы образует группу интерфейсов. Далее, для их применения в классах, используется следующий синтаксис:
Мы здесь указываем сначала имя класса, а затем, через точку имя интерфейса. Но так можно делать, если модификатор доступа позволяет обратиться к интерфейсу извне. Например, если у первого указать модификатор private:
то возникнет ошибка. Такой интерфейс можно использовать только внутри класса. Как? Например, для расширения других публичных интерфейсов. Расширение – это когда один интерфейс наследуется от другого. В частности, мы можем расширить Interface_2, следующим образом:
То есть, здесь используется тот же синтаксис, что и при наследовании классов, только применительно к интерфейсам. В результате такой операции, второй интерфейс унаследует все публичные элементы первого. И, затем, указывая его в классе ReleaseInterface, должны определить уже два метода:
Приватные методы интерфейса
Но что значит: наследуются все публичные методы интерфейса? Разве в интерфейсах методы и константы могут быть не публичными? Да, начиная с версии JDK 9, допускается в интерфейсах объявлять приватные методы. Конечно, они обязательно должны иметь реализацию и используются исключительно внутри интерфейса. Например, мы можем объявить такой приватный метод:
Тогда при расширении второго интерфейса этот метод унаследован не будет.
Приватные методы используются исключительно для внутреннего использования, например, для программирования повторяющихся действий, когда мы используем описание метода с реализацией по умолчанию. Об этом речь пойдет дальше.
Интерфейсы с абстрактными классами
Давайте теперь зададимся вопросом: а можно ли к абстрактному классу применять интерфейсы? Оказывается, да, можно и при этом реализация интерфейсного метода getSquare в нем может отсутствовать:
В этом случае метод getSquare обязательно должен быть определен в дочернем классе. Если же этот метод прописать непосредственно в классе Geom:
То дочерние классы могут его не переопределять. Тогда при обращении к getSquare() будет возвращаться значение 0. Благодаря такой гибкости, мы можем в программе реализовывать самую разную логику взаимодействия с интерфейсами.
Методы с реализацией по умолчанию
Фактически вот этот последний пример позволяет использовать абстрактный класс для определения метода getSquare с реализацией по умолчанию (то есть, его действие, когда он не переопределяется в дочерних классах). Так приходилось делать до версии JDK 8, чтобы не «заставлять» программистов определять методы интерфейса, если это не требовалось. Теперь (начиная с JDK 8 и выше) в интерфейсах можно определять методы с реализацией по умолчанию и такие методы можно не переопределять в классах. Для их объявления используется следующий синтаксис:
default ([аргументы]) <
[операторы]
>
Например, определим в интерфейсе MathGeom метод getSquare с реализацией по умолчанию:
И применим его ко всем классам графических примитивов:
Смотрите, в классе Line мы не переопределяли метод getSquare, а в классах Rectangle и Triangle он переопределен. Теперь, создавая экземпляры этих классов в функции main:
мы можем совершенно свободно вызывать у них метод getSquare:
Обратите внимание, нам здесь сначала нужно привести ссылку g[i] к типу MathGeom и только потом вызвать метод getSquare. В консоли увидим значения:
Здесь первый ноль был получен из реализации метода по умолчанию для класса Line. Остальные значения – из переопределенных методов. То есть, теперь, мы можем не прописывать реализацию метода getSquare в классах примитивов, если она нам не нужна. И это добавляет дополнительное удобство при программировании.
Но что будет, если в GeomInterface также определить метод getSquare с реализацией по умолчанию:
Тогда для класса Line, который применяет оба интерфейса, какая реализация будет использована? В действительности, никакая. Виртуальная машина Java в этом случае выдаст ошибку и потребуется явное определение этого метода. И это можно сделать так:
то он будет вызван из интерфейса MathGeom и вернет значение 0. Вот так, непосредственно из экземпляра класса можно обращаться к объектам интерфейсов и использовать их элементы. Конечно, это имеет смысл только в условиях неопределенности, например, как в нашем случае. Иначе, достаточно просто записать имя метода или константы и она будет взята из соответствующего интерфейса или базового класса.
Заключение
Если вы четко представляете все эти моменты, то вы в целом знаете что такое интерфейсы в Java и как ими пользоваться. Конечно, правильное использование любой конструкции языка приходит с опытом и интерфейсы здесь далеко не исключение. Поэтому предлагаю пройти определенный путь кодера и совершить следующий великий подвиг.
Великий подвиг. Объявить класс DataGraph для хранения данных для графика в виде массива вещественных чисел размерностью N элементов (число N задать как константу, например, N=10). Записать отдельные классы (НЕ дочерние): LineGraph (точки в графике соединяются линиями), BarGraph (график в виде столбцов), ChartGraph (график в виде круговой диаграммы). При создании экземпляров этих классов они должны хранить ссылку на объект класса DataGraph. При рисовании графиков, данные следует брать через публичный метод getData() (класса DataGraph), т.е. получать ссылку на массив из N вещественных чисел. Взаимодействие между объектами классов должно выглядеть так:
Далее, объявить интерфейс Observer с методом update() и применить его к классам LineGraph, BarGraph и ChartGraph. По методу update() должно происходить обновление данных и перерисовка графика. В классе DataGraph хранить массив graphs для экземпляров классов LineGraph, BarGraph и ChartGraph. Как только происходит изменение данных в массиве data, вызывать метод update через ссылки graphs. (Изменение данных делать искусственно, например, в программе поменять данные, а затем, вызвать некий метод в DataGraph для запуска вызовов update).
Если все сделать правильно, то управление перерисовкой графиков будет выполняться через интерфейс Observer и благодаря этому классы графиков могут иметь любую структуру наследования, т.к. мы у них не задаем никаких базовых классов. В этом преимущество данной схемы реализации.
Для самых неистовых. В данной реализации класс DataGraph должен иметь только один экземпляр. Поэтому здесь целесообразно реализовать метод getInstance(), который бы возвращал ссылку на объект класса и контролировал бы единственность этого объекта. При этом нужно закрыть возможность создавать экземпляр класса напрямую через оператор new.
Видео по теме
#11 Концепция объектно-ориентированного программирования (ООП)
Интерфейсы в 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 полиморфизм можно реализовать через:
Интерфейс выручает в ситуации, когда при создании переменной мы не знаем, объект какого класса ей будет присвоен.