java对象序列化
java对象序列化概述
序列化的过程就是对象写入字节流和从字节流中读取对象。
将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存在文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。
对象序列化功能可应用在RMI/Socket/JMS/EJB
简化了对象持久化(Persistence)的实现
XML序列化指的是把对象转换成XML数据,传输到目的地后再把XML数据反序列化为对象。
对象序列化实用意义
对象序列化可以实现分布式对象。RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。
java序列化的实现
java序列化比较简单,实现Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。
Serializable是标志性接口,不具有方法。注意:不是每个类都可以序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。
java库中的类,如String,都实现了Serializable接口,所以他们都可进行serialization操作。
对一个对象进行Serialize操作时,不仅会把对象在内存中的数据保存下来,还会把对象中所包含的可serialize成员对象也保存下来。如果对象中包含了没有实现Serializable接口的成员对象,那将在尝试对对象进行Serialize操作时,将发生错误。
对一个Serializable对象进行次第读取时,并不会调用任何构造函数。这是因为对象中的所有数据都是通过InputStream读取的数据来恢复的,所有不用通过构造函数来进行初始化。
进行序列读取时,在执行读取操作的class 中一定要能找到相应的class文件。
处理对象流
java.io包有两个序列化对象的类。
A. ObjectOutputStream
将对象写入字节流
存储对象writeObject()
B. ObjectInputStream
从字节流重构对象
恢复对象readObject()
ObjectOutputStream类
ObjectOutputStream扩展DataOutput接口writeObject()方法用语对象序列化。如果对象包含其它对象的引用,则writeObject()方法递归序列化这些对象,每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
注:经证明,如果一个对象包含两个引用,但引用的是同一个对象,那么确实不会发送两份。如果没有显式指定serialVersionUID,就算添加一个静态变量,也会造成对象在序列化时生成不同的serialVersionUID值,这个值是会被序列化的哦!为了更好地控制序列化,我们最好还是显式指定serialVersionUID。
序列化today's date到一个文件中。
FileOutputStream f = new FileOutputStream("tmp");
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject("Today");
s.writeObject(new Date());
s.flush(); //flush()在方法返回前显示清空输出缓冲区
ObjectInputStream类
现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
例子如下:
//从文件中反序列化 string 对象和 date 对象
FileInputStream in = new FileInputStream("tmp");
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject();
Date date = (Date)s.readObject();
定制序列化过程:
序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为Serializable,但仍可手工控制声明为static或transient的数据成员。
例子:一个非常简单的序列化类。
public class simpleSerializableClass implements Serializable{
String sToday="Today:";
transient Date dtToday=new Date();
}
序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。
关于如何使用定制序列化的部分代码如下:
//重写writeObject()方法以便处理transient的成员。
private void writeObject(ObjectOutputStream outputStream) throws IOException{
outputStream.defaultWriteObject(); //使定制的writeObject()方法可以利用自动序列化中内置的逻辑。
outputStream.writeObject(oSocket.getInetAddress());
outputStream.writeInt(oSocket.getPort());
}
//重写readObject()方法以便接收transient的成员。
private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{
inputStream.defaultReadObject(); //defaultReadObject()补充自动序列化
InetAddress oAddress=(InetAddress)inputStream.readObject();
int iPort =inputStream.readInt();
oSocket = new Socket(oAddress,iPort);
}
完全定制序列化过程:
如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流。类实现Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。关于序列化的高级教程,以后再述。
http://blog.csdn.net/passren/archive/2004/06/30/30421.aspx
http://blog.csdn.net/luofengjava/archive/2007/01/05/1475282.aspx
例
import java.io.*;
class Person implements Serializable {
private static final long serialVersionUID = 0L;
String name;
String age;
public Person() {
}
public Person(String name, String age) {
this.name = name;
this.age = age;
}
public void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(name);
out.writeObject(age);
}
public void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
name = (String) in.readObject();
age = (String) in.readObject();
}
public String toString() {
return "name: " + this.name + "age: " + this.age;
}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
Person p1 = new Person("java01", "15");
Person p2 = new Person("java02", "30");
Person p = new Person();
public void write() {
try {
File f = new File("a.txt");
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream os = new ObjectOutputStream(fos);
p1.writeObject(os);
p2.writeObject(os);
os.close();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void read() {
try {
File f = new File("a.txt");
FileInputStream fis = new FileInputStream(f);
ObjectInputStream os = new ObjectInputStream(fis);
while (fis.available() > 0) {
p.readObject(os);
System.out.println(p);
}
os.close();
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Test t = new Test();
t.write();
t.read();
}
}
这是一个从网上抄的代码,实际上,它不是正规的用法,如果这样完全就和MFC中的方式一样了,此时,如果你想保存Person类本身的信息,你就没有方便的办法了。实际上Person不从Serializable继承都没有任何问题,呵呵。还有就是调用defaultWriteObject时会抛NotActiveException,虽然看起来是在writeObject中调用的defaultWriteObject,但实际上是假的,因为writeObject可以取任意名字。
正规的做法writeObject和readObject是私有的,调用时仍然是oos.writeObject(myclass);而不是myclass.writeObject(oos);
具体参考下面这篇文章:
在Java中使用Serialization相当简单。如果你有一些对象想要进行序列化,你只需实现Serializable接口。然后,你可以使用 ObjectOutputStream将该对象保存至文件或发送到其他主机。所有的non-transient和non-static字段都将被序列化,并且由反序列化重构造出一模一样的对象联系图(譬如许多引用都指向该对象)。但有时你可能想实现你自己的对象序列化和反序列化。那么你可以在某些特定情形下得到更多的控制。来看下面的简单例子。
Java代码
1. class SessionDTO implements Serializable {
2. private static final long serialVersionUID = 1L;
3. private int data; // Stores session data
4.
5. // Session activation time (creation, deserialization)
6. private long activationTime;
7.
8. public SessionDTO(int data) {
9. this.data = data;
10. this.activationTime = System.currentTimeMillis();
11. }
12.
13. public int getData() {
14. return data;
15. }
16.
17. public long getActivationTime() {
18. return activationTime;
19. }
20. }
class SessionDTO implements Serializable {
private static final long serialVersionUID = 1L;
private int data; // Stores session data
// Session activation time (creation, deserialization)
private long activationTime;
public SessionDTO(int data) {
this.data = data;
this.activationTime = System.currentTimeMillis();
}
public int getData() {
return data;
}
public long getActivationTime() {
return activationTime;
}
}
以下是序列化上述class到文件和其反序列化的主函数。
Java代码
1. public class SerializeTester implements Serializable {
2. public static void main(String... strings) throws Exception {
3. File file = new File("out.ser");
4.
5. ObjectOutputStream oos = new ObjectOutputStream(
6. new FileOutputStream(file));
7. SessionDTO dto = new SessionDTO(1);
8. oos.writeObject(dto);
9. oos.close();
10.
11. ObjectInputStream ois = new ObjectInputStream(
12. new FileInputStream(file));
13. SessionDTO dto = (SessionDTO) ois.readObject();
14.
15. System.out.println("data : " + dto.getData()
16. + " activation time : " + dto.getActivationTime());
17. ois.close();
18. }
19. }
public class SerializeTester implements Serializable {
public static void main(String... strings) throws Exception {
File file = new File("out.ser");
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(file));
SessionDTO dto = new SessionDTO(1);
oos.writeObject(dto);
oos.close();
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(file));
SessionDTO dto = (SessionDTO) ois.readObject();
System.out.println("data : " + dto.getData()
+ " activation time : " + dto.getActivationTime());
ois.close();
}
}
类SessionDTO展现的是要在两个服务器之间传输的session。它包含了一些信息在字段data上,该字段需要被序列化。但是它还有另外一个字段activationTime,该字段应该是session对象第一次出现在任意服务器上的时间。它不在我们想要传输的信息之列。这个字段应该在反序列化之后在赋值。进一步来说,没必要把它放在stream中在服务器中传递,因为它占据了不必要的空间。
解决这种情况可以使用writeObject和readObject。有可能你们有一些人没有听说过它们,那是因为它们在许多Java书籍中给忽略了,而且它们们也不是众多流行Java考试的一部分。让我们用这些方法来重写SessionDTO:
Java代码
1. class SessionDTO implements Serializable {
2. private static final long serialVersionUID = 1L;
3. private transient int data; // Stores session data
4.
5. //Session activation time (creation, deserialization)
6. private transient long activationTime;
7.
8. public SessionDTO(int data) {
9. this.data = data;
10. this.activationTime = System.currentTimeMillis();
11. }
12.
13. private void writeObject(ObjectOutputStream oos) throws IOException {
14. oos.defaultWriteObject();
15. oos.writeInt(data);
16. System.out.println("session serialized");
17. }
18.
19. private void readObject(ObjectInputStream ois) throws IOException,
20. ClassNotFoundException {
21. ois.defaultReadObject();
22. data = ois.readInt();
23. activationTime = System.currentTimeMillis();
24. System.out.println("session deserialized");
25. }
26.
27. public int getData() {
28. return data;
29. }
30.
31. public long getActivationTime() {
32. return activationTime;
33. }
34. }
class SessionDTO implements Serializable {
private static final long serialVersionUID = 1L;
private transient int data; // Stores session data
//Session activation time (creation, deserialization)
private transient long activationTime;
public SessionDTO(int data) {
this.data = data;
this.activationTime = System.currentTimeMillis();
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeInt(data);
System.out.println("session serialized");
}
private void readObject(ObjectInputStream ois) throws IOException,
ClassNotFoundException {
ois.defaultReadObject();
data = ois.readInt();
activationTime = System.currentTimeMillis();
System.out.println("session deserialized");
}
public int getData() {
return data;
}
public long getActivationTime() {
return activationTime;
}
}
方法writeObject处理对象的序列化。如果声明该方法,它将会被ObjectOutputStream调用而不是默认的序列化进程。如果你是第一次看见它,你会很惊奇尽管它们被外部类调用但事实上这是两个private的方法。并且它们既不存在于java.lang.Object,也没有在Serializable中声明。那么ObjectOutputStream如何使用它们的呢?这个吗,ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为 priate以至于供ObjectOutputStream来使用。
在两个方法的开始处,你会发现调用了defaultWriteObject()和defaultReadObject()。它们做的是默认的序列化进程,就像写/读所有的non-transient和 non-static字段(但他们不会去做serialVersionUID的检查).通常说来,所有我们想要自己处理的字段都应该声明为 transient。这样的话,defaultWriteObject/defaultReadObject便可以专注于其余字段,而我们则可为这些特定的字段(译者:指transient)定制序列化。使用那两个默认的方法并不是强制的,而是给予了处理复杂应用时更多的灵活性。
你可以从这里或这里读到更多有关于序列化的知识。
自己再补充一些:
1.Write的顺序和read的顺序需要对应,譬如有多个字段都用wirteInt一一写入流中,那么readInt需要按照顺序将其赋值;
2.Externalizable,该接口是继承于Serializable ,所以实现序列化有两种方式。区别在于Externalizable多声明了两个方法readExternal和writeExternal,子类必须实现二者。Serializable是内建支持的也就是直接implement即可,但Externalizable的实现类必须提供 readExternal和writeExternal实现。对于Serializable来说,Java自己建立对象图和字段进行对象序列化,可能会占用更多空间。而Externalizable则完全需要程序员自己控制如何写/读,麻烦但可以有效控制序列化的存储的内容。
注: 经验证,如果继承Externalizable后,虽然相当于也继承了Serializable,但是就算类实现writeObject和readObject,oos.writeObject(myclass);时也不会调用了,仅仅是把类的基本信息序列化,而所有的成员都不会被序列化,需要我们自己动手了。
3.正如Effectvie Java中提到的,序列化就如同另外一个构造函数,只不过是有由stream进行创建的。如果字段有一些条件限制的,特别是非可变的类定义了可变的字段会反序列化可能会有问题。可以在readObject方法中添加条件限制,也可以在readResolve中做。参考56条“保护性的编写 readObject”和“提供一个readResolve方法”。
4.当有非常复杂的对象需要提供deep clone时,可以考虑将其声明为可序列化,不过缺点也显而易见,性能开销。
综上,虽然Java中的序列化道理和MFC中差不多,但是它封装得更多一些,至少,我们任何时候都不用取调用对象的writeObject和readObject函数。
java对象序列化概述
序列化的过程就是对象写入字节流和从字节流中读取对象。
将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存在文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机。
对象序列化功能可应用在RMI/Socket/JMS/EJB
简化了对象持久化(Persistence)的实现
XML序列化指的是把对象转换成XML数据,传输到目的地后再把XML数据反序列化为对象。
对象序列化实用意义
对象序列化可以实现分布式对象。RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的“深复制”,即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。
java序列化的实现
java序列化比较简单,实现Serializable接口的类对象可以转换成字节流或从字节流恢复,不需要在类中增加任何代码。只有极少数情况下才需要定制代码保存或恢复对象状态。
Serializable是标志性接口,不具有方法。注意:不是每个类都可以序列化,有些类是不能序列化的,例如涉及线程的类与特定JVM有非常复杂的关系。
java库中的类,如String,都实现了Serializable接口,所以他们都可进行serialization操作。
对一个对象进行Serialize操作时,不仅会把对象在内存中的数据保存下来,还会把对象中所包含的可serialize成员对象也保存下来。如果对象中包含了没有实现Serializable接口的成员对象,那将在尝试对对象进行Serialize操作时,将发生错误。
对一个Serializable对象进行次第读取时,并不会调用任何构造函数。这是因为对象中的所有数据都是通过InputStream读取的数据来恢复的,所有不用通过构造函数来进行初始化。
进行序列读取时,在执行读取操作的class 中一定要能找到相应的class文件。
处理对象流
java.io包有两个序列化对象的类。
A. ObjectOutputStream
将对象写入字节流
存储对象writeObject()
B. ObjectInputStream
从字节流重构对象
恢复对象readObject()
ObjectOutputStream类
ObjectOutputStream扩展DataOutput接口writeObject()方法用语对象序列化。如果对象包含其它对象的引用,则writeObject()方法递归序列化这些对象,每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
注:经证明,如果一个对象包含两个引用,但引用的是同一个对象,那么确实不会发送两份。如果没有显式指定serialVersionUID,就算添加一个静态变量,也会造成对象在序列化时生成不同的serialVersionUID值,这个值是会被序列化的哦!为了更好地控制序列化,我们最好还是显式指定serialVersionUID。
序列化today's date到一个文件中。
FileOutputStream f = new FileOutputStream("tmp");
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject("Today");
s.writeObject(new Date());
s.flush(); //flush()在方法返回前显示清空输出缓冲区
ObjectInputStream类
现在,让我们来了解ObjectInputStream这个类。它与ObjectOutputStream相似。它扩展DataInput接口。ObjectInputStream中的方法镜像DataInputStream中读取Java基本数据类型的公开方法。readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
例子如下:
//从文件中反序列化 string 对象和 date 对象
FileInputStream in = new FileInputStream("tmp");
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject();
Date date = (Date)s.readObject();
定制序列化过程:
序列化通常可以自动完成,但有时可能要对这个过程进行控制。java可以将类声明为Serializable,但仍可手工控制声明为static或transient的数据成员。
例子:一个非常简单的序列化类。
public class simpleSerializableClass implements Serializable{
String sToday="Today:";
transient Date dtToday=new Date();
}
序列化时,类的所有数据成员应可序列化除了声明为transient或static的成员。将变量声明为transient告诉JVM我们会负责将变元序列化。将数据成员声明为transient后,序列化过程就无法将其加进对象字节流中,没有从transient数据成员发送的数据。后面数据反序列化时,要重建数据成员(因为它是类定义的一部分),但不包含任何数据,因为这个数据成员不向流中写入任何数据。记住,对象流不序列化static或transient。我们的类要用writeObject()与readObject()方法以处理这些数据成员。使用writeObject()与readObject()方法时,还要注意按写入的顺序读取这些数据成员。
关于如何使用定制序列化的部分代码如下:
//重写writeObject()方法以便处理transient的成员。
private void writeObject(ObjectOutputStream outputStream) throws IOException{
outputStream.defaultWriteObject(); //使定制的writeObject()方法可以利用自动序列化中内置的逻辑。
outputStream.writeObject(oSocket.getInetAddress());
outputStream.writeInt(oSocket.getPort());
}
//重写readObject()方法以便接收transient的成员。
private void readObject(ObjectInputStream inputStream) throws IOException,ClassNotFoundException{
inputStream.defaultReadObject(); //defaultReadObject()补充自动序列化
InetAddress oAddress=(InetAddress)inputStream.readObject();
int iPort =inputStream.readInt();
oSocket = new Socket(oAddress,iPort);
}
完全定制序列化过程:
如果一个类要完全负责自己的序列化,则实现Externalizable接口而不是Serializable接口。Externalizable接口定义包括两个方法writeExternal()与readExternal()。利用这些方法可以控制对象数据成员如何写入字节流。类实现Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。这包括使用安全套接或加密整个字节流。到此为至,我们学习了序列化的基础部分知识。关于序列化的高级教程,以后再述。
http://blog.csdn.net/passren/archive/2004/06/30/30421.aspx
http://blog.csdn.net/luofengjava/archive/2007/01/05/1475282.aspx
例
import java.io.*;
class Person implements Serializable {
private static final long serialVersionUID = 0L;
String name;
String age;
public Person() {
}
public Person(String name, String age) {
this.name = name;
this.age = age;
}
public void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(name);
out.writeObject(age);
}
public void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
name = (String) in.readObject();
age = (String) in.readObject();
}
public String toString() {
return "name: " + this.name + "age: " + this.age;
}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class Test {
Person p1 = new Person("java01", "15");
Person p2 = new Person("java02", "30");
Person p = new Person();
public void write() {
try {
File f = new File("a.txt");
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream os = new ObjectOutputStream(fos);
p1.writeObject(os);
p2.writeObject(os);
os.close();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void read() {
try {
File f = new File("a.txt");
FileInputStream fis = new FileInputStream(f);
ObjectInputStream os = new ObjectInputStream(fis);
while (fis.available() > 0) {
p.readObject(os);
System.out.println(p);
}
os.close();
fis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Test t = new Test();
t.write();
t.read();
}
}
这是一个从网上抄的代码,实际上,它不是正规的用法,如果这样完全就和MFC中的方式一样了,此时,如果你想保存Person类本身的信息,你就没有方便的办法了。实际上Person不从Serializable继承都没有任何问题,呵呵。还有就是调用defaultWriteObject时会抛NotActiveException,虽然看起来是在writeObject中调用的defaultWriteObject,但实际上是假的,因为writeObject可以取任意名字。
正规的做法writeObject和readObject是私有的,调用时仍然是oos.writeObject(myclass);而不是myclass.writeObject(oos);
具体参考下面这篇文章:
在Java中使用Serialization相当简单。如果你有一些对象想要进行序列化,你只需实现Serializable接口。然后,你可以使用 ObjectOutputStream将该对象保存至文件或发送到其他主机。所有的non-transient和non-static字段都将被序列化,并且由反序列化重构造出一模一样的对象联系图(譬如许多引用都指向该对象)。但有时你可能想实现你自己的对象序列化和反序列化。那么你可以在某些特定情形下得到更多的控制。来看下面的简单例子。
Java代码
1. class SessionDTO implements Serializable {
2. private static final long serialVersionUID = 1L;
3. private int data; // Stores session data
4.
5. // Session activation time (creation, deserialization)
6. private long activationTime;
7.
8. public SessionDTO(int data) {
9. this.data = data;
10. this.activationTime = System.currentTimeMillis();
11. }
12.
13. public int getData() {
14. return data;
15. }
16.
17. public long getActivationTime() {
18. return activationTime;
19. }
20. }
class SessionDTO implements Serializable {
private static final long serialVersionUID = 1L;
private int data; // Stores session data
// Session activation time (creation, deserialization)
private long activationTime;
public SessionDTO(int data) {
this.data = data;
this.activationTime = System.currentTimeMillis();
}
public int getData() {
return data;
}
public long getActivationTime() {
return activationTime;
}
}
以下是序列化上述class到文件和其反序列化的主函数。
Java代码
1. public class SerializeTester implements Serializable {
2. public static void main(String... strings) throws Exception {
3. File file = new File("out.ser");
4.
5. ObjectOutputStream oos = new ObjectOutputStream(
6. new FileOutputStream(file));
7. SessionDTO dto = new SessionDTO(1);
8. oos.writeObject(dto);
9. oos.close();
10.
11. ObjectInputStream ois = new ObjectInputStream(
12. new FileInputStream(file));
13. SessionDTO dto = (SessionDTO) ois.readObject();
14.
15. System.out.println("data : " + dto.getData()
16. + " activation time : " + dto.getActivationTime());
17. ois.close();
18. }
19. }
public class SerializeTester implements Serializable {
public static void main(String... strings) throws Exception {
File file = new File("out.ser");
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(file));
SessionDTO dto = new SessionDTO(1);
oos.writeObject(dto);
oos.close();
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(file));
SessionDTO dto = (SessionDTO) ois.readObject();
System.out.println("data : " + dto.getData()
+ " activation time : " + dto.getActivationTime());
ois.close();
}
}
类SessionDTO展现的是要在两个服务器之间传输的session。它包含了一些信息在字段data上,该字段需要被序列化。但是它还有另外一个字段activationTime,该字段应该是session对象第一次出现在任意服务器上的时间。它不在我们想要传输的信息之列。这个字段应该在反序列化之后在赋值。进一步来说,没必要把它放在stream中在服务器中传递,因为它占据了不必要的空间。
解决这种情况可以使用writeObject和readObject。有可能你们有一些人没有听说过它们,那是因为它们在许多Java书籍中给忽略了,而且它们们也不是众多流行Java考试的一部分。让我们用这些方法来重写SessionDTO:
Java代码
1. class SessionDTO implements Serializable {
2. private static final long serialVersionUID = 1L;
3. private transient int data; // Stores session data
4.
5. //Session activation time (creation, deserialization)
6. private transient long activationTime;
7.
8. public SessionDTO(int data) {
9. this.data = data;
10. this.activationTime = System.currentTimeMillis();
11. }
12.
13. private void writeObject(ObjectOutputStream oos) throws IOException {
14. oos.defaultWriteObject();
15. oos.writeInt(data);
16. System.out.println("session serialized");
17. }
18.
19. private void readObject(ObjectInputStream ois) throws IOException,
20. ClassNotFoundException {
21. ois.defaultReadObject();
22. data = ois.readInt();
23. activationTime = System.currentTimeMillis();
24. System.out.println("session deserialized");
25. }
26.
27. public int getData() {
28. return data;
29. }
30.
31. public long getActivationTime() {
32. return activationTime;
33. }
34. }
class SessionDTO implements Serializable {
private static final long serialVersionUID = 1L;
private transient int data; // Stores session data
//Session activation time (creation, deserialization)
private transient long activationTime;
public SessionDTO(int data) {
this.data = data;
this.activationTime = System.currentTimeMillis();
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeInt(data);
System.out.println("session serialized");
}
private void readObject(ObjectInputStream ois) throws IOException,
ClassNotFoundException {
ois.defaultReadObject();
data = ois.readInt();
activationTime = System.currentTimeMillis();
System.out.println("session deserialized");
}
public int getData() {
return data;
}
public long getActivationTime() {
return activationTime;
}
}
方法writeObject处理对象的序列化。如果声明该方法,它将会被ObjectOutputStream调用而不是默认的序列化进程。如果你是第一次看见它,你会很惊奇尽管它们被外部类调用但事实上这是两个private的方法。并且它们既不存在于java.lang.Object,也没有在Serializable中声明。那么ObjectOutputStream如何使用它们的呢?这个吗,ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为 priate以至于供ObjectOutputStream来使用。
在两个方法的开始处,你会发现调用了defaultWriteObject()和defaultReadObject()。它们做的是默认的序列化进程,就像写/读所有的non-transient和 non-static字段(但他们不会去做serialVersionUID的检查).通常说来,所有我们想要自己处理的字段都应该声明为 transient。这样的话,defaultWriteObject/defaultReadObject便可以专注于其余字段,而我们则可为这些特定的字段(译者:指transient)定制序列化。使用那两个默认的方法并不是强制的,而是给予了处理复杂应用时更多的灵活性。
你可以从这里或这里读到更多有关于序列化的知识。
自己再补充一些:
1.Write的顺序和read的顺序需要对应,譬如有多个字段都用wirteInt一一写入流中,那么readInt需要按照顺序将其赋值;
2.Externalizable,该接口是继承于Serializable ,所以实现序列化有两种方式。区别在于Externalizable多声明了两个方法readExternal和writeExternal,子类必须实现二者。Serializable是内建支持的也就是直接implement即可,但Externalizable的实现类必须提供 readExternal和writeExternal实现。对于Serializable来说,Java自己建立对象图和字段进行对象序列化,可能会占用更多空间。而Externalizable则完全需要程序员自己控制如何写/读,麻烦但可以有效控制序列化的存储的内容。
注: 经验证,如果继承Externalizable后,虽然相当于也继承了Serializable,但是就算类实现writeObject和readObject,oos.writeObject(myclass);时也不会调用了,仅仅是把类的基本信息序列化,而所有的成员都不会被序列化,需要我们自己动手了。
3.正如Effectvie Java中提到的,序列化就如同另外一个构造函数,只不过是有由stream进行创建的。如果字段有一些条件限制的,特别是非可变的类定义了可变的字段会反序列化可能会有问题。可以在readObject方法中添加条件限制,也可以在readResolve中做。参考56条“保护性的编写 readObject”和“提供一个readResolve方法”。
4.当有非常复杂的对象需要提供deep clone时,可以考虑将其声明为可序列化,不过缺点也显而易见,性能开销。
综上,虽然Java中的序列化道理和MFC中差不多,但是它封装得更多一些,至少,我们任何时候都不用取调用对象的writeObject和readObject函数。