整理 Effective Java 書中 Item 41: Use marker interfaces to define types 心得筆記

主旨

標記介面(Marker Interface)是沒有任何方法的介面,只是單純用來代表一個「型別」。雖然 Java 也可以用標註(annotation)來達到類似效果,但在要表示「某類型具備某種能力」時,還是推薦使用標記介面,因為它可以帶來更強的型別安全與語意明確性。

點出問題

有些時候,我們希望標示某個類別具備某種能力,例如:

  • 可以被序列化(Serializable)
  • 支援克隆(Cloneable)
  • 是 Thread-safe 的(假設你自訂一個介面 ThreadSafe)

這種情況,很多人會選擇用 @interface 自訂註解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ThreadSafe {}

然後在類別上加上註解:

@ThreadSafe
public class SafeCounter { ... }

乍看之下沒問題,但這樣做其實缺少了型別檢查與語意上的一致性。

範例:標記介面的優勢

來看個實際的例子:

public interface ThreadSafe {}

public class SafeCounter implements ThreadSafe {
    private int count = 0;
    public synchronized void increment() { count++; }
}

假設有個方法只接受 thread-safe 的實作:

public void useSafely(ThreadSafe obj) {
    // 呼叫邏輯,這裡保證 obj 是安全的
}

這時候,如果你傳入一個沒有實作 ThreadSafe 的物件,編譯器就會報錯,這就是介面帶來的型別保護。

反觀使用 @ThreadSafe 標註的寫法,這類檢查只能靠 runtime 的反射判斷,而且失去編譯時的幫助。

劃重點

  • 標記介面是介面,但不定義任何方法
  • 可作為方法參數的型別,帶來編譯期型別檢查
  • 清楚地表達一個類別「是」某種型別(例如 Serializable
  • 比註解更適合用來表示「型別能力」
  • 不適合跨層架構使用時,可考慮使用註解取代(例如框架掃描註解)

真實世界範例

JDK 本身就是用標記介面的最佳範例,像是:

public interface Serializable {}
public interface Cloneable {}

JVM 內部會根據這些介面做特別處理,例如序列化只能針對實作 Serializable 的類別進行。

你也可以在專案中自訂:

public interface Auditable {}

讓系統能夠針對實作 Auditable 的物件,自動記錄資料修改紀錄或存取歷史。

小結

當需要讓某個類別具備某種語意特性時(例如可序列化、支援安全併發),比起使用註解,定義一個空介面(標記介面)通常是更好的做法。它可以讓 API 在編譯時就能受益於型別檢查,也讓使用者的意圖更清楚。除非需要的是跨層的註解掃描或 metadata 設定,否則盡量避免濫用註解來標示型別語意。