Java 编程思想中是这么描述序列化的:
Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可通过网络进行,自动弥补不同操作系统之间的差异
对象序列化主要是为了支持如下两种主要特性:
一是Java的远程方法调用RMI,使存活于其他计算机上的对象使用起来就像是存活于本机一样。再者,对Java Beans来说,对象的序列化也是必需的,使用一个Bean时,在设计阶段对它的状态信息进行配置,并保存这些信息,在程序启动时进行后期恢复。
对象序列化为字节的过程:
创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内,调用writeObject()即可将对象序列化,并将其发送给OutputStream
字节序列还原为对象的过程:
创建某些InputStream对象,然后将其封装在一个ObjectInputStream对象内,调用readObject()即可还原为对象,还原回来的对象是Object,需要类型转换
对象序列化时基于字节的,要使用字节输入流/输出流
对象序列化不仅保存了对象的“全景图”,而且能追踪对象内所包含的所有引用
下面通过一些示例演示序列化相关的一些细节问题
反序列化回来的对象和原对象内容一致,但是一个新对象
class SerializableDemo implements Serializable {
/** * */
private static final long serialVersionUID = -707015875588596115L;
private String name;
public SerializableDemo(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SerializableDemo other = (SerializableDemo) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "SerializableDemo [name=" + name + "]";
}
}
public static void seri(SerializableDemo demo) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("my.txt"))) {
oos.writeObject(demo);
oos.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
public static SerializableDemo desri() {
try (ObjectInputStream oin = new ObjectInputStream(new FileInputStream("my.txt"))) {
SerializableDemo demo = (SerializableDemo) oin.readObject();
return demo;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
SerializableDemo demo = new SerializableDemo("Tom");
seri(demo);
SerializableDemo newDemo = desri();
System.out.println(newDemo);
System.out.println(demo == newDemo); // false
System.out.println(demo.equals(newDemo)); // true
serialVersionUID的作用
字面意思是序列化版本号,Eclipse中默认有两种生成方式:default serial version ID 和 generated serial version ID,前者是1L,后者是根据类名,属性等生成的随机Long类型的数
如果修改一下类,重新生成serialVersionUID,从原序列化文件中反序列化会失败
class SerializableDemo implements Serializable {
/** * */
private static final long serialVersionUID = -9071009327174509480L;
/** * */
// private static final long serialVersionUID = -707015875588596115L;
private String name;
private int age;
public SerializableDemo(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SerializableDemo other = (SerializableDemo) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "SerializableDemo [name=" + name + ", age=" + age + "]";
}
}
SerializableDemo newDemo = desri();
System.out.println(newDemo);
但是如果还是使用原来的serialVersionUID则可以反序列化成功,只是age属性的值是0
若希望类的不同版本对序列化兼容,需要确保类的不同版本具有相同的serialVersionUID;
若不希望类的不同版本对序列化兼容,需要确保类的不同版本具有不同的serialVersionUID;
对象中存在引用对象时的序列化,其引用的对象也需要实现Serializable接口
class Address implements Serializable {
/** * */
private static final long serialVersionUID = -5432410643914580007L;
private String city;
public Address(String city) {
super();
this.city = city;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((city == null) ? 0 : city.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Address other = (Address) obj;
if (city == null) {
if (other.city != null)
return false;
} else if (!city.equals(other.city))
return false;
return true;
}
@Override
public String toString() {
return "Address [city=" + city + "]";
}
}
class SerializableDemo implements Serializable {
/** * */
private static final long serialVersionUID = 950815272813597778L;
private String name;
private int age;
private Address address;
public SerializableDemo(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SerializableDemo other = (SerializableDemo) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "SerializableDemo [name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
SerializableDemo demo = new SerializableDemo("Tom", 20);
seri(demo);
若Address没有实现Serializable接口,则SerializableDemo对象序列化会失败
当父类实现了Serializable接口时,其子类不需要显示声明Serializable接口也可以序列化
当父类未实现了Serializable接口,但子类实现了Serizlizable接口,子类序列化时会失败
static 关键字修饰变量,不受序列化反序列化影响,序列化和反序列化都是基于对象,但是static是类的任何实例对象共享的
transient 关键字 修饰的变量,也不受序列化反序列化影响,该变量的值反序列化时是该变量数据类型的默认值,相当于隐藏了原来的值
class SerializableDemo implements Serializable {
/** * */
private static final long serialVersionUID = 164659918544315637L;
private String name;
private transient int age;
public SerializableDemo(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "SerializableDemo [name=" + name + ", age=" + age + "]";
}
}
SerializableDemo demo = new SerializableDemo("Tom", 30);
seri(demo);
SerializableDemo newDemo = desri();
System.out.println(newDemo);//SerializableDemo [name=Tom, age=0]
通过writeObject()方法和readObject()方法自定义序列化策略
class SerializableDemo implements Serializable {
/** * */
private static final long serialVersionUID = 164659918544315637L;
private String name;
private transient int age;
public SerializableDemo(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "SerializableDemo [name=" + name + ", age=" + age + "]";
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
age += 5;
oos.writeInt(age);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
age = ois.readInt();
age -= 5;
}
}
验证序列化和反序列化,age的值会被正常还原,但是实际是在writeObject和readObject方法中做过处理的,如果别人拿到了字节序列,但是不知道还原逻辑,也是没有意义的
SerializableDemo demo = new SerializableDemo("Tom", 30);
seri(demo);
SerializableDemo newDemo = desri();
System.out.println(newDemo);
通过writeObject()方法和readObject()方法自定义序列化策略是基于ObjectInputStream和ObjectOutputStream里的readObject和writeObject方法实现的,其内部有一个方法调用栈:
writeObject –> writeObject0 –> writeOrdinaryObject –> writeSerialData –> invokeWriteObject
readObject –> readObject0 –> readOrdinaryObject – readSerialData –>invokeReadObject
readObject和writeObject中属性的顺序必须一致的,反之会反序列化失败或错误
自定义序列化策略不受transient关键字影响
具体详情留待ObjectInputStream和ObjectOutputStream源码学习
还有一种实现自定义序列化策略的即实现Externalizable接口,该接口继承了Serializable接口,扩展定义了两个方法:
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
class SerializableDemo implements Externalizable {
/** * */
private static final long serialVersionUID = 164659918544315637L;
private String name;
private transient int age;
public SerializableDemo() {
super();
}
public SerializableDemo(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "SerializableDemo [name=" + name + ", age=" + age + "]";
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
}
SerializableDemo demo = new SerializableDemo("Tom", 30);
seri(demo);
SerializableDemo newDemo = desri();
System.out.println(newDemo);
类似于readObject和writeObject的模式,实现readExternal和writeExternal即可
这里有两个需要注意:
1. 必须有无参构造器,不然会反序列化失败
2. writeExternal和readExternal中属性的顺序必须一致,不然也会反序列化失败
同样的也不受transient关键字影响
暂时就这么多,小结一下:
1. 序列化即将对象转化为字节序列,反序列化即将字节序列还原为对象
2. 默认的序列化机制实现Serializable接口即可
3. 序列化和反序列化基于字节流ObjectInputStream和ObjectOutputStream操作
4. 序列化机制序列化的是对象的“全景图”,包含引用对象,继承等关系
5. 序列化对象的引用对象也必须实现序列化接口
6. 子类实现了序列化接口,父类也必须实现序列化接口
7. 父类实现了序列化接口,子类默认实现序列化接口
8. transient 和 static 关键字修饰的变量不受默认序列化机制的影响
9. 可通过readObject和writeObject对象实现自定义序列化策略,基于ObjectInputStream和ObjectOutputStrema的readObject和writeObject原理,且不受transient关键字影响,但方法内属性顺序必须一致
10. 可通过实现Externalizable接口实现自定义序列化策略,该方式必须有无参构造器且,方法内属性顺序必须一致,也不受transient关键字影响