Java 圖形界面開發中,按鈕事件處理是最基礎也最頻繁的操作。本文對比傳統匿名內部類、Java 8 Lambda、方法引用三種寫法,並講解事件處理線程模型與常見 UI 凍結問題。
java.awt.Button、ActionListener (重量級、平台原生)javax.swing.JButton、ActionListener (輕量級、純 Java 繪製、推薦)javafx.scene.control.Button、EventHandler<ActionEvent> (現代、FXML、CSS、新項目首選)JButton loginBtn = new JButton("登錄");
loginBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("登錄按鈕被點擊");
new LoginDialog().setVisible(true); // 彈出登錄對話框
}
});
JButton loginBtn = new JButton("登錄");
loginBtn.addActionListener(e -> {
System.out.println("登錄按鈕被點擊");
new LoginDialog().setVisible(true);
});
JButton loginBtn = new JButton("登錄");
loginBtn.addActionListener(this::handleLogin); // 實例方法引用
// 或靜態方法
loginBtn.addActionListener(LoginController::handleLogin);
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ButtonDemo extends JFrame {
public ButtonDemo() {
setTitle("按鈕事件演示");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 300);
setLocationRelativeTo(null);
setLayout(new FlowLayout());
// 按鈕 1:匿名內部類
JButton btn1 = new JButton("匿名內部類");
btn1.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(this, "匿名內部類觸發");
}
});
// 按鈕 2:Lambda
JButton btn2 = new JButton("Lambda 表達式");
btn2.addActionListener(e ->
JOptionPane.showMessageDialog(this, "Lambda 觸發")
);
// 按鈕 3:方法引用
JButton btn3 = new JButton("方法引用");
btn3.addActionListener(this::onBtn3Click);
add(btn1); add(btn2); add(btn3);
}
private void onBtn3Click(ActionEvent e) {
JOptionPane.showMessageDialog(this, "方法引用觸發");
}
public static void main(String[] args) {
// 必須在 EDT (事件分發線程) 中創建/操作 Swing 組件
SwingUtilities.invokeLater(() -> new ButtonDemo().setVisible(true));
}
}
所有 Swing 組件創建、修改、查詢必須在 EDT 中執行
違規後果:界面凍結、繪製異常、死鎖、不可預測行為
// 1. 主程序入口
SwingUtilities.invokeLater(() -> new MyFrame().setVisible(true));
// 2. 耗時任務放入後台線程,完成後回 EDT 更新 UI
btn.addActionListener(e -> {
new Thread(() -> {
// 耗時操作:網絡請求、文件讀寫、數據庫查詢
String result = heavyOperation();
// 回 EDT 更新 UI
SwingUtilities.invokeLater(() -> {
label.setText(result);
progressBar.setVisible(false);
});
}).start();
});
btn.addActionListener(e -> {
// 直接在 EDT 執行耗時操作 → 界面凍結,無法響應關閉/移動/重繪
String result = heavyOperation();
label.setText(result);
});
btn.addActionListener(e -> {
new SwingWorker<String, Integer>() {
@Override
protected String doInBackground() throws Exception {
for (int i = 0; i <= 100; i += 10) {
Thread.sleep(500); // 模擬耗時
publish(i); // 發布進度
}
return "完成";
}
@Override
protected void process(List<Integer> chunks) {
// 在 EDT 執行,安全更新 UI
progressBar.setValue(chunks.get(chunks.size() - 1));
}
@Override
protected void done() {
try {
label.setText(get()); // 獲取結果
} catch (Exception ex) {
label.setText("錯誤: " + ex.getMessage());
}
}
}.execute();
});
// AWT 重量級組件,外觀隨平台變化,已基本淘汰
import java.awt.*;
import java.awt.event.*;
public class AwtButtonDemo extends Frame {
public AwtButtonDemo() {
setTitle("AWT Button");
setSize(300, 200);
setLayout(new FlowLayout());
Button btn = new Button("AWT 按鈕");
btn.addActionListener(e -> System.out.println("AWT Clicked"));
add(btn);
addWindowListener(new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) { System.exit(0); }
});
}
public static void main(String[] args) { new AwtButtonDemo().setVisible(true); }
}
| 現象 | 原因 | 解決 |
|---|---|---|
| 點擊按鈕無反應 | 未添加 Listener、或組件被遮擋、佈局問題 | 檢查 addActionListener、setBounds/Layout、isEnabled() |
| 界面點擊後卡死 | EDT 執行耗時操作 | Thread/SwingWorker 異步處理 |
| Lambda 中 this 報錯 | Lambda 無 this,指向外部類 | 用 外部類.this、或提取為方法引用 |
| 彈窗顯示在主窗口後面 | 未指定父組件 | JOptionPane.showMessageDialog(parentFrame, ...) |
| 圖標/樣式不生效 | LookAndFeel 未設置、路徑錯誤 | UIManager.setLookAndFeel、ClassLoader 獲取資源 |
// JavaFX Button (FXML + Controller 分離)
@FXML private Button loginBtn;
@FXML
private void initialize() {
loginBtn.setOnAction(event -> {
// 異步任務用 Task/Service
new Task<Void>() {
@Override protected Void call() { heavyOperation(); return null; }
}.setOnSucceeded(e -> Platform.runLater(() -> label.setText("Done")));
});
}
按鈕事件處理的核心在於:正確註冊監聽器 + 嚴守 EDT 線程規則 + 耗時操作異步化。Swing 中優先使用 Lambda 或 方法引用 簡化代碼,複雜後台任務用 SwingWorker 管理生命週期與進度。新項目建議直接採用 JavaFX,擁有更現代的綁定機制、CSS 樣式、FXML 聲明式界面與原生異步支持。