Facebook google+ e-mail
Учимся программировать на JAVA
Практикум по программированию на языке Java

Занятие 11. События (взгляд с высоты)

Мы опять возвращаемся к обсуждению следующего фрагмента кода:

okButton.addActionListener (new ActionListener () {
     public void actionPerformed (ActionEvent e) {
         exitFlag = true;
         dispose ();
     }
});

Удивительно, сколь многое скрывается всего за несколькими строками! Начнем...

Здесь кнопка okButton является источником событий (т.е. компонентом, чье состояние нас интересует). Вспоминая то, о чем говорилось на предыдущем занятии (Диалоговые окна) мы видим, что кнопка okButton назначен слушатель (listener), т.е. нечто, что наблюдает за состоянием источника событий – в данном случае, за состоянием кнопки и порождаемыми кнопкой событиями.

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

Но для начала мы рекомендуем вернуться немного назад и вспомнить, что ранее говорилось о классах, их экземплярах и типах (см. Занятие 4. Начинаем вникать (начало) и Занятие 5. Начинаем вникать (окончание)). Напомним, что тип – это нечто обладающее состоянием и поведением. Тогда мы говорили о типе в контексте объектов, которые описываются в java-программах посредством конструкции class. Классы – это базовая единица программы и все о чем мы тогда говорили по-прежнему и впредь остается верным. Однако, классы – не единственный способ описания типов.

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

Интерфейсы идеально подходят для описания таких случаев, когда есть множество сущностей, которые отличаясь в каких-то деталях, имеют между собой нечто общее. Рассмотрим, например, понятие «совпадать»; что оно означает?

В случае целых чисел – все просто: число x совпадает с числом y, если они равны друг другу. Аналогично для строк: строки «abc» и «abc» совпадают, в то время как сроки «abc» и «acb» отличаются. Можно сравнивать на совпадение и другие типы данных (в том числе объекты). Сходным во всех этих случаях является вопрос: идентичны ли сравниваемые типы или нет? Для некоторых данных можно определить понятия «больше» или «меньше» (для чисел или строк). Для других – этого сделать нельзя (какой смысл в утверждении «объект M больше объекта N»?). Во всех случаях мы должны решить вопрос – одинаковы или отличны друг от друга рассматриваемые сущности.

Для каждого типа данных алгоритмы сравнения могут (и, скорее всего, будут) отличными друг от друга. Для чисел такие алгоритмы просты и сводятся к использованию стандартной условной конструкции if (условие) {... } else {...}. Для строк алгоритм становится сложнее, но и здесь мы можем сравнивать строки на «больше» или «меньше» (вспомните хотя бы словари, где слова располагаются в некотором порядке, обычно – алфавитном).

Для объектов понятия «больше» и «меньше» уже не применимы, но вот выяснить – отличается ли один объект от другого – мы можем.

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

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

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

Вот интерфейсы и обеспечивают этот самый принцип действия. Мы объявляем интерфейс в котором объявляем метод сравнения. Затем в пользовательском классе мы реализуем интерфейс, т.е. наполняем метод сравнения конкретным кодом. Из нашего краткого описания интерфейсов не очень видны преимущества такой «двухстадийной» реализации методов, но чем больше вы будете программировать на java, преимущества интерфейсов будут выявляться все четче и четче. Мы не будем слишком углубляться сейчас в то, как «устроены» интерфейсы и как их использование влияет на программирование. На первых порах достаточно и сказанного.

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

  1. вызываем метод addActionListener. Обратите внимание на то, что вызов метода осуществляется через точку («.») перед которой указано имя кнопки okButton, являющейся экземпляром класса JButton (вот оно, объектно-ориентированное программирование в действии - вспоминайте!). Этот метод добавляет к нашей кнопке слушателя, но сам слушатель пока не определен.
  2. метод addActionListener имеет параметр ActionListener. Если «порыться» в документации для Java, то легко обнаружить, что ActionListener является интерфейсом:
  3. Программируем на Java. События ...

    Передача параметра производится несколько непривычным способом, с использованием оператора new. Ранее мы использовали этот оператор в виде

    TheClass name = new TheClass ();

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

  4. Еще раз обратившись к документации интерфейса ActionListener мы обнаруживаем, что в нем объявлен единственный метод actionPerformed:
  5. Программируем на Java. События ...

    которому в качестве параметра передается описание события. Это событие генерируется кнопкой и передается соответствующему слушателю ActionListener. Метод actionPerformed вызывается на исполнение при наступлении события, которое мы желаем «отловить» и обработать, т.е. при нажатии на кнопку okButton. И именно этот класс мы обязаны (именно так – обязаны!) реализовать, т.е. явно его вызвать в нашем коде обработки события.

  6. Внутреннее наполнение метода actionPerformed может быть любым; все зависит от того, что должно произойти при наступлении события нажатия на кнопку. В нашем случае происходит присваивание значения true переменной exitFlag и вызов метода dispose () по которому окно диалога закрывается.

Похожий (хотя и совершенно отличающийся в деталях) подход используется при обработке события закрытия окна системной кнопкой:

// Обработчик события закрытия диалога addWindowListener (new WindowAdapter () { public void windowClosing (WindowEvent e) { exitFlag = false; dispose (); } });

Мы не станем на этом этапе углубляться в детали реализации модели событий в Java: это увело бы нас далеко в сторону от основной цели. Хотя обработка событий в Java не настолько проста, как, возможно, нам хотелось бы, у нее есть множество приятных и полезных преимуществ, которые выдержали проверку временем и заслужили признание всех программистов. Нам пока достаточно и того, что было рассказано. Кроме того, если помните, мы в самом начале курса говорили, что наши занятия – не учебник, а метод, отталкиваясь от которого вы сможете в дальнейшем восполнить все пробелы.

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

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

 

Занятие 10. Занятие 10. Диалоговые окна (окончание). События (знакомство)

 

Занятие 12. Данные, данные, данные...

 

Автор: Alex Tuesday

 

Все уроки ... 24.05.2014