详解java中的序列化与反序列化
在项目中我们知道model中的DO要实现序列化,并给他一个serialVersionUID,但是可能并不是很清楚其中的原理,本篇文章就详细解读其中的原理,包括序列化是什么,序列化使用的场景,以及实现方式。下面我们一一道来。
1. 序列化是什么
序列化就是把Java 对象转化为二进制类型的字节流的过程,反序列化就是把二进制类型的字节流数据转换成Java对象的过程,序列化和反序列化是一个相反的过程。
2.序列化使用的场景
2.1 网络上进程间的相互通信其实是以二进制序列的方式进行的,那么Java进程之间的通信也是这个原理,都是依赖于序列化与反序列化的技术,发送端先将Java对象通过序列化转换成二进制的序列码,通过网络传输,接收端在接收到二进制序列码的时候再通过反序列,将二进制序列码转换成Java 对象。
2.2 可以实现数据的持久化,可以通过将数据序列化,将数据保存到磁盘上,实现类似缓存的作用。我们知道java中的对象只存在于jvm中,当jvm退出时,这些对象也就不存在了。
3.实现方式
3.1 实现Serializable接口
public class TestDO implements Serializable{ private static final long serialVersionUID = 1846543788987314616L; private Long id; private String name; private String sex; public TestDO() { } public TestDO(Long id, String name, String sex) { this.id = id; this.name = name; this.sex = sex; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } TestDO testDO = (TestDO)o; return id.equals(testDO.id); } @Override public int hashCode() { return id.hashCode(); } @Override public String toString() { return "TestDO{" + "id=" + id + ", name='" + name + '\'' + ", sex='" + sex + '\'' + '}'; }
假如TestDO不实现Serializable接口,结果会怎样?
会报TestDO没有实现序列化的错误信息
3.2 自定义序列化方法
自定义序列化以后,可以只序列化我们需要序列化部分的字段
3.3 实现Externalizable接口
Externalizable接口继承自Serializable接口,两者之间的序列化机制是存在不同的,Externalizable序列化时会调用序列化对象的一个无参的构造函数,而Serializable在序列化时是不需要调用无参的构造函数,原因是Externalizable在反序列化时会重新实例化一个新的对象,然后将反序列化的对象状态的信息复制到这个新的对象中去。
public class Test2DO implements Externalizable { private static final long serialVersionUID = 1846543788987387216L; private Long id; private String name; private String sex; public Test2DO() { } public Test2DO(Long id, String name, String sex) { this.id = id; this.name = name; this.sex = sex; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Test2DO test2DO = (Test2DO)o; return id != null ? id.equals(test2DO.id) : test2DO.id == null; } @Override public int hashCode() { return id != null ? id.hashCode() : 0; } @Override public void writeExternal(ObjectOutput out) throws IOException { //首先writeObject()调用了默认的序列化方法defaultWriteObject()来处理非transient域 //id name sex //objectOutputStream.defaultWriteObject(); out.writeObject(name); out.writeObject(sex); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { //调用defaultReadObject()方法,将非transient域id、name和sex从流中读出; //objectInputStream.defaultReadObject(); name = (String)in.readObject(); sex = (String)in.readObject(); } @Override public String toString() { return "Test2DO{" + "id=" + id + ", name='" + name + '\'' + ", sex='" + sex + '\'' + '}'; }
4.transient的作用
有时候我们并不希望对象中的某个字段被序列化,就可以在这个字段前面加上这个修饰,用来表示在这个对象被序列化的时候,忽略该字段
5.serialVersionUID的作用
我们知道一个对象能够被序列化和反序列化的唯一依据就是两者具有相同的标识,而这个标识就是serialVersionUID,他决定着该对象能不能被反序列化。如果我们没有显示的定义一个serialVersionUID,那么jvm在编译class的时候会默认的给该对象添加一个serialVersionUID,但是只有在同一次编译的时候,才会生成相同的serialVersionUID,如果以后这个类的信息发生变更,增加或者删除一个字段,或者名称修改了,这时候,在反序列化的时候,就会出现版本号不一致的错误了,所以,为了避免这种错误,我们应该显示的给他加上一个serialVersionUID,我们可以利用编辑工具,自动生成一个serialVersionUID。
6.静态变量的序列化
序列化保存的是对象的信息,而静态变量是类层级的信息,因此静态变量是不会被序列化的。