1. 이벤트와의 첫 만남
프로그래밍에서 이벤트는 무언가가 발생했음을 알리는 신호입니다. 버튼 클릭, 텍스트 입력, 데이터 로드 완료, 변수 값 변경 등 애플리케이션에서 일어날 수 있고 개발자인 여러분이 반응하고 싶은 모든 것이 이벤트가 될 수 있습니다.
비유하자면 이벤트는 초인종과 같습니다. 누군가가 찾아왔고, 이제 무엇을 할지 여러분이 결정합니다. 문을 열 수도 있고, 무시할 수도 있고, 집에 아무도 없는 척할 수도 있으며, 가족 모두를 불러서 누가 왔는지 함께 볼 수도 있습니다. 프로그래밍에서 이벤트는 그 “초인종”이고, 여러분의 반응은 이벤트 핸들러입니다.
Java에서 이벤트는 반응형 및 그래픽 프로그래밍(GUI)의 근간입니다. 이벤트가 없다면 버튼, 드롭다운, 데스크톱 애플리케이션은 물론 많은 서버 시스템조차 존재하기 어렵습니다.
리스너(Listener): 무엇이며 왜 필요한가
리스너(Listener)는 특정 이벤트를 구독하고 발생을 기다리는 객체입니다. 이벤트가 발생하면 리스너는 신호를 받고 지정된 반응 코드를 실행합니다.
Java에서 리스너는 보통 인터페이스로 구현합니다. 가장 널리 쓰이는 예 중 하나가 ActionListener 인터페이스입니다. 이는 사용자 동작(예: 버튼 클릭)에 반응해야 하는 클래스가 구현합니다.
실무에서는 다음과 같이 상호작용이 이루어집니다. 이벤트 소스(예: 버튼)가 있고, 필요한 인터페이스를 구현한 리스너 객체가 있습니다. 소스는 자신에게 구독된 모든 리스너의 목록을 보관합니다. 사용자가 버튼을 누르면 이벤트 소스가 각 리스너의 특정 메서드를 호출합니다.
이 패턴은 뉴스 구독과 비슷합니다. 구독되어 있는 한 알림이 도착합니다. Java에서는 그 알림이 리스너 메서드 호출이며, 거기서 미리 정의한 동작이 수행됩니다.
2. 이벤트 인터페이스: ActionListener, MouseListener 등
Java에는 이벤트 유형마다 대응하는 리스너 인터페이스가 있습니다:
| 이벤트 유형 | 리스너 인터페이스 | 사용 위치 |
|---|---|---|
| 액션 | |
버튼, 메뉴, 타이머 |
| 마우스 | |
마우스에 반응하는 컴포넌트 |
| 키보드 | |
텍스트 필드, 임의의 컴포넌트 |
| 변경 | |
슬라이더, 체크박스, 데이터 모델 |
| 문서 | |
텍스트 변경(예: JTextField) |
각 인터페이스는 구현해야 하는 하나 이상의 메서드를 정의합니다. 예를 들어 ActionListener는 메서드 actionPerformed(ActionEvent e)의 구현을 요구합니다.
예: ActionListener
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
class MyActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("버튼이 눌렸습니다!");
}
}
3. 이벤트 모델의 동작 원리
Java의 이벤트 모델은 간단하지만 강력한 메커니즘에 기반합니다:
- 이벤트 소스(예: 버튼)가 리스너 목록을 유지합니다.
- 무언가 동작이 발생하면(예: 사용자가 버튼을 클릭) 소스는 이벤트 객체(예: ActionEvent)를 생성합니다.
- 소스는 등록된 모든 리스너를 순회하며 해당 메서드(예: actionPerformed)를 호출하고 이벤트 객체를 전달합니다.
- 각 리스너는 이 이벤트를 어떻게 처리할지 스스로 결정합니다.
동작 다이어그램:
flowchart LR
A[사용자가 버튼을 눌렀다] --> B[버튼이 이벤트를 생성했다]
B --> C[버튼이 모든 리스너의 actionPerformed를 호출한다]
C --> D[리스너가 반응한다: 자신의 코드를 실행한다]
코드 예:
import javax.swing.*;
public class EventDemo {
public static void main(String[] args) {
JButton button = new JButton("눌러 보세요!");
button.addActionListener(new MyActionListener());
JFrame frame = new JFrame("이벤트 예제");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(button);
frame.setSize(200, 100);
frame.setVisible(true);
}
}
사용자가 버튼을 누르면 모든 리스너의 actionPerformed가 실행됩니다.
4. 예: 버튼에 리스너 추가하기
고전적인 패턴을 간단한 예로 살펴봅시다.
1단계. 버튼 생성
JButton button = new JButton("인사하기");
2단계. 리스너 생성
class HelloListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("안녕, 세상!");
}
}
3단계. 리스너 등록
button.addActionListener(new HelloListener());
요약: 코드에서는 이렇게 보입니다
JButton button = new JButton("인사하기");
button.addActionListener(new HelloListener());
5. 익명 클래스와 람다 표현식: 짧고 편리하게
익명 클래스
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("익명 리스너: 안녕!");
}
});
람다 표현식 (Java 8+)
button.addActionListener(e -> System.out.println("람다! 안녕!"));
람다는 반응 코드가 한두 줄일 때 리스너를 추가하는 가장 간결한 방법입니다.
6. 여러분의 애플리케이션과는 어떻게 연결될까요?
이 메커니즘을 학습용 애플리케이션에 통합해 봅시다. 예를 들어, “작업 추가” 버튼이 있는 간단한 창이 있다고 합시다. 버튼을 누르면 목록에 새 작업을 추가하고 메시지를 출력하고자 합니다.
코드 예:
import javax.swing.*;
import java.awt.event.ActionListener;
public class TaskManagerGUI {
public static void main(String[] args) {
JFrame frame = new JFrame("작업 관리자");
JButton addButton = new JButton("작업 추가");
// 리스너로 람다 사용
addButton.addActionListener(e -> {
System.out.println("작업이 추가되었습니다!");
// 여기에서 목록에 작업을 추가하는 로직이 들어갈 수 있습니다
});
frame.add(addButton);
frame.setSize(300, 100);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
이제 여러분의 애플리케이션은 단순히 명령을 수행하는 것이 아니라 사용자 동작에 “반응”합니다!
7. 리스너와 이벤트를 다룰 때 흔한 실수
실수 1: 리스너 등록을 잊음. 리스너를 만들어 놓고 이벤트 소스에 추가하지 않으면 해당 코드는 결코 호출되지 않습니다. 잡지 구독을 신청하지 않은 것과 같습니다 — 잡지는 오지 않습니다.
실수 2: 같은 리스너를 여러 번 등록. 실수로 동일한 리스너를 여러 번 추가하면, 핸들러는 추가한 횟수만큼 호출됩니다. 때로는 유용할 수 있지만, 대부분은 이상한 버그의 원인입니다(“내 함수가 왜 세 번 실행되지?”).
실수 3: 리스너 제거를 잊음. 더 이상 필요 없는 리스너를 제거하지 않으면 메모리에 계속 남습니다. 수명이 긴 애플리케이션에서는 메모리 누수로 이어질 수 있습니다.
실수 4: 이벤트 핸들러에서 오래 걸리는 작업을 수행. 핸들러에서 무거운 작업(예: 인터넷에서 데이터 로드)을 직접 수행하면 UI가 “멈추고” 사용자가 불편해합니다. 무거운 작업은 별도의 스레드에서 실행하는 것이 좋습니다.
실수 5: 리스너에서 예외를 처리하지 않음. 핸들러에서 예외가 발생하면 이벤트 흐름 전체가 중단될 수 있습니다. 오류를 로깅하고 예외를 처리하여 애플리케이션이 중단되지 않도록 하세요.
GO TO FULL VERSION