Java 無函數指針,但通過接口可優雅實現回調模式。本文從事件驅動模型切入,展示 InterestingEvent 接口回調、EventNotifier 通知者、匿名內部類與 Lambda 簡化寫法,並給出「名字變更器」與「按鈕點擊」兩個完整實戰案例。
// 1. 定義回調接口
interface Callback {
void onEvent(); // 可帶參數、返回值
}
// 2. 事件源持有回調引用
class EventSource {
private Callback callback;
public EventSource(Callback cb) { this.callback = cb; }
public void trigger() {
if (callback != null) callback.onEvent(); // 回調觸發
}
}
// 3. 客戶端實現接口並註冊
class Client implements Callback {
public void onEvent() { System.out.println("事件發生!"); }
public void register() { new EventSource(this).trigger(); }
}
public interface InterestingEvent {
void interestingEvent(); // 可擴展為帶參數:void onEvent(EventData data)
}
public class EventNotifier {
private InterestingEvent ie;
private boolean somethingHappened;
public EventNotifier(InterestingEvent event) { this.ie = event; this.somethingHappened = false; }
public void setHappened() { this.somethingHappened = true; }
public void doWork() {
if (somethingHappened) ie.interestingEvent(); // 觸發回調
}
}
class ButtonPressedEvent implements InterestingEvent {
private EventNotifier en;
public ButtonPressedEvent() { en = new EventNotifier(this); }
@Override public void interestingEvent() { System.out.println("button pressed"); }
}
public class EventNotifierTest {
public static void test() {
// 方式一:顯式實現類
EventNotifier en = new EventNotifier(new ButtonPressedEvent());
en.setHappened(); en.doWork();
// 方式二:匿名內部類(Java 7 風格)
EventNotifier en2 = new EventNotifier(new InterestingEvent() {
@Override public void interestingEvent() { System.out.println("inputtext change"); }
});
en2.setHappened(); en2.doWork();
// 方式三:Lambda 表達式(Java 8+ 推薦)
EventNotifier en3 = new EventNotifier(() -> System.out.println("lambda callback"));
en3.setHappened(); en3.doWork();
}
}
NameChanger 改變 Client 名字,Client 持有 NameChanger 並打印新名字 —— 雙向引用,典型回調結構。
class Client {
private INameChanger changer;
private String clientName;
public Client(INameChanger changer) { this.changer = changer; }
public void showMyNewName() {
String newName = changer.changeName(this); // 回調:傳遞 this 給 changer
System.out.println(newName);
}
public String getName() { return clientName; }
public void setName(String name) { this.clientName = name; }
}
interface INameChanger {
String changeName(Client client); // 回調方法,接收 Client 引用
}
public class ChangeNameTest {
public static void main(String[] args) {
// 匿名內部類實現不同前綴邏輯
Client client = new Client(new INameChanger() {
@Override public String changeName(Client c) { return "Mr. " + c.getName(); }
});
client.setName("Tom"); client.showMyNewName(); // Mr. Tom
Client client2 = new Client(new INameChanger() {
@Override public String changeName(Client c) { return "Miss. " + c.getName(); }
});
client2.setName("Lucy"); client2.showMyNewName(); // Miss. Lucy
// Lambda 版 (Java 8+)
Client client3 = new Client(c -> "Dr. " + c.getName());
client3.setName("House"); client3.showMyNewName(); // Dr. House
}
}
interface A {}
class B implements A {}
class C implements A {}
class Test {
A b = new B(); // 面向接口編程
A c = new C();
}
本質:接口類型變量引用具體實現對象,運行時動態綁定具體行為。
| 優點 | 缺點 | 現代替代 |
|---|---|---|
| 解耦事件源與處理邏輯 | 匿名內部類啰嗦 (Java 7) | Lambda 表達式 (Java 8+) |
| 支持多種實現熱插拔 | 回調地獄:多層嵌套難閱讀 | CompletableFuture/Reactive Streams (RxJava/Project Reactor) |
| 類型安全,編譯期檢查 | 單方法接口仍需定義接口類 | 函數式接口 @FunctionalInterface + 方法引用 |
| 可傳遞上下文 (this) | 異常傳播需手動處理 | Try-with-resources / 結構化併發 |
XxxListener、XxxCallback、XxxHandler,單方法加 @FunctionalInterface。onEvent(Event e))。WeakReference 或提供 removeListener()。try-catch 包裹回調調用,防單個監聽器異常阻塞其他監聽器。Java 以單方法接口模擬函數指針,實現類型安全的回調機制。從 Swing ActionListener 到 Spring ApplicationListener,再到現代 CompletableFuture 併發編排,回調模式貫穿 Java 生態。掌握「接口定義契約、事件源持有引用、客戶端實現註冊」這一核心三要素,配合 Lambda 與響應式框架,能讓你在事件驅動、異步編程、插件架構等場景中游刃有餘。