1、先举个例子,包含以下几个类
public class Person implements Serializable {
private String name;
private int age;
//get set toString省略
}
public class Client {
public static void main(String[] args) throws Exception{
Person person = new Person();
person.setAge(123);
person.setName("里斯");
SerializationUtil.writeObject(person);
Person person2 = (Person) SerializationUtil.readObject();
System.out.println(person2);
}
}
public class SerializationUtil {
private static final String FILE_NAME = "C:/serialization.txt";
public static void writeObject(Serializable s) throws FileNotFoundException, IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME));
oos.writeObject(s);
oos.close();
}
public static Object readObject() throws FileNotFoundException, IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME));
Object o = ois.readObject();
ois.close();
return o;
}
}
2、首先说下serialVersionUID是干嘛用的
一个类实现Serializable的目的是为了可持久化,比如网络传输或本地存储,上边的实体类Person没有serialVersionUID会有什么问题呢?直接执行main方法输出如下:
Person [name=里斯, age=123]
一切正常,现在在person类中增加属性
private String address;
再次读取文件中的对象
public class Client {
public static void main(String[] args) throws Exception{
// Person person = new Person();
// person.setAge(123);
// person.setName("里斯");
// SerializationUtil.writeObject(person);
Person person2 = (Person) SerializationUtil.readObject();
System.out.println(person2);
}
}
抛出异常
Exception in thread "main" java.io.InvalidClassException: com.tz.serializable.Person; local class incompatible: stream classdesc serialVersionUID = -7123988162792518014, local class serialVersionUID = 4484202720629602315
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:617)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1622)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1517)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1771)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370)
at com.tz.serializable.SerializationUtil.readObject(SerializationUtil.java:30)
at com.tz.serializable.Client.main(Client.java:17)
异常信息里表示本地serialVersionUID = 4484202720629602315而读取到的文件里的serialVersionUID = -7123988162792518014,这两个serialVersionUID是哪里来的呢
这个是编译器在编译的时候帮你生成的,生成的依据是通过包名、类名、继承关系、非私有的方法和属性,以及参数、返回值等很多因素计算出来的,基本上计算出来的值是唯一的。
JVM在反序列化时会比较数据流中的serialVersionUID和类中的serialVersionUID是否一致,一致表示类在传输或存储过程中没有变化,可以把数据流转为实例对象。不一致的话就会抛出上边的异常。
JVM在反序列化时会比较数据流中的serialVersionUID和类中的serialVersionUID是否一致,一致表示类在传输或存储过程中没有变化,可以把数据流转为实例对象。不一致的话就会抛出上边的异常。
所以手动加上一个serialVersionUID会省去很多不必要的麻烦,下次如果生产端person类增加了属性address而消费端未能及时更新时所带来的问题只是新增的属性在消费端不能读取。
3、相关的几个关键字
1)transient:保证被该关键字修饰的变量不被序列化
1)transient:保证被该关键字修饰的变量不被序列化
2)static:也是不会被序列化,大概原因是因为静态变量所在内存位置与一般变量不同,序列化时不会被写入流中
3)final:会被序列化,但是在反序列化时会重新计算变量值(只限于简单对象:8个基本类型、数组、字符串——字符串情况有点复杂,不通过new关键字生成String对象的情况下,final变量的赋值与基本类型相同),但是不能方法赋值,不能通过构造函数赋值
3)final:会被序列化,但是在反序列化时会重新计算变量值(只限于简单对象:8个基本类型、数组、字符串——字符串情况有点复杂,不通过new关键字生成String对象的情况下,final变量的赋值与基本类型相同),但是不能方法赋值,不能通过构造函数赋值
4、举个栗子
public class Person implements Serializable {
/** */
private static final long serialVersionUID = -1940095792507660710L;
private String name;
private int age;
private String address;
private static String staticValue = "staticValue";
private final String finalValue = "finalValue";
private static final String sfValue = "sfValue";
private transient String transientValue = "transientValue";
//省略get set
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address=" + address + ", finalValue=" + finalValue
+ ", transientValue=" + transientValue + ", staticValue=" + staticValue + ", sfValue=" + sfValue + "]";
}
}
public class Client {
public static void main(String[] args) throws Exception{
Person person = new Person();
person.setAge(123);
person.setName("里斯");
SerializationUtil.writeObject(person);
Person person2 = (Person) SerializationUtil.readObject();
System.out.println(person2);
}
}
main方法执行结果
Person [name=里斯, age=123, address=null, finalValue=finalValue, transientValue=null, staticValue=staticValue, sfValue=sfValue]
transientValue=null可以理解, finalValue=finalValue, staticValue=staticValue, sfValue=sfValue,这三个的值是怎么来的呢。首先finalValue和sfValue是在反序列化时重新计算得来的,而staticValue是静态的Person被调用时就赋值了,把上边两个类改一下
private String name;
private int age;
private String address;
private static String staticValue = "staticValue2";
private final String finalValue = "finalValue2";
private static final String sfValue = "sfValue2";
private transient String transientValue = "transientValue2";
public class Client {
public static void main(String[] args) throws Exception{
// Person person = new Person();
// person.setAge(123);
// person.setName("里斯");
// SerializationUtil.writeObject(person);
Person person2 = (Person) SerializationUtil.readObject();
System.out.println(person2);
}
}
本地person类初始值已经变化了,main方法直接从文件读取数据执行结果如下
Person [name=里斯, age=123, address=null, finalValue=finalValue2, transientValue=null, staticValue=staticValue2, sfValue=sfValue2]
finalValue,staticValue,sfValue都已经是本地修改后的Person类的初始值了,与文件中存储的数据没关系了5、关于final重新计算的问题,再举个栗子
person类改成这样
private static final long serialVersionUID = -1940095792507660710L;
private String name;
private int age;
private String address;
private final String finalValue;
public Person() {
finalValue = "通过构造器重新赋值";
}
执行结果
修改person类
private static final long serialVersionUID = -1940095792507660710L;
private String name;
private int age;
private String address;
private final String finalValue;
public Person() {
finalValue = "通过构造器重新赋值2222";
}
再次读取
并没有重新计算为修改后的【通过构造器重新赋值2222】这是什么原因呢?
反序列化的执行过程是这样的:JVM从数据流中获取一个Object对象,然后根据数据流中的类文件描述信息查看,发现是final变量,需要重新计算,于是引用person类中的finalValue值,此时JVM发现变量没有赋值,于是停止初始化而保持原值状态,也就是最先序列化到文件中的【通过构造器重新赋值】(摘自《编写高质量代码——改善Java程序的151个建议》)