当我们需要把Java对象保存在磁盘中或者要在网络中传播,那我们就要用到Java对象的序列化和反序列化。对象序列化把内存中的对象转换成跟平台无关的二进制流,从而可以永久的保存在磁盘中或者在网络中传播。这在现实开发中经常用到,如:web容器把Session保存在磁盘中,减少内存的占用,等需要时,再反序列化。还有分布部署程序时,我们用到的RPC远程访问别人方法返回对象时等等。
实现序列化
我们只要实现Serializable接口就行,当然Java已经帮我们封装好了实现类,所以Serializable接口是一个标记接口,我们不用实现任何方法,它就是用来表明这个类对象是可序列化的。如:
publicclassdogimplementsSerializable{
privatestaticfinallongserialVersionUID=1L;//版本号,反序列化那里会说到
privateStringname;
privateStringage;
publicdog(Stringname,Stringage){
this.name=name;
this.age=age;
System.out.println("调用了构造方法");
}
publicStringgetName(){
returnname;
}
publicvoidsetName(Stringname){
this.name=name;
}
publicStringgetAge(){
returnage;
}
publicvoidsetAge(Stringage){
this.age=age;
}
}
使用对象流实现序列化
1.创建一个ObjectOutputStream,这个是对象处理流,所以必须建立在其他文件流上(FileOutputStream)
ObjectOutputStreamoutputStream=newObjectOutputStream(newFileOutputStream("Object.txt"));
2.然后用ObjectOutputStream的writeObject(Object object)方法就可以输出序列化对象。
dog d=newdog("小狗","12");
outputStream.writeObject(d);
使用对象流实现反序列化
1.创建一个ObjectInputStream,这个是对象处理流,所以必须建立在其他文件流上(FileInputStream)
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream(“Object.txt”));
2然后用创建一个ObjectInputStream的raadObject()方法就可以输入序列化对象
ObjectInputStreaminputStream=newObjectInputStream(newFileInputStream("Object.txt"));
dog dd=(dog)inputStream.readObject();
readObject返回的是Object,我们知道类型可以强制转换。
注意:由于我们读取到的是对象,所以我们必须要有java对象的class文件,而且我们知道dog类只有一个有参构造方法,这个构造方法里有输出语句,但是我们获得对象时,并没有输出,所以我们可以得出反序列化时,我们不用构造方法来初始化对象。
对象引用的序列化
当你要序列化的对象有(除了基本类型和String类型外)引用其他对象时,这个对象必须也是要序列化的。
publicclassCatimplementsSerializable{
privatedog d;
privateStringname;
publicCat(dog d,Stringname){
this.d=d;
this.name=name;
}
publicdog getD(){
returnd;
}
publicvoidsetD(dog d){
this.d=d;
}
publicStringgetName(){
returnname;
}
publicvoidsetName(Stringname){
this.name=name;
}
}
Cat类有一个dog对象,只有dog对象是可序列化的,Cat类对象才可以序列化,因为当你序列化Cat类对象时,由于dog对象不可序列化,所以在反序列化时为了正常返回Cat对象,要对dog对象进行反序列化,所以dog对象要是可序列化的。当然我们可以用transient关键字进行指定哪些不用序列化,后面会说到。
序列号
当你对同一个对象序列化多次,反序列化回来的多个对象怎么保证是同一个呢,所以就有了序列号。
ObjectOutputStreamoutputStream=newObjectOutputStream(newFileOutputStream("Object.txt"));
dog d=newdog("小狗","12");
outputStream.writeObject(d);
outputStream.writeObject(d);
我们对d这个对象序列化3次,正常每次序列化都会把d这对象写入到磁盘中,就有了3个d对象在磁盘中,当你反序列化时就有了3个不同的对象,于是Java引入了序列号。
所有保存到磁盘的对象都有一个序列号。
当程序序列号一个对象时,先查看这个对象是否序列号,只有没有序列化过的,系统才会把这个对象转换成字节序列化到磁盘中,或则直接输出序列号
ObjectInputStreaminputStream=newObjectInputStream(newFileInputStream("Object.txt"));
dog d1=(dog)inputStream.readObject();
dog d2=(dog)inputStream.readObject();
System.out.println(d1==d2);
版本号
我们发现每个序列化的类都有这个 ,如果没有系统每次都会随机生成
private static final long serialVersionUID = 1L;
这个我们称为版本号,为了就是当你反序列化时,可以兼容。也就是说,当你的类升级后,只要这个编号不变,序列化机制就把它们当成同一个版本,这样,如果当一个类对象让你序列化后,你修改了这个类,保证改对象可以正常的被反序列化。如果你没有写,改变量由jvm更具类信息就行计算,所以修改后肯定不同,就不能正常的反序列化了。
transient关键字
这个关键字就是让序列化机制不用理会它修饰的变量,这个变量不会被序列化
publicclassdogimplementsSerializable{
privatestaticfinallongserialVersionUID=1L;
privateStringname;
privatetransientStringage;
publicdog(Stringname,Stringage){
this.name=name;
this.age=age;
System.out.println("调用了构造方法");
}
publicStringgetName(){
returnname;
}
publicvoidsetName(Stringname){
this.name=name;
}
publicStringgetAge(){
returnage;
}
publicvoidsetAge(Stringage){
this.age=age;
}
}
测试
publicclasstest{
publicstaticvoidmain(Stringarg[])throwsException{
ObjectOutputStreamoutputStream=newObjectOutputStream(newFileOutputStream("Object.txt"));
dog d=newdog("小狗","12");
outputStream.writeObject(d);
ObjectInputStreaminputStream=newObjectInputStream(newFileInputStream("Object.txt"));
dog d1=(dog)inputStream.readObject();
System.out.println("name:"+d1.getName());
System.out.println("age:"+d1.getAge());
}
}
结果:
调用了构造方法
name:小狗
age:null