Java序列化与反序列化的语法应用

是的,Java默认序列化必须实现Serializable接口,否则抛出NotSerializableException;该接口为标记接口,不实现则无法触发JVM内置序列化逻辑,且手动重写writeObject/readObject仍需显式实现它。

Java序列化必须实现Serializable接口吗?

是的,但仅限于使用默认序列化机制(即调用ObjectOutputStream.writeObject())时。JVM会检查类是否实现了Serializable接口,否则抛出java.io.NotSerializableException

注意:Serializable是标记接口,无方法,但它的存在会触发JVM内置序列化逻辑。若不实现该接口,又想序列化对象,必须手动实现writeObject/readObject方法并声明private void writeObject(ObjectOutputStream)等,但这仍要求类或其父类显式实现Serializable——否则编译器不会允许你重写这些私有方法。

  • transient字段不会被序列化,反序列化后为默认值(如null0false
  • 静态字段(static)从不参与序列化,无论是否transient
  • 子类序列化时,若父类未实现Serializable,则父类部分字段在反序列化后会被执行父类无参构造函数初始化

为什么serialVersionUID不加会出问题?

不显式定义serialVersionUID时,JVM会根据类名、接口、成员变量、方法签名等自动生成一个64位哈希值。一旦类结构变化(如增删字段、改访问修饰符、调整继承关系),这个值就变了,导致反序列化时抛出InvalidClassException: class invalid for deserialization

显式声明可避免意外失败,尤其在分布式系统或持久化场景中:

private static final long serialVersionUID = 1L;
  • 建议用serialver命令工具生成真实哈希值(如serialver -classpath . com.example.User
  • 修改非破坏性字段(如新增transient字段、增加static方法)通常不影响兼容性,但serialVersionUID不变更更稳妥
  • 若明确要破坏兼容(如彻底重构类),应主动更新serialVersionUID值,让旧数据无法误加载

反序列化时readOb

ject
方法怎么安全使用?

直接调用ObjectInputStream.readObject()可能反序列化任意类,引发远程代码执行(如通过BadAttributeValueExpException链)。因此,生产环境必须限制可反序列化的类型。

  • 优先使用白名单机制:继承ObjectInputStream,重写resolveClass(),只允许指定类名通过
  • 避免在readObject中执行外部可控逻辑(如反射调用、IO操作、网络请求)
  • 若需校验数据,应在readObject末尾做字段合法性检查,并抛出InvalidObjectException终止反序列化
  • 敏感字段(如密码、token)务必声明为transient,且不在readObject中恢复

示例白名单控制:

public class SafeObjectInputStream extends ObjectInputStream {
    private static final Set ALLOWED_CLASSES = Set.of(
        "java.lang.String",
        "com.example.User",
        "java.util.ArrayList"
    );

    public SafeObjectInputStream(InputStream in) throws IOException {
        super(in);
    }

    @Override
    protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        if (!ALLOWED_CLASSES.contains(desc.getName())) {
            throw new InvalidClassException("Unauthorized deserialization: " + desc.getName());
        }
        return super.resolveClass(desc);
    }
}

替代方案比原生序列化更实用?

原生Java序列化耦合JVM版本、安全性差、跨语言能力为零,除极少数遗留系统外,不建议用于网络传输或持久化存储。

  • JSON优先选JacksonObjectMapper)或Gson:可读性强、语言无关、支持注解控制(如@JsonIgnore
  • 高性能二进制场景用Protobuf(需定义.proto文件 + 生成代码)或Kryo(无需实现接口,但需注册类)
  • 数据库存对象?别序列化——拆成字段映射到关系表,或用JSON类型列(MySQL 5.7+、PostgreSQL)存标准化JSON
  • 缓存场景(如Redis)推荐StringRedisTemplate配JSON序列化器,而非RedisTemplate默认的JDK序列化

真正需要原生序列化的地方极少:比如同一JVM内短期传递不可变配置对象,或调试时临时保存对象快照。