本文共 4861 字,大约阅读时间需要 16 分钟。
序列化的含义和意义
序列化指将Java对象转换成字节序列, 这些字节序列可以保存在磁盘上, 或者进行网络传输。反序列化即指将序列化后的字节序列重新恢复成对象。序列化机制使得对象可以脱离程序的运行而独立存在。
一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口,这个接口不包含任何方法或成员变量,只是一个标记。
序列化/反序列化
序列化通过ObjectOutputStream来实现,它可以把一个Java对象写入字节流。
当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名.
反序列化使用ObjectInputStream对象,它可以从字节流中读取一个Java对象。若反序列化时要使用readObject()读出多个对象,注意要与写入的顺序一致。
示例:
public class SerializableDemo {
public static void main(String[] args) throws Exception {
writeObj();
readObj();
}
private static void writeObj() throws Exception {
File file = new File("E:\\person.ser");
FileOutputStream out = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
Person person = new Person("王大锤", 25);
objectOutputStream.writeObject(person);
System.out.println("写入的对象:" + person);
objectOutputStream.close();
}
private static void readObj() throws Exception{
File file = new File("E:\\person.ser");
FileInputStream in = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(in);
Person person = (Person)objectInputStream.readObject();
System.out.println("读取的对象:" + person);
objectInputStream.close();
}
}
class Person implements Serializable{
private String name;
private int age;
public Person(String name, int age) {
System.out.println("Person's constructor");
this.name = name;
this.age = age;
}
//...省略getter、setter、toString
}
程序输出:
Person's constructor
写入的对象:Person{name='王大锤', age=25}
读取的对象:Person{name='王大锤', age=25}
可以看到反序列化时,Person的构造器并没有执行。反序列化时,是由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时将不被执行。
看另一个示例:
public static void main(String[] args) throws Exception {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOut);
Person person = new Person("张三", 20);
objectOutputStream.writeObject(person);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
Person readPerson = (Person)objectInputStream.readObject();
System.out.println("反序列化得到的Person对象:" + readPerson);
System.out.println("反序列化得到的对象与原对象是否是同一对象:" + (person==readPerson));
}
输出:
反序列化得到的Person对象:Person{name='张三', age=20}
反序列化得到的对象与原对象是否是同一对象:false
可见反序列化得到的对象与原来的person不是同一个对象,即反序列化得到的是一个新的对象。
serialVersionUID
每一个可序列化类都会带有一个long类型的serialVersionUID静态常量值,如果没有人为显式定义过serialVersionUID,那编译器会根据类的各种信息为它自动声明一个。
serialVersionUID是序列化前后类的唯一标识符,在反序列化时,jvm会把字节流中的序列号id和被序列化类的序列号id进行比较,只有两个id相同才能成功反序列化,否则抛出InvalidClassException异常。
若是没有人为指定序列号id,则若是改变类时(如添加新的成员变量),默认的serialVersionUID值将会改变。
示例:
执行上述例子的writeObj()方法后,在Person类定义中添加一个birth成员变量:private Date birth;
再执行readObj()进行反序列化,此时抛出异常:即字节流中的serialVersionUID与Person类的不同。
Exception in thread "main" java.io.InvalidClassException: io.stream.serializable.Person; local class incompatible: stream classdesc serialVersionUID = 5578959987866231607, local class serialVersionUID = -8466270221378179612
一般对于可序列化类,为了serialVersionUID的确定性,都要人为指定一个serialVersionUID值,可以使用idea的自动生成功能:IDEA自动生成serialVersionUID 。
反序列化时的异常
在反序列化时readObject()可能抛出的异常有:
ClassNotFoundException:没有找到对应的Class;
InvalidClassException:Class不匹配。
抛出ClassNotFoundException的情况:一台电脑上的Java程序把一个Java对象序列化以后,通过网络传给另一台电脑上的另一个Java程序,但是这台电脑的Java程序并没有定义该类,所以无法反序列化,抛出该异常。
抛出InvalidClassException的情况:如serialVersionUID中的示例情况。
序列化的特殊情况
当要序列化的对象中包含其他引用类型变量的引用时,该变量对应的类也要实现Serializable接口。
对同一个对象多次序列化,只有第一次序列化时JVM才会将对象转换为字节序列,之后的序列化只是直接输出一个序列化编号。且反序列化时多次使用readObject()读出的是同一个对象。
示例:
示例中的Person类同上。新增了一个ID类:
package stream.serializable;
import java.io.Serializable;
public class ID implements Serializable {
private static final long serialVersionUID = -5829455553074732547L;
private int number;
private Person person;
//...省略getter、setter、toString
}
创建一个Person对象person和一个ID对象id,id中的Person指向person。将person和id序列化。再反序列,可看到readId.getPerson()==readPerson返回true。
public static void main(String[] args) throws Exception {
File file = new File("E:\\id.ser");
FileOutputStream out = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
ID id = new ID();
Person person = new Person("王大锤", 30);
id.setNumber(1);
id.setPerson(person);
objectOutputStream.writeObject(id);
objectOutputStream.writeObject(person);
FileInputStream in = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(in);
ID readId = (ID) objectInputStream.readObject();
Person readPerson = (Person) objectInputStream.readObject();
System.out.println(readId);
System.out.println(readPerson);
System.out.print("readId.getPerson()==readPerson:");
System.out.print(readId.getPerson()==readPerson);
}
凡是被static修饰的字段是不会被序列化的。
因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域。
凡是被transient修饰符修饰的字段也是不会被序列化的。
transient修饰符的作用就是修饰不想被序列号的成员变量,一般是对于一些要保密的字段,如密码。对于被transient修饰的字段,在序列号时以null值填充(对于值类型,则使用0、false等默认值)。
还有一个点是序列化的受控和加强,具体见第3个参考链接中最后一个小节。
参考
---------------------------------------------------------
转载地址:https://blog.csdn.net/weixin_28871097/article/details/114726839 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!