1. 引言

在本篇文章中,我们将深入探讨 Java 中的 标记接口(Marker Interface)。虽然它在现代开发中已不如早期那么常见,但在特定场景下依然具有实用价值。

2. 标记接口是什么?

标记接口是一种不包含任何方法或常量的接口。它的主要作用是为 JVM 或运行时环境提供对象类型的附加信息,以便在运行时做出相应的处理决策。

✅ 简单来说,它就是一个“标签”,告诉 JVM:“嘿,这个对象有某种特殊身份”。

由于它不定义任何行为,所以也被称为 标签接口(Tagging Interface)

⚠️ 虽然依然可用,但标记接口往往暗示着代码设计上的某种妥协。在现代 Java 开发中,更多推荐使用注解(Annotation)来替代标记接口。

3. JDK 内置的标记接口

Java 标准库中已经为我们提供了几个常用的标记接口,比如:

  • Serializable
  • Cloneable
  • Remote

3.1 Cloneable 接口示例

如果我们尝试克隆一个没有实现 Cloneable 接口的对象,JVM 会抛出 CloneNotSupportedException 异常。

public class Entity implements Cloneable {
    // 实现细节
}

也就是说,Cloneable 是一个标记接口,用来告诉 JVM:这个对象可以被克隆

3.2 Serializable 接口示例

当我们使用 ObjectOutputStream.writeObject() 方法时,JVM 会检查对象是否实现了 Serializable 接口。如果没有,就会抛出 NotSerializableException

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    // 其他字段和方法
}

✅ 所以,这两个接口本质上是运行时的“通行证”。

4. 自定义标记接口

我们也可以自定义标记接口。比如,我们想标识某个对象是否可以被从数据库中删除:

public interface Deletable {
}

然后让实体类实现这个接口:

public class Entity implements Deletable {
    // 实现细节
}

在 DAO 层中,我们可以这样控制删除逻辑:

public class ShapeDao {

    // 其他方法...

    public boolean delete(Object object) {
        if (!(object instanceof Deletable)) {
            return false;
        }

        // 删除逻辑...

        return true;
    }
}

✅ 这样,我们就通过标记接口在运行时控制了对象的行为。

5. 标记接口 vs 注解

自从 Java 5 引入了注解机制后,很多原本使用标记接口的场景都可以用注解来替代。

比如我们可以这样定义一个注解:

@Retention(RetentionPolicy.RUNTIME)
public @interface Deletable {
}

然后用在类上:

@Deletable
public class Entity {
    // 实现细节
}

但注解和标记接口有一个本质区别:

❌ 注解不具备多态性,而接口可以。

5.1 利用接口实现多态约束

比如我们希望只有 Shape 类型的对象可以被删除,我们可以这样设计:

public interface Shape {
    double getArea();
    double getCircumference();
}

然后定义一个标记接口继承 Shape

public interface DeletableShape extends Shape {
}

实现类:

public class Rectangle implements DeletableShape {
    // 实现细节
}

✅ 这样一来,所有 DeletableShape 的实现类也必须是 Shape 的实现类,实现了类型约束。

⚠️ 但要注意:所有继承 Rectangle 的类也会自动实现 DeletableShape,这可能不是我们想要的。

6. 标记接口 vs 普通接口

有人可能会问:我们为什么不用普通接口来判断类型,而非要用标记接口?

比如我们可以这样写:

public boolean delete(Object object) {
    if (!(object instanceof Shape)) {
        return false;
    }

    // 删除逻辑...

    return true;
}

看起来也能达到目的,对吧?

但问题来了:如果我们还希望删除 Person 对象呢?

6.1 第一种做法:不断加条件

if (!(object instanceof Shape || object instanceof Person)) {
    return false;
}

❌ 问题是:每增加一个类型,都要改这个方法,扩展性差。

6.2 第二种做法:让 Person 实现 Shape

public class Person implements Shape {
    // 实现 getArea() 等方法...
}

❌ 这就明显不合理了,难道 Person 是一种 Shape 吗?显然不是。

✅ 所以,在这种场景下,使用标记接口是更合理、更清晰的设计。

7. 总结

在这篇文章中,我们:

  • 介绍了标记接口的定义和作用;
  • 查看了 JDK 中几个常见的标记接口;
  • 实现了一个自定义标记接口;
  • 比较了标记接口与注解、普通接口的异同;
  • 指出了在哪些场景下使用标记接口更合适。

虽然标记接口在现代开发中使用频率不高,但在需要运行时类型标识类型约束的场景中,它依然是一个简单粗暴且有效的工具。

📌 本文代码示例可参考:GitHub 项目地址


原始标题:Marker Interfaces in Java | Baeldung