視窗/面板類別直接實作 ActionListener,this 即為監聽器。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class ButtonListener extends JFrame implements ActionListener {
JButton ok, cancel, exit;
public ButtonListener(String title) {
super(title);
setLayout(new FlowLayout());
ok = new JButton("確定");
cancel = new JButton("返回");
exit = new JButton("退出");
// 註冊監聽器
ok.addActionListener(this);
cancel.addActionListener(this);
exit.addActionListener(this);
add(ok); add(cancel); add(exit);
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == ok) System.out.println("確定");
else if (e.getSource() == cancel) System.out.println("返回");
else if (e.getSource() == exit) System.exit(0);
}
public static void main(String[] args) {
ButtonListener f = new ButtonListener("ActionEvent Demo");
f.setSize(250, 100);
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
建立專門的監聽器類別實作 ActionListener,元件註冊其實例。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class ButtonListener1 extends JFrame {
JButton ok, cancel, exit;
public ButtonListener1(String title) {
super(title);
setLayout(new FlowLayout());
ok = new JButton("確定");
cancel = new JButton("返回");
exit = new JButton("退出");
// 共用同一監聽器實例
ActionListener listener = new MyListener();
ok.addActionListener(listener);
cancel.addActionListener(listener);
exit.addActionListener(listener);
add(ok); add(cancel); add(exit);
}
public static void main(String[] args) {
new ButtonListener1("ActionEvent Demo").setVisible(true);
}
}
class MyListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if ("確定".equals(cmd)) System.out.println("確定");
else if ("返回".equals(cmd)) System.out.println("返回");
else if ("退出".equals(cmd)) System.exit(0);
}
}
AbstractAction 同時實作 ActionListener 與 Action 介面,封裝動作屬性(名稱、圖示、快捷鍵、啟用狀態)。
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import javax.swing.*;
class AbstractEvent extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
switch (cmd) {
case "open": JOptionPane.showMessageDialog(null, "打開"); break;
case "close": JOptionPane.showMessageDialog(null, "關閉"); break;
case "run": JOptionPane.showMessageDialog(null, "運行"); break;
case "stop": JOptionPane.showMessageDialog(null, "停止"); break;
}
}
}
public class TestAbstractEvent {
private static JMenuBar menubar;
private static JFrame frame;
private static Action menuAction = new AbstractEvent();
public TestAbstractEvent() {
frame = new JFrame("Menu Demo");
frame.setLayout(new BorderLayout());
menubar = new JMenuBar();
JMenu menuFile = new JMenu("File");
menuFile.add(createMenuItem("open", "開啟"));
menuFile.add(createMenuItem("close", "關閉"));
JMenu menuTool = new JMenu("Tool");
menuTool.add(createMenuItem("run", "執行"));
menuTool.add(createMenuItem("stop", "停止"));
menubar.add(menuFile);
menubar.add(menuTool);
frame.add(menubar, BorderLayout.NORTH);
frame.setSize(400, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private JMenuItem createMenuItem(String actionCommand, String text) {
JMenuItem item = new JMenuItem(text);
item.setActionCommand(actionCommand);
item.addActionListener(menuAction);
return item;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(TestAbstractEvent::new);
}
}
按鈕名稱對應處理類別,透過反射動態建立實例執行。
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import javax.swing.*;
class ViewAction extends AbstractAction {
private String actionName;
private Action action; // 快取實例
public ViewAction(String actionName) { this.actionName = actionName; }
@Override
public void actionPerformed(ActionEvent e) {
Action a = getAction(actionName);
if (a != null) a.execute();
}
private Action getAction(String name) {
if (action != null) return action;
try {
action = (Action) Class.forName(name).newInstance();
return action;
} catch (Exception ex) { return null; }
}
}
interface Action { void execute(); }
class b1 implements Action { public void execute() { JOptionPane.showMessageDialog(null, "點擊了 b1"); } }
class b2 implements Action { public void execute() { JOptionPane.showMessageDialog(null, "點擊了 b2"); } }
class b3 implements Action { public void execute() { JOptionPane.showMessageDialog(null, "點擊了 b3"); } }
public class TestAE extends JFrame {
public TestAE() {
super("反射事件Demo");
JToolBar bar = new JToolBar();
String[] btnNames = {"b1", "b2", "b3"};
for (String name : btnNames) {
ViewAction action = new ViewAction(name);
JButton btn = new JButton(name);
btn.addActionListener(action);
bar.add(btn);
}
add(bar, BorderLayout.NORTH);
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(TestAE::new);
}
}
button.addActionListener(e -> System.out.println("Clicked!"));
// 或方法引用
button.addActionListener(this::handleClick);
// 避免實作介面所有方法
window.addWindowListener(new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) { System.exit(0); }
});
// Guava EventBus / Spring ApplicationEventPublisher
@EventListener
public void handleOrderEvent(OrderCreatedEvent event) { ... }
SwingWorker 或 ExecutorService 避免凍結 UISwingUtilities.invokeLater() 將任務排入 EDT| 場景 | 推薦模式 | 理由 |
|---|---|---|
| 簡單視窗、少量按鈕 | 模式一(容器監聽) | 程式碼集中、易寫易讀 |
| 中型應用、邏輯複用 | 模式二(獨立監聽器) | 關注點分離、可測試 |
| 選單/工具列/按鈕共用動作 | 模式三 | Action 封裝屬性、狀態同步 |
| 插件架構、動態載入 | 模式四 | 反射解耦、配置驅動 |
| 現代 Java (8+) | Lambda + 方法引用 | 簡潔、函數式 |
| 問題 | 原因 | 解決 |
|---|---|---|
| 事件不觸發 | 未註冊監聽器、組件不可見/不可用 | 檢查 addActionListener、setEnabled(true)、setVisible(true) |
| 重複觸發 | 重複註冊同一監聽器 | 註冊前移除舊監聽器、或使用標記防重 |
| UI 凍結 | EDT 執行耗時操作 | SwingWorker、CompletableFuture、ExecutorService |
| 記憶體洩漏 | 監聽器持有外部引用未移除 | 移除監聽器、使用 WeakReference |
| 反射找不到類 | 類名錯誤、ClassLoader 問題、未編譯 | 使用完整類名、確認 classpath、捕獲異常日誌 |
Java 事件機制核心是觀察者模式:事件源 + 監聽器 + 事件物件。從簡單的容器監聽到 AbstractAction 封裝、反射動態分發,各模式權衡了簡潔性、複用性、解耦度、效能。現代開發推薦Lambda/方法引用處理簡單邏輯,Action 架構管理複雜工具列/選單,事件總線實現跨組件通訊。理解 EDT 執行模型、避免記憶體洩漏,是構建健壯 Swing/JavaFX 應用的關鍵。