是什么?
有什么用?
serialVersionUID是什么?
基础 & 操作
注意:如下创建的任何类,如不加以说明,均在javas.advanced.serializable包下。
查看序列值的方式是使用serialver,该文件和javac/java并列在$JAVA_HOME/bin下。使用方式有两种:
- 界面方式:
serialver -show启动程序,然后输入类的全名- 命令方式:
serialver 类的全名即可打印类的序列值或提示类没有被序列化
查看序列值
问题:一个没有继承任何类或实现任何接口的类有没有序列值?(答案:没有。如下为验证)
- 创建一个类NotSerializableSample.java,内容如下
public class NotSerializableSample {
}使用界面方式得到的结果截图如下(此处需要说明的是,执行该命令时,需要和该类的class文件所在的顶级包javas并列,由于本类并没使用第三方jar,所以不用使用-cp参数):

使用命令方式得到的结果截图如下:

上面两种方式得到的结果是一样的,以后我们只采用命令方式查看得到的结果。
问题:一个仅实现Serializable接口的的类有没有序列值?(答案:有。如下为验证)
- 创建一个类SimplestSerializableSample.java,内容如下
public class SimplestSerializableSample implements java.io.Serializable {
}得到的结果截图如下:

文字信息就是:
private static final long serialVersionUID = 4702323230704035076L;这个值和类是对应的。把这个class文件发给任何人,他们执行同样的命令,得到的结果应该是一样的。
serialVersionUID有两种生成方式:
- 显式定义,就是在类中添加一个
private static final long serialVersionUID = xxxL,其中xxx可以是任意合法的long值 - 隐式生成,jvm自动根据类名、接口名、成员方法以及属性等来生成一个64位的Hash值
可以看到我们的SimplestSerializableSample使用的就是隐式生成方式。
问题:如何序列化一个对象到文件中?
- 新建一个类SerializableObjSample.java,内容如下:
public class SerializableObjSample implements Serializable {
private int i;
private String str;
public SerializableObjSample() {
}
public SerializableObjSample(int i, String str) {
this.i = i;
this.str = str;
}
public int getI() {
return i;
}
public SerializableObjSample setI(int i) {
this.i = i;
return this;
}
public String getStr() {
return str;
}
public SerializableObjSample setStr(String str) {
this.str = str;
return this;
}
}- 同时新建一个Main.java,内容如下:
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("c:" + File.separator + "sos5.txt");
// 写入
OutputStream os = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(new SerializableObjSample(5, "abcd"));
oos.close();
// 读取
InputStream is = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(is);
SerializableObjSample sos = (SerializableObjSample) ois.readObject();
ois.close();
System.out.println("i = " + sos.getI());
System.out.println("str = " + sos.getStr());
}
}- 查看序列值截图为

- 运行Main类,得到结果为
i = 5
str = abcd可知ObjectOutputStream.writeObject()确实把SerializableObjSample的实例对象序列化到文件中;又通过ObjectInputStream.readObject()读取出来了。
问题:如果不显示定义serialVersionUID,类的内容变更后会有什么问题?(答案:出现InvalidClassException异常)
- 运行Main类,以在c盘生成sos5.txt (此时把sos5.txt文件备份一份比较可行)
- 在原来SerializableObjSample类的基础上添加一个成员变量
private String str2;,并提供set/get方法 - 注释或删除Main类的写入操作,再次运行Main类,看发生什么。此时Main代码变成如下:
public class Main {
public static void main(String[] args) throws Exception {
File file = new File("c:" + File.separator + "sos5.txt");
// 读取
InputStream is = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(is);
SerializableObjSample sos = (SerializableObjSample) ois.readObject();
ois.close();
System.out.println("i = " + sos.getI());
System.out.println("str = " + sos.getStr());
}
}- 运行出错,异常信息截图如下:

异常类型是java.io.InvalidClassException,说明类无效,原因写入的文件中的对象的serialVersionUID与本地的类的serialVersionUID不一致。
问题:上面的问题,如果显示定义serialVersionUID呢?(答案:不会出现InvalidClassException异常,保是新的属性的值是默认初始化值)
- 新建SerializableObj2Sample.java,显示定义serialVersionUID=1L,即在类中添加
private static final long serialVersionUID = 1L;,它的内容如下:
public class SerializableObj2Sample implements Serializable {
private static final long serialVersionUID = 1L;
private int i;
private String str;
public SerializableObj2Sample() {
}
public SerializableObj2Sample(int i, String str) {
this.i = i;
this.str = str;
}
public int getI() {
return i;
}
public SerializableObj2Sample setI(int i) {
this.i = i;
return this;
}
public String getStr() {
return str;
}
public SerializableObj2Sample setStr(String str) {
this.str = str;
return this;
}
}- 新建Main2.java,其内容为:
public class Main2 {
public static void main(String[] args) throws Exception {
File file = new File("c:" + File.separator + "so2s5.txt");
// 写入
OutputStream os = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(new SerializableObj2Sample(5, "abcd"));
oos.close();
// 读取
InputStream is = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(is);
SerializableObj2Sample sos = (SerializableObj2Sample) ois.readObject();
ois.close();
System.out.println("i = " + sos.getI());
System.out.println("str = " + sos.getStr());
}
}- 运行Main2程序是正常打印 i = 5 str = abcd的
- 为SerializableObj2Sample.java添加一个属性
private String str2;并提供set/get方法 - 修改Main2.java,注释或删除写入文件的操作,并打印str2的值。最终内容如下:
public static void main(String[] args) throws Exception {
File file = new File("c:" + File.separator + "so2s5.txt");
// 写入
// OutputStream os = new FileOutputStream(file);
// ObjectOutputStream oos = new ObjectOutputStream(os);
// oos.writeObject(new SerializableObj2Sample(5, "abcd"));
// oos.close();
// 读取
InputStream is = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(is);
SerializableObj2Sample sos = (SerializableObj2Sample) ois.readObject();
ois.close();
System.out.println("i = " + sos.getI());
System.out.println("str = " + sos.getStr());
System.out.println("str2 = " + sos.getStr2());
}- 打印结果为
i = 5
str = abcd
str2 = null没有出现异常java.io.InvalidClassException,且i和str的值已正确回显,str2为null是因为之前序列化到文件时SerializableObj2Sample还没有这个属性。对比可以发现,没有出现异常的原因是序列化前后serialVersionUID没有变化。
上面依次验证了基本的功能。全部理解之后,可以自问如下问题:
- 序列化时,你希望某些成员不要序列化?你如何实现它?
- 如果类中的一个成员未实现可序列化接口, 会发生什么情况?
- Java中的可序列化接口和可外部接口之间的区别是什么?
- 如果类是可序列化的, 但其超类不是, 则反序列化后从超级类继承的实例变量的状态如何?
- 是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?
- 假设新类的超级类实现可序列化接口, 如何避免新类被序列化?
- 在 Java 中的序列化和反序列化过程中使用哪些方法?
- 假设你有一个类,它序列化并存储在持久性中, 然后修改了该类以添加新字段。如果对已序列化的对象进行反序列化, 会发生什么情况?
- Java序列化机制中的兼容更改和不兼容更改是什么?
- 在 Java 序列化期间, 哪些变量未序列化?
序列化时,你希望某些成员不要序列化?你如何实现它?
答:静态变量是属性类的,不属于对象,不参与序列化;使用transient关键字可跳过某些属性的序列化,如private transient String str=”abcdefg”; 反序列化时并不会给str赋值的。(!为什么)
如果类中的一个成员未实现可序列化接口, 会发生什么情况?
答:一个实现了序列化接口的类A中含有未实现序列化接口的属性B,在序列化A对象的实例时会报java.io.NotSerializableException异常。可以理解为:一个类想要序列化,自己得实现序列化接口,自己的属性也得实现序列化接口,同样自己的属性中的属性也得实现序列化,依次类推。
Java中的可序列化接口和可外部接口之间的区别是什么?
如果类是可序列化的, 但其超类不是, 则反序列化后从超级类继承的实例变量的状态如何?
答:若父类有无参构造方法,从超类继承的实例变量不会进行序列化,在反序列化时这些值就是初始化值。
是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?
答:可以。可以在当前类中自定义writeObject()及readObject(),方法可以定义成private,jvm可以调用private方法。如下为示例代码(name可能为null,就不要关注这个bug了),
public class UserDefinedSerialize implements Serializable {
private String name;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
private void writeObject(ObjectOutputStream out) throws Exception {
ObjectOutputStream.PutField putFields = out.putFields();
putFields.put("id", id);
putFields.put("name", name + "1");
out.writeFields();
}
private void readObject(ObjectInputStream in) throws Exception {
ObjectInputStream.GetField readFields = in.readFields();
id = readFields.get("id", 0);
String name = (String) readFields.get("name", "");
this.name = name.substring(0, name.length()-1);
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
UserDefinedSerialize uds = new UserDefinedSerialize();
uds.setId(4);
uds.setName("san");
String filePath = "c:/UserDefinedSerialize_tmp.obj";
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream(filePath));
out.writeObject(uds);
out.close();
ObjectInputStream oin = new ObjectInputStream(
new FileInputStream(filePath));
UserDefinedSerialize t = (UserDefinedSerialize) oin.readObject();
oin.close();
System.out.println("id=" + t.getId());
System.out.println("name=" + t.getName());
}
}假设新类的超级类实现可序列化接口, 如何避免新类被序列化?
答:和上题一样,因为父类已实现序列化接口,所以自身也一定实现了,只有在本类中添加writeObject()和readObject()方法。测试方法为新建一个父类实现序列化接口,再新建一个子类,添加writeObject()和readObject()方法,再写个main方法测试一下。(需要注意,父类的属性会被反序列化成功)
在 Java 中的序列化和反序列化过程中使用哪些方法?
答:ObjectInputStream.readObject() ObjectOutputStream.writeObject()
假设你有一个类,它序列化并存储在持久性中, 然后修改了该类以添加新字段。如果对已序列化的对象进行反序列化, 会发生什么情况?
答:这取决于类是否具有其自己的 serialVersionUID。正如我们从上面的问题知道, 如果我们不提供 serialVersionUID, 则 Java 编译器将生成它, 通常它等于对象的哈希代码。通过添加任何新字段, 有可能为该类新版本生成的新 serialVersionUID 与已序列化的对象不同, 在这种情况下, Java 序列化 API 将引发 java.io.InvalidClassException, 因此建议在代码中拥有自己的 serialVersionUID, 并确保在单个类中始终保持不变。
Java序列化机制中的兼容更改和不兼容更改是什么?
答:真正的挑战在于通过添加任何字段、方法或删除任何字段或方法来更改类结构, 方法是使用已序列化的对象。根据 Java 序列化规范, 添加任何字段或方法都面临兼容的更改和更改类层次结构或取消实现的可序列化接口, 有些接口在非兼容更改下。对于兼容和非兼容更改的完整列表, 我建议阅读 Java 序列化规范。
在 Java 序列化期间, 哪些变量未序列化?
答:Java开发人员是否知道静态和瞬态变量的细节。由于静态变量属于类, 而不是对象, 因此它们不是对象状态的一部分, 因此在 Java 序列化过程中不会保存它们。由于 Java 序列化仅保留对象的状态,而不是对象本身。瞬态变量也不包含在 Java 序列化过程中, 并且不是对象的序列化状态的一部分。在提出这个问题之后,面试官会询问后续内容, 如果你不存储这些变量的值, 那么一旦对这些对象进行反序列化并重新创建这些变量, 这些变量的价值是多少?这是你们要考虑的。
Serializable 与 Externalizable
答:
- 两者均是接口
- Externalizable 是 Serializable 的子接口。
- Serializable是个标记接口,没有任何方法;Externalizable中有两个方法writeExternal() readExternal()
- 实现Serializable的类必需显示定义serialVersionUID以防止反序列化失败,而实现Externalizable的类则不需要。
序列化的其它方案
答:json, protobuf, xml
TODO 待验证
- 方法是否参与serialVersionUID的生成?
- serialVersionUID是否已以写在了class文件中了?
- 5和abcd是否也已经写在文件里了?
最后编辑:张三 更新时间:2026-03-12 19:41