博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入了解java序列化
阅读量:6085 次
发布时间:2019-06-20

本文共 7890 字,大约阅读时间需要 26 分钟。

在日常开发中,前端与后端的交互,系统之间的远程调用都需要使用到序列化技术,在java中使用序列化非常简单,只需要将被序列化的对象的类实现Java.io.Serializable接口即可。

对于实现序列化接口的类,我们需要注意两点:

  1. 类中的静态变量我们是无法序列化的,因为序列化只是针对对象,而静态变量是类级别的。
  2. 当子类实现序列化接口,而父类没有实现序列化接口时,将子类进行序列化,再反序列化回来后,发现父类中的属性会被重新初始化,也就是说会调用父类的无参构造。如果没有无参构造,则会抛出异常。

下面是通过两个小案例来证明:

public class SeriaTest implements Serializable{
private static final long serialVersionUID = 1L; private static Integer count = 10; public static void main(String[] args) { try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj")); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))) { oos.writeObject(new SeriaTest()); count = 20; SeriaTest seriaTest = (SeriaTest) ois.readObject(); System.out.println(seriaTest.count); } catch (Exception e) { System.out.println("序列化反序列化时出现异常:"+e); } }}

这段代码的运行结果为20,说明静态变量不会被序列化。

public class Fu {
public String fu; public String getFu() { return fu; } public void setFu(String fu) { this.fu = fu; }}public class Zi extends Fu implements Serializable{
private static final long serialVersionUID = 1L; public String zi; public Zi(){ fu = "fu"; zi = "zi"; } public String getZi() { return zi; } public void setZi(String zi) { this.zi = zi; }}public class SeriaTest{
public static void main(String[] args) { public static void main(String[] args) { try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj")); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))){ oos.writeObject(new Zi()); Zi zi = (Zi) ois.readObject(); System.out.println(zi.fu); System.out.println(zi.zi); } catch (Exception e) { } }}

这段代码的运行结果为null,zi。验证了上面的第二点。

将对象序列化实际上调用的就是ObjectOutputStream的wirteObject方法,跟踪源代码可知能被序列化的对象除了是实现Serializable接口的对象,还可以是String,数组或者枚举对象:

if (paramObject instanceof String) {        writeString((String) paramObject, paramBoolean);} else if (((Class) localObject2).isArray()) {        writeArray(paramObject, localObjectStreamClass, paramBoolean);} else if (paramObject instanceof Enum) {        writeEnum((Enum) paramObject, localObjectStreamClass,paramBoolean);} else if (paramObject instanceof Serializable) {        writeOrdinaryObject(paramObject, localObjectStreamClass,paramBoolean);}

再继续跟进writeOrdinaryObject方法,我们会发现会有这么一段逻辑:

if ((paramObjectStreamClass.isExternalizable()) && (!(paramObjectStreamClass.isProxy())))    writeExternalData((Externalizable) paramObject);else    writeSerialData(paramObject, paramObjectStreamClass);

先判断是不是实现了Externalizable接口(这个是Serializable的子接口,这里先不管,后面会解释),如果不是,则执行writeSerialData方法。跟进去:

if (localObjectStreamClass.hasWriteObjectMethod()) {    ********省略*******           localObjectStreamClass.invokeWriteObject(paramObject, this);    ********省略*******   } else {    defaultWriteFields(paramObject, localObjectStreamClass);}

我们发现源代码先判断了一下这个需要被序列化的类中有没有writeObject这个方法,如果有,那么就执行,如果没有,那么就执行默认的序列化方法。对于这个writeObject方法,有以下几个要求:

  • 方法名必须叫writeObject
  • 必须是私有的方法
  • 返回值类型必须为void

    只有满足了以上三点,在序列化对象的时候,才会执行我们自定义的序列化方法。当然,我们除了可以重写writeObject方法,我们还可以重写readObject,readObjectNoData,writeReplace,readResolve等方法,这些方法之间有什么联系,有什么作用呢?我们通过一段代码来探索一下:

public class SeriaTest implements Serializable{
private static final long serialVersionUID = 1L; public Integer count = 10; public SeriaTest(Integer count){ this.count = count; } public static void main(String[] args) { try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj")); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))) { oos.writeObject(new SeriaTest(20)); SeriaTest seriaTest = (SeriaTest) ois.readObject(); System.out.println(seriaTest.count); } catch (Exception e) { System.out.println("序列化反序列化时出现异常:"+e); } } private void writeObject(ObjectOutputStream oos) throws IOException{ System.out.println("序列化前执行了自定义的writeObject方法"); oos.defaultWriteObject(); System.out.println("序列化后执行了自定义的writeObject方法"); } private Object writeReplace() { System.out.println("序列化时执行了自定义的writeReplace方法"); SeriaTest s = new SeriaTest(10); return s; }}

执行结果如下:

序列化时执行了自定义的writeReplace方法序列化前执行了自定义的writeObject方法序列化后执行了自定义的writeObject方法10

根据执行结果我们我们可以知道,在对象序列化的时候,会先去执行被序列化的类中的writeReplace方法,再执行writeObject方法,如果重写了writeReplace方法,那么被序列化的对象就是这个方法的返回值,而writeObject方法主要作用就是在序列化前后可以做处理操作。

对应的读操作的方法分别是readResolve方法和readObject方法,将上面的代码再完善一下:

public class SeriaTest implements Serializable{
private static final long serialVersionUID = 1L; public Integer count = 10; public SeriaTest(Integer count){ this.count = count; } public static void main(String[] args) { try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj")); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))) { oos.writeObject(new SeriaTest(20)); SeriaTest seriaTest = (SeriaTest) ois.readObject(); System.out.println(seriaTest.count); } catch (Exception e) { System.out.println("序列化反序列化时出现异常:"+e); } } private void writeObject(ObjectOutputStream oos) throws IOException{ System.out.println("序列化前执行了自定义的writeObject方法"); oos.defaultWriteObject(); System.out.println("序列化后执行了自定义的writeObject方法"); } private Object writeReplace() { System.out.println("序列化时执行了自定义的writeReplace方法"); SeriaTest s = new SeriaTest(10); return s; } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException{ System.out.println("反序列化前执行了自定义的readObject方法"); ois.defaultReadObject(); System.out.println("反序列化后执行了自定义的readObject方法"); } private Object readResolve(){ System.out.println("反序列化前执行了自定义的readReslove方法"); SeriaTest s = new SeriaTest(30); return s; }}

执行结果符合我们的预期:

序列化时执行了自定义的writeReplace方法序列化前执行了自定义的writeObject方法序列化后执行了自定义的writeObject方法反序列化前执行了自定义的readObject方法反序列化后执行了自定义的readObject方法反序列化前执行了自定义的readReslove方法30

至于readObjectNoData方法,这个比较少见,这个方法的作用就是当一个对象被序列化后,反序列化时可以添加属性。(具体可参考:)

下面我们再来解析一下上面提到的Externalizable接口,这个接口是Serializable接口的子接口,这个接口有两个抽象方法:writeExternal和readExternal,这个两个方法分别对应于writeObject和readObject,不同点在于writeObject和readObject都是私有方法,所以其子类不能复用并且不能复写,而writeExternal和readExternal是共有方法,其子类可以复用并且复写。

public class SeriaTest implements Externalizable{
private String name; private Integer age; public static void main(String[] args) { try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj")); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj")) ) { oos.writeObject(new SeriaTest()); SeriaTest seriaTest = (SeriaTest) ois.readObject(); System.out.println(seriaTest.name); System.out.println(seriaTest.age); } catch (Exception e) { System.out.println("序列化反序列化时出现异常:"+e); } } @Override public void writeExternal(ObjectOutput paramObjectOutput) throws IOException { System.out.println("writeExternal....."); paramObjectOutput.writeInt(18); paramObjectOutput.write("zhangsan".getBytes(Charset.forName("UTF-8"))); } @Override public void readExternal(ObjectInput paramObjectInput) throws IOException, ClassNotFoundException { System.out.println("readExternal....."); age = paramObjectInput.readInt(); name = paramObjectInput.readLine(); }}

执行结果如下:

writeExternal.....readExternal.....zhangsan18

转载地址:http://gfuwa.baihongyu.com/

你可能感兴趣的文章
C#方法拓展
查看>>
MySql.Data.dll的版本
查看>>
Linux系统磁盘管理
查看>>
hdu 2191 (多重背包+二进制优化)
查看>>
home.php
查看>>
neo4j---删除关系和节点
查看>>
redis分布式锁redisson
查看>>
什么样的企业可以称之为初创企业?
查看>>
Python爬虫之BeautifulSoup
查看>>
《HTML 5与CSS 3权威指南(第3版·下册)》——第20章 使用选择器在页面中插入内容...
查看>>
如何判断自己适不适合做程序员?这几个特点了解一下
查看>>
newinstance()和new有什么区别
查看>>
android下载封装类
查看>>
[node] 用 node-webkit 开发桌面应用
查看>>
Nginx访问控制和虚拟主机
查看>>
report widget not working for external users
查看>>
windows phone 摄像头得到图片是旋转90°
查看>>
Linux--sed使用
查看>>
没有显示器的情况下安装和使用树莓派
查看>>
Q85 最大矩形
查看>>