你好,我是吴计可师,一个工作十多年的后端开发,曾就职京东、阿里等多家互联网头部企业。
文章可能会比较长,主要解析的非常详解,或涉及一些底层知识,供面试高阶难度用。可以根据自己实际理解情况合理取舍阅读
对象持久化:将对象的状态保存到磁盘中,以便后续恢复。这在保存用户数据、配置文件、缓存等场景中非常常见。
网络传输:将对象通过网络发送到远程主机。序列化将对象转化为字节流,可以通过网络协议进行传输(如 HTTP、RPC 等)。
分布式系统中的通信:在分布式系统中,不同服务之间可能需要传递对象数据,序列化将对象转化为字节流,使得数据可以在不同的系统之间传递。
序列化(Serialization):将 Java 对象转换成字节流。Java 提供了 ObjectOutputStream 类来实现这一过程。这个字节流可以被存储在磁盘上,或通过网络传输。
调用 ObjectOutputStream.writeObject() 方法将对象写入字节流。
序列化对象时,如果对象包含引用类型的字段,这些字段也会递归地被序列化。
序列化时还会保存类的元数据(类名、字段类型等)。
反序列化(Deserialization):将字节流重新构造成原来的 Java 对象。Java 提供了 ObjectInputStream 类来实现这一过程。
调用 ObjectInputStream.readObject() 方法从字节流中恢复对象。
在反序列化时,Java 会根据字节流中的数据(如类名、字段值)重建对象及其状态。
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// 构造方法、getters 和 setters
}
上面的 Person 类实现了 Serializable 接口,表示它的对象可以被序列化。
import java.io.*;
public class SerializationExample {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
out.writeObject(person); // 将对象写入文件
System.out.println("对象已序列化");
} catch (IOException e) {
e.printStackTrace();
}
}
}
反序列化过程:
import java.io.*;
public class DeserializationExample {
public static void main(String[] args) {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person person = (Person) in.readObject(); // 从文件读取对象
System.out.println("对象已反序列化:" + person.getName() + ", " + person.getAge());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
serialVersionUID:
serialVersionUID 是用来验证序列化版本的标识符。每当类的结构发生变化时,serialVersionUID 应该做相应的修改。
如果没有显式定义,Java 会自动生成一个 serialVersionUID,但它是根据类的结构自动生成的,不适合跨版本兼容。因此,建议显式定义一个 serialVersionUID。
private static final long serialVersionUID = 1L;
transient 关键字:
如果你不希望某个字段被序列化,可以使用 transient 关键字修饰该字段。这样,字段值在序列化时会被忽略,反序列化时该字段会被赋予默认值(如 null、0 等)。
private transient String password; // 该字段不会被序列化
继承关系中的序列化:
当一个类实现了 Serializable 接口时,它的子类会自动继承这一特性,不需要子类显式实现 Serializable。但是,如果父类没有实现 Serializable,子类也必须实现该接口,才能进行序列化。
性能问题:序列化和反序列化是比较昂贵的操作,特别是当对象的字段较多或者对象深度嵌套时。应当合理使用序列化,例如避免在高频请求中反复进行序列化操作。
7.1 如果一个类没有实现 Serializable 接口,会发生什么情况?
如果一个类没有实现 Serializable 接口,那么该类的对象不能被序列化。当尝试对该对象进行序列化操作时,Java 会抛出 java.io.NotSerializableException 异常。
serialVersionUID 是一个版本控制号,用于验证序列化和反序列化过程中类的版本一致性。当类在不同版本之间发生变化时,反序列化时会检查 serialVersionUID,如果不同则抛出 InvalidClassException 异常。它是保证反序列化过程不会出错的重要字段。
作用:
确保序列化版本一致性。
防止因为类的结构变化导致反序列化失败。
示例:
private static final long serialVersionUID = 1L;
Serializable:它是一个标记接口,Java 默认提供实现来处理序列化和反序列化。你可以通过实现这个接口,使得对象可以被序列化。序列化的过程由 Java 自动进行,通常会将类的所有字段进行序列化(除非被标记为 transient)。
Externalizable:它是 Serializable 的子接口,提供了更细粒度的控制。它要求类必须实现 writeExternal() 和 readExternal() 方法,开发者可以自己定义如何序列化和反序列化对象。
区别:
Serializable 序列化过程是自动的,Externalizable 需要手动定义序列化和反序列化的行为。
Externalizable 在序列化时允许开发者选择性地存储字段,给了更大的灵活性。
可以使用 transient 关键字来标记不希望被序列化的字段。被 transient 修饰的字段在序列化过程中将被忽略,不会被写入字节流。
private transient String password; // 这个字段不会被序列化
7.5 如果一个类的父类实现了 Serializable 接口,子类需要实现 Serializable 吗?
回答:如果一个类的父类实现了 Serializable 接口,子类会自动具备序列化能力,除非子类显式声明不实现 Serializable。也就是说,子类继承了父类的序列化特性,但可以通过重写 writeObject 和 readObject 方法来定制序列化行为。
修改类后,如果没有相应地更新 serialVersionUID,反序列化时可能会抛出 InvalidClassException,因为类的版本不匹配。为了防止这种情况,通常建议在类中声明 serialVersionUID,即使你修改了类的结构,也可以保持兼容性。
解决方法:
修改类时更新 serialVersionUID。
在修改类时保持 serialVersionUID 一致,或使用兼容的方式修改类字段(如保持字段名称不变)。
解决方法: 在类中定义 readObject 方法,或者在反序列化后通过其他方式恢复 transient 字段的值。
回答:如果一个对象中的字段是引用类型,那么仅仅通过 clone() 或 Serializable 的浅拷贝方式是不够的,因为引用类型的字段会共享原对象中的实例。深度复制(Deep Clone)要求对对象的所有字段(包括引用类型字段)进行递归复制。
通过 Serializable 实现深度复制的一种方式是:将对象序列化为字节流,再通过反序列化恢复对象,达到深度复制的目的。
今天的内容就分享到这儿,喜欢的朋友可以关注,点赞。有什么不足的地方欢迎留言指出,您的关注是我前进的动力!