Думаю, что тот, кто ввел понятие интерфейса, возможно и не подозревал, какое фантастическое по своим возможностям сотворил явление. Хотя это только мои догадки. В любом случае понятие интерфейса раздвинуло возможности ООП весьма сильно.
Так что же такое интерфейс ? По сути — это описание голой функциональности без каких либо привязок к особенностям класса. Если выражаться немного образно, то классы получили возможность иметь профессии — отправитель почты, управляющий транзакциями, распределитель страниц, контроллер и т.д. Я сейчас пытаюсь обрушить на вас грандиозность этой идеи и понимаю, что пока не получается. Просто поверьте на слово — это здорово. С помощью интерфейсов отношения между объектами становятся более гибкими, что позволяет строить архитектуру приложений из еще более независимых блоков.
Если опять вернуться к аналогии профессии — по сути вас не волнует пол, цвет глаз, возраст и рост человека, который работает водителем, электриком или программистом. Вам важно, что он умеет делать эту работу и умеет делать ее хорошо (в какой-то степени). Что еще важно отметить — как человек может обладать несколькими профессиями, так и класс может реализовывать несколько интерфейсов. Если вернуться к теме наследования, то как известно класс может наследоваться ТОЛЬКО ОТ ОДНОГО класса. А вот интерфейсов у него может быть достаточно много.

Перейдем от слов к делу — вернемся к нашему (широко известному в узких кругах) классу Robot 🙂
В прошлый раз мы научили его двигаться и запоминать свой маршрут для отображения на форме. При его создании мы немного забежали вперед — я ввел класс, о котором мы еще некоторое время не будем говорить — ArrayList. Пока мы не будем его обсуждать — просто еще раз отмечу, что это класс позволяет вам работать с динамическими списками объектов — добавлять, удалять, просматривать, перебирать. Расширим возможности нашего робота — наделим его способностью сообщать кому-нибудь о том, что он начал двигаться вперед и остановился. Возможно, что не такая уж и бесполезная вещь — например при наблюдении за марсоходом.
Заострим свое внимание на словах «сообщать кому-нибудь». Это очень тонкий момент — роботу ведь действительно неважно, кому сообщать. Вот она, точка применения интерфейса — нам неважно кто будет слушать — нам важно, чтобы этот кто-то или что-то умело слушать наше сообщение, соблюдало определенный контракт. Пришло время посмотреть, как определяется интерфейс.

Как видите, описание интерфейса достаточно несложный процесс — гораздо сложнее понять, когда он действительно нужен. Рассмотрим его несколько подробнее.
Во-первых, для описания интерфейса надо использовать слово interface. Во-вторых, методы не содержат тела — совсем. Это просто запрещено правилами. Создается только описание — доступность, возвращаемый тип и входные параметры. После этого ставится точка с запятой.
У вас может возникнуть вопрос — а зачем мы передаем координаты x и y в методы интерфейса ? Вполне резонный вопрос, но и вполне резонный ответ — робот же должен сообщить где он стартовал и где остановился.
Настало время модифицировать код робота для того, чтобы он, во-первых, мог зарегистрировать «слушателя», а во-вторых он должен с ним уметь работать. Смотрим код.

В нашем коде есть три момента, на которые надо обратить внимание. Первое — это объявление ссылки на слушателя.

private RobotListener listener;

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

И наконец третье — в методе forward робот вызывает слушателя. Обратите внимание — перед вызовом мы делаем проверку на NULL — если слушатель не установлен, то мы можем получить неприятную ошибку NullPointer — указатель пустой. Такие ошибки делают и достаточно опытные программисты — так что будьте внимательны. Очень коварная ошибка. Хотя достаточно простая при обнаружении.
Наш робот готов посылать события и наша задача теперь создать этого самого слушателя. И теперь ВНИМАНИЕ — вы не можете создавать объект типа интерфейс. Очень похоже на абстрактный класс, но если в абстрактном классе хоть какой-то код может присутствовать, то в интерфейсе его вообще нет. Если опять вернуться к аналогии профессии, то наличие врача в клинике означает присутствие человека, который обладает профессией врача. С интерфейсами дело обстоит точно также — мы должны создать класс, который реализует (имплементирует — implements) нужный интерфейс. И опять же по аналогии с профессией — класс может реализовать более одного интерфейса. Сделаем простого слушателя:

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

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

Надеюсь, вы помните, что значит аннотация @Override — мы это уже обсуждали. Чтобы не бегать по ссылкам — это специальное обозначение того, что метод переопределен.
Слушатель готов — осталось только подключить его к нашему роботу и запустить программу. Подключение делается через вызов метода setListener. Можем это сделать в классе RobotManager.

В коде мы создаем экземпляр объекта SimpleRobotListener, который реализует интерфейс RobotListener и класс Robot без проблем позволяет установить себе слушателя. Отметим важную мысль — классу Robot совершенно неважно, какой класс реализует нужный интерфейс.
Если быть еще более точным, то при создании SimpleRobotListener можно (даже нужно) писать так:

Можно вспомнить про полиморфизм — класс умеет быть слушателем робота (профессия у него такая). И мы работаем с экземпляром класса SimpleRobotListener как с профессией RobotListener. Такое абстрагирование весьма удобно при проектировании — вы это увидите. Вы не привязываетесь к конкретному классы — вы привязываетесь исключительно к «профессии».
Теперь при запуске нашей программы в консоль вывода будет виден текст, который должен выводить наш слушатель.

Исходный код программы можно скачать здесь — Robot5

Свойства и константы

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

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

Двигаем квадрат

Рассмотрим пример, который позволит нам создать интерактивное графическое приложение, в котором мы будем использовать интерфейсы. Я очень тепло отношусь к примерам, которые позволяют наглядно посмотреть работу программы. И графические приложения являются крайне благодарным материалом. Итак, наша задача — создать приложение, которое в помощью кнопок будет передвигать по экрану квадрат.
На форме будет две кнопки — UP и DOWN, которые позволят двигать квадрат соответственно вверх и вниз. Само приложение не сложное — здесь важно увидеть применение интерфейсов.
Кнопки на самом деле очень похожи по своей идее на нашего робота — при нажатии на них они способны рассылать события тем объектам, которые у них зарегистрированы как слушатели. Причем кнопкам позволяется иметь много слушателей одновременно — целый список.
Приведем код формы, которая содержит три компонента — две кнопки для управления и панель, которая умеет рисовать квадрат с определенными координатами и, что важно отметить сразу, умеет «слушать» события от кнопок. Умение слушать достигается очень просто — наша панель реализует интерфейс ActionListener (этот интерфейс описан в библиотеке Swing). Причем компонент умеет слушать события от обеих кнопок — он зарегистрирован в качестве слушателя у обеих. Итак, смотрим код:

Если просто аккуратно прочитать код, то видно, что сначала мы создаем панель, а потом создаем кнопки. При создании кнопки мы даем ее заголовок (прямо в конструкторе), потом устанавливаем ей название команды (для того, чтобы панель могла различать, кто ее позвал — кнопка UP или DOWN). Вызывая метод кнопки addActionListener мы регистрируем нашу панель в качестве слушателя. И в самом конце устанавливаем нашу кнопку либо вверх, либо вниз. Мы уже касались вопроса о layout в разделе Полиморфизм — так вот по умолчанию форма использует BorderLayout — здесь компоненты распределяются по сторонам света — север, юг, запад, восток и в центре. Если не указывать направление, то компонент располагается в центре — дальше догадаетесь.
Теперь посмотрим код нашей панели — он не должен показаться вам очень сложным — метод прорисовки мы уже использовали неоднократно.

Новинкой для нас будет метод actionPerformed. Это метод, который описан в интерфейсе ActionListener. И кнопке совершенно безразлично, какой именно класс ее слушает — это может быть другой компонент, класс для записи файлов, для отсылки почты и еще море всяких других классов. Важен просто контракт — «я умею слушать кнопку». Это позволяет в разы повысить гибкость при проектировании. Возвращаясь к методу — он принимает в качестве параметра специальный класс/объект, который содержит интересную информацию об источнике события — в данном случае о кнопке. В нашем случае нам очень интересен параметр, который мы устанавливали — getActionCommand/setActionCommand. Именно он нам скажет какая кнопка нажата. Еще раз обратите внимание на приятную возможность слушать события от обеих кнопок.
Ну и наконец код для запуска нашей формы:

Предлагаю вам расширить пример — добавить туда кнопки LEFT и RIGHT и двигать квадрат в стороны. Также вы можете сделать проверку, чтобы квадрат «не убежал» за пределы экрана.
Исходный код примера находится здесь — MoveSquare

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

И теперь нас ждет следующая статья: Расширенное описание классов.