以 JButton 點擊事件為例,展示四種不同耦合度的實作模式:
特點:元件類別直接實作 ActionListener,this 即為監聽器。
class EventListener1 extends JFrame implements ActionListener {
private JButton btBlue, btDialog;
public EventListener1() {
btBlue = new JButton("藍色");
btDialog = new JButton("彈窗");
btBlue.addActionListener(this);
btDialog.addActionListener(this);
// ...
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == btBlue) getContentPane().setBackground(Color.BLUE);
else if (e.getSource() == btDialog) new JDialog().setVisible(true);
}
}
優缺點:簡單但違反單一職責;所有事件擠在同一方法,需 if-else 判斷來源;難以複用。
特點:每個事件對應一個獨立類別實作介面。
class ColorEventListener implements ActionListener {
@Override public void actionPerformed(ActionEvent e) {
// 需取得外部視窗引用才能操作 UI
}
}
class DialogEventListener implements ActionListener { ... }
// 使用
btBlue.addActionListener(new ColorEventListener());
btDialog.addActionListener(new DialogEventListener());
優缺點:職責分離佳,易複用;但需傳遞外部元件引用(如視窗實例)才能操作 UI,構造函數耦合。
特點:在註冊處直接定義實作,無需額外類別檔案。
btBlue.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
getContentPane().setBackground(Color.BLUE);
}
});
btDialog.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
new JDialog().setVisible(true);
}
});
優缺點:程式碼緊湊、封裝性好;但過多匿名類會導致類別檔案膨脹(Outer$1.class 等),且 Java 7 前無法存取非 final 區域變數。
特點:定義於外部類內部,持有外部實例引用,可直接存取外部成員。
class EventListener4 extends JFrame {
// ...
btBlue.addActionListener(new ColorEventListener(this));
// ...
}
// 內部類
class ColorEventListener implements ActionListener {
private EventListener4 outer;
ColorEventListener(EventListener4 outer) { this.outer = outer; }
@Override public void actionPerformed(ActionEvent e) {
outer.getContentPane().setBackground(Color.BLUE);
}
}
優缺點:封裝性與存取外部成員平衡;但內部類實例隱含外部實例引用,可能阻礙 GC,序列化時需注意。
函數式介面(單一抽象方法)可直接用 Lambda 取代匿名內部類:
btBlue.addActionListener(e -> getContentPane().setBackground(Color.BLUE));
btDialog.addActionListener(e -> new JDialog().setVisible(true));
| 維度 | 匿名內部類 | Lambda 表達式 |
|---|---|---|
| 語法冗長度 | 高(6-8 行) | 極低(1 行) |
| 類別檔案產生 | 每個匿名類產生 .class | 無額外 .class(invokedynamic) |
| this 語義 | 指向匿名類實例 | 指向外部類實例 |
| 序列化 | 支援(若實作 Serializable) | 僅目標型別可序列化時支援 |
| 存取區域變數 | 需 final 或 effectively final | 同樣限制 |
大型應用建議引入事件總線(Guava EventBus、Spring ApplicationEvent、MBassador),實現發布-訂閱模式,徹底解耦事件產生者與消費者:
// 發布
eventBus.post(new ButtonClickEvent("blue"));
// 訂閱(任意類別,無需繼承介面)
@Subscribe
public void onBlueClick(ButtonClickEvent event) {
if ("blue".equals(event.getButtonId())) getContentPane().setBackground(Color.BLUE);
}