Java序列化(Serialization)
关于Java的序列化的文章在网上已经够多了,在这里写关于Java序列化的文章是对自己关于这方面的的一种总结,结合以前的开发经验与网上的资料,写了这篇文章,对自己是有着巩固记忆的作用,也希望能够对大家有一定帮助。
一、什么是序列化(Serialization)?
序列化是Java提供的一种机制,将对象转化成字节序列,在字节序列中保存了对象的数据、对象的类型的信息与存储在对象中的数据的类型。序列化实际上就是将保存对象的"状态",可以方便以后的程序使用或者通过网络传输到另一台主机使用。一般来说,对象的生成周期取决于程序是否在执行,而序列化能够将对象保存在磁盘或网络中,这样对象就能生存在程序的调用之间。
二、序列化的目的
用序列化来保存对象的状态,主要是为了支持两大特性:
- Java的远程方法调用(RMI),它能够存活于其他机器上的对象使用起来就像存活于本地一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。
- 在Java Beans中,必须使用对象序列化。
三、序列化基本实例
Java中实现序列化最基本的方法是实现Serializable接口,Serializable接口是标记接口,不包含任何方法。要序列化一个对象,必须先创建某些OutoutStream对象,然后封装到ObjectOutputStream对象内,其中ObjectOutputStream提供writeObject()方法,调用该方法即可将对象序列化。
反序列化是指将对象序列化生成的字节序列还原成对象,需要将InputStream对象封装在ObjectInputStream对象中,然后调用readObject即可读出对象。
下面是对象序列化的基本实例:
class Pet implements Serializable {
private String name;
public Pet(String name) {
this.name = name;
}
public String toString() {
return name;
}
}
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private int age;
private String name;
private Pet ownPet;
public Person(String name, int age, Pet ownPet) {
this.name = name;
this.age = age;
this.ownPet = ownPet;
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + ", ownPet=" + ownPet
+ "]";
}
public static void main(String[] args) throws FileNotFoundException,
IOException, ClassNotFoundException {
Person p1 = new Person("person1 ", 47, new Pet("dog"));
Person p2 = new Person("person2 ", 34, new Pet("cat"));
// 对象序列化
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
"person.out"));
System.out.println("Save objects:");
out.writeObject(p1);
out.writeObject(p2);
out.close();
// 对象反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"person.out"));
System.out.println("Recovering objects:");
p1 = (Person) in.readObject();
p2 = (Person) in.readObject();
System.out.println(p1);
System.out.println(p2);
}
}
上述程序结果是:
Save objects:
Recovering objects:
Person [age=47, name=person1 , ownPet=dog]
Person [age=34, name=person2 , ownPet=cat]
从上面的结果看出,对象序列化不仅将调用writeObject()的对象序列化,而且通过追踪调用writeObject()的对象的引用,并保存那些引用的对象,所以对象序列化能够自动保存对象的相关引用对象,能够保证对象信息的完整性,因此能够使用对象序列化进行对象的深度复制。
注意事项:实现Serializable接口对象反序列化并没有调用构造器,是直接将字节序列还原成对象。在序列化过程中必须保证classpath中必须有该类,在本实例即Person.class,否则抛出ClassNotFoundException。
三、实现Externalizable接口
序列化的另一种方法是实现Externalizable接口,Externalizable接口提供以下默认方法:
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
用户必须使用自己实现这两个方法,在方法中可以控制序列化,如可以让一些成员属性不进行序列化,实例如下:
public class Test implements Externalizable {
private int i;
private String s;
public Test() {
System.out.println("Test default constructor");
}
public Test(String x, int a) {
System.out.println("Test(String x, int a)");
s = x;
i = a;
}
public String toString() {
return s + i;
}
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Blip3.writeExternal");
// You must do this:
out.writeObject(s);
out.writeInt(i);
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
System.out.println("Blip3.readExternal");
// You must do this:
s = (String) in.readObject();
i = in.readInt();
}
public static void main(String[] args) throws FileNotFoundException,
IOException, ClassNotFoundException {
System.out.println("Constructorint objects:");
Test test = new Test("A String ", 47);
System.out.println(b3);
// 对象序列化
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
"test.out"));
System.out.println("Save objects:");
out.writeObject(b3);
out.close();
// 对象反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"test.out"));
System.out.println("Recovering test:");
b3 = (Test) in.readObject();
}
}
上述程序运行结果为:
Constructorint objects:
Test(String x, int a)
A String 47
Save objects:
Test.writeExternal
Recovering test:
Test default constructor
Test.readExternal
从结果中能够看出为了能实现对象的序列化,不仅必须在writeExternal()方法中写入对象成员变量,而且要在readExternal()方法中读取成员变量。在对象的反序列化过程中调用了默认的构造方法。
四、transient关键词
transient能够对成员变量进行控制,防止敏感信息泄漏,当然上面的实现Externalizable的方法也可以完成该功能,但是必须自己控制所有的属性进行序列化,比较复杂。而采用transient(瞬时)关键词就能关闭该字段的序列化,如下实例所示:
public class Logon implements Serializable {
private Date date = new Date();
private String username;
private transient String password;
public Logon(String name, String password) {
this.username = name;
this.password = password;
}
public String toString() {
return "Logon info: \n username: " + username + "\n date: " + date
+ "\n password: " + password;
}
public static void main(String[] args) throws FileNotFoundException,
IOException, ClassNotFoundException {
Logon a = new Logon("Hulk", "myLittlePony");
System.out.println("logon a =" + a);
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
"out/Logon.out"));
o.writeObject(a);
o.close();
// 获取序列化的数据
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"out/Logon.out"));
System.out.println("Recovering object at " + new Date());
a = (Logon) in.readObject();
System.out.println("logon a =" + a);
}
}
上述程序的结果为:
logon a = Logon info:
username: Hulk
date: Wed Jan 20 21:55:16 CST 2016
password: myLittlePony
Recovering object at Wed Jan 20 21:55:16 CST 2016
logon a = Logon info:
username: Hulk
date: Wed Jan 20 21:55:16 CST 2016
password: null
从结果中,可知对password字段进行了隐藏,没有进行序列化。
五、代替实现Externalizable
在对象序列化过程中对序列化控制,如果采用实现Externalizable接口的方式,必须实现每一个属性的序列化控制,太过复杂,那如何替代它呢?通过实现Serializable接口并添加两个方法,只要提供这两个方法,就会使用它们而不是默认的序列化机制。这两个方法签名如下所示:
private void writeObject(ObjectOutputStream stream) throws IOException
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
在上面两个方法中能够调用默认的处理方法,也可以自己控制属性的序列化,实例代码如下:
public class SerialCtl implements Serializable {
private String a;
private transient String b;
public SerialCtl(String aa, String bb) {
this.a = "Not trnsient: " + aa;
this.b = "Transient: " + bb;
}
public String toString() {
return a + "\n" + b;
}
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
stream.writeObject(b);
}
private void readObject(ObjectInputStream stream) throws IOException,
ClassNotFoundException {
stream.defaultReadObject();
b = (String) stream.readObject();
}
public static void main(String[] args) throws FileNotFoundException,
IOException, ClassNotFoundException {
SerialCtl sc = new SerialCtl("Test1", "Test2");
System.out.println("Before:\n " + sc);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream o = new ObjectOutputStream(buf);
o.writeObject(sc);
// 获取序列化的数据
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
sc = (SerialCtl) in.readObject();
System.out.println("After:\n " + sc);
}
}
上述程序的结果:
Before:
Not trnsient: Test1
Transient: Test2
After:
Not trnsient: Test1
Transient: Test2
六、总结
综上所述,最简单的序列化就是实现Serializable接口,如果为了敏感信息的安全,可以使用transient关键字。如果想近一步控制序列化过程,建议实现Serializable接口并添加两个序列化方法,并自定义实现方法。