Событие (event) в Java — это объект, который описывает изменение состояния источника: нажатие кнопки, выбор пункта списка, ввод символа с клавиатуры. Базовый класс для всех событий — java.util.EventObject, а слушатель (listener) — объект, реагирующий на событие методом обратного вызова.
Содержание
Здесь разбираем событийную модель GUI-библиотек AWT и Swing (пакет java.awt.event) — клики, нажатия клавиш и другие действия пользователя в окне. Не путайте ее с событиями бизнес-логики в Spring (ApplicationEvent) — та же идея, но для связи компонентов внутри приложения, не для реакции на клик в окне. Разница — в отдельном разделе ниже.
Источник события, объект события, слушатель
В теме легко перепутать четыре термина:
- Источник события — компонент, который порождает событие: кнопка, текстовое поле, окно.
- Объект события — экземпляр класса-наследника
EventObject; хранит ссылку на источник и детали произошедшего. - Слушатель (listener) — объект, реализующий интерфейс-наследник
java.util.EventListener; содержит метод, который вызывается при событии. - Регистрация — вызов метода
addXXXListener(...)на источнике, после которого слушатель начинает получать уведомления.
Сквозная цепочка на одном примере: пользователь кликает по JButton (источник) -> кнопка создает объект ActionEvent (событие) -> Swing вызывает метод actionPerformed() у каждого зарегистрированного ActionListener (слушатель).
Иерархия классов и интерфейсов
java.util.EventObject — корень иерархии, два метода: getSource() (объект-источник) и toString(). java.util.EventListener — маркерный интерфейс без единого метода, от него наследуются все интерфейсы слушателей.
Пакет java.awt.event вводит промежуточный класс AWTEvent (наследник EventObject) и конкретные события поверх него: ActionEvent, MouseEvent, KeyEvent, WindowEvent, ItemEvent и другие. Для каждого типа — свой интерфейс слушателя:
| Тип события | Интерфейс слушателя | Основные методы | Типичный источник |
|---|---|---|---|
| ActionEvent | ActionListener | actionPerformed | JButton, JMenuItem |
| MouseEvent | MouseListener | mousePressed, mouseReleased, mouseClicked | JPanel, любой JComponent |
| KeyEvent | KeyListener | keyPressed, keyReleased, keyTyped | JTextField, JComponent |
| WindowEvent | WindowListener | windowClosing, windowOpened, windowClosed | JFrame |
| ItemEvent | ItemListener | itemStateChanged | JCheckBox, JComboBox |
Интерфейс с несколькими методами (например MouseListener) описывает несколько связанных операций — для мыши это нажатие, отпускание, клик, вход и выход курсора. Вызывается ровно тот метод, который соответствует произошедшему.
Регистрация слушателя
Метод регистрации называется по шаблону addXXXListener(XXXListener listener), где XXX — имя события; парный removeXXXListener снимает подписку. Один источник может иметь несколько слушателей одного типа — все они получат уведомление в порядке регистрации.
Полный рабочий пример: окно с тремя кнопками и текстовым полем, один общий слушатель на все кнопки.
package test;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class TestFrame extends JFrame {
private static final long serialVersionUID = 1L;
private JTextField textField;
private JButton button1, button2, button3;
public TestFrame() {
super("Test frame");
createGUI();
}
public void createGUI() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
button1 = new JButton("Button 1");
button1.setActionCommand("Button 1 was pressed!");
panel.add(button1);
button2 = new JButton("Button 2");
button2.setActionCommand("Button 2 was pressed!");
panel.add(button2);
button3 = new JButton("Button 3");
button3.setActionCommand("Button 3 was pressed!");
panel.add(button3);
textField = new JTextField();
textField.setColumns(23);
panel.add(textField);
ActionListener actionListener = new TestActionListener();
button1.addActionListener(actionListener);
button2.addActionListener(actionListener);
button3.addActionListener(actionListener);
getContentPane().add(panel);
setPreferredSize(new Dimension(320, 100));
}
public class TestActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
textField.setText(e.getActionCommand());
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
TestFrame frame = new TestFrame();
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
После запуска откроется окно с тремя кнопками и текстовым полем. Клик по любой кнопке подставляет в поле ее actionCommand, например «Button 2 was pressed!». Код в main обязательно выполняется через SwingUtilities.invokeLater: все создание и изменение компонентов Swing должно идти в потоке обработки событий (Event Dispatch Thread), иначе возможны гонки при обновлении интерфейса.
Для интерфейсов с одним методом (ActionListener, ItemListener) начиная с Java 8 удобнее лямбда вместо анонимного класса:
button1.addActionListener(e -> textField.setText(e.getActionCommand()));
Программный вызов события: где легко ошибиться
Событие можно создать и передать слушателю вручную, без клика — иногда так делают в тестах. Здесь легко ошибиться с идентификатором события.
Неверный вариант (компилируется, но id бессмысленный):
public class TestActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
if (e.getSource() != button3) {
textField.setText(e.getActionCommand());
} else {
ActionEvent e1 = new ActionEvent(button2,
Event.MOUSE_DOWN,
"Button 2 was pressed programmatically!");
ActionListener[] listeners = button2.getActionListeners();
listeners[0].actionPerformed(e1);
}
}
}
Код скомпилируется: Event.MOUSE_DOWN — целочисленная константа (501) из старого класса java.awt.Event, а конструктор ActionEvent принимает любой int как id. Но код, который читает e1.getID() и ждет ActionEvent.ACTION_PERFORMED (1001), получит чужое устаревшее значение и может отработать неверно.
Исправление — константа самого ActionEvent:
ActionEvent e1 = new ActionEvent(button2,
ActionEvent.ACTION_PERFORMED,
"Button 2 was pressed programmatically!");
ActionListener[] listeners = button2.getActionListeners();
listeners[0].actionPerformed(e1);
После клика по button3 в консоль выведется текст «Button 2 was pressed programmatically!», и это же значение появится в текстовом поле, если в обработчике вызвать textField.setText(e1.getActionCommand()).
Адаптеры: когда слушатель нужен частично
Для интерфейсов с несколькими методами (MouseListener, WindowListener, KeyListener и подобных) в java.awt.event есть классы-адаптеры: MouseAdapter, KeyAdapter, WindowAdapter, FocusAdapter, ComponentAdapter и другие. Каждый адаптер — это абстрактный класс с пустой реализацией всех методов интерфейса.
Без адаптера пришлось бы реализовать все методы интерфейса, даже ненужные. С адаптером переопределяется только то, что действительно требуется:
panel.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("Клик по панели: x=" + e.getX() + ", y=" + e.getY());
}
});
Для интерфейсов с одним методом (ActionListener, ItemListener) адаптер не нужен — там и переопределять больше нечего, лямбда решает ту же задачу короче. Похожий пример для клавиатуры — реакция на Enter через KeyAdapter:
textField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
System.out.println("Enter нажат, текст поля: " + textField.getText());
}
}
});
При нажатии Enter программа выведет в консоль текущее содержимое поля. getKeyCode() возвращает код физической клавиши, а не введенный символ (для символа — getKeyChar()).
События AWT/Swing и события Spring — разные механизмы
В Spring есть свой класс ApplicationEvent и интерфейс ApplicationListener — они тоже строятся на java.util.EventObject и java.util.EventListener, но решают другую задачу: не реакцию на клик в окне, а уведомление между компонентами бизнес-логики, например «заказ оформлен».
| Критерий | AWT/Swing событие | Spring ApplicationEvent |
|---|---|---|
| Домен | Реакция GUI на действие пользователя | Уведомление между компонентами бизнес-логики |
| Источник события | Компонент интерфейса (JButton, JFrame) | Любой bean, вызвавший publishEvent |
| Подписка | addXXXListener на конкретном компоненте | @EventListener на методе или класс, реализующий ApplicationListener |
Смешивать их не стоит: у Java-новичка, который встречает слово «событие» в вакансии Spring-разработчика, речь почти всегда про второй случай, а не про клики в GUI.
Выводы
- Событие Java — объект-наследник
EventObject, слушатель — объект-наследникEventListener; связь задается регистрациейaddXXXListener. - В AWT/Swing под каждый тип события (Action, Mouse, Key, Window, Item) есть свой интерфейс слушателя и метод обратного вызова; адаптеры (MouseAdapter, KeyAdapter и другие) избавляют от реализации ненужных методов.
- Ручное создание события требует правильного id (константа самого класса события, а не чужого устаревшего класса).
- Spring ApplicationEvent — другой домен той же идеи: уведомления между компонентами бизнес-логики, а не реакция на действия в окне.
Где применяется / связь с практикой
Событийная модель AWT/Swing — основа любого desktop-приложения на Java с графическим интерфейсом: от учебных калькуляторов до промышленных панелей управления. Принцип «источник -> событие -> подписчик» лежит и в основе Spring-событий, и в паттерне Observer в целом.
Освойте тему на практике
Разобрать модель событий на практике, с разбором классов, слушателей и типичных ошибок, можно на курсе Java Basic. Также можно заглянуть на открытые уроки Otus, чтобы посмотреть формат занятий перед стартом.
Смежные темы: Java concurrency: как работают потоки.
FAQ
Можно ли назначить компоненту несколько слушателей одного типа?
Да, addXXXListener можно вызывать многократно на одном источнике — все слушатели получат уведомление в порядке регистрации.
Что будет, если не зарегистрировать слушатель?
Событие все равно произойдет, но обработать его будет некому: метод actionPerformed не вызовется, и клик не даст видимого эффекта.
Работает ли этот же механизм в консольном приложении без GUI?
Базовые EventObject и EventListener не привязаны к GUI, на их основе можно строить свой Observer в консоли. Но конкретные AWT/Swing события требуют реальных GUI-источников, которых там нет.



