为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java为我们提供一种很好保存对象状态的机制,那就是序列化。
简单来说序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,流的概念这里不用多说(就是I/O),我们可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间!在对对象流进行读写操作时会引发一些问题,而序列化机制正是用来解决这些问题的!
它面向那些实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回原来的样子。这一过程亦可通过网络进行。这意味着序列化机制能自动补偿操作系统间的差异。换句话说,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。不必关心数据在不同机器上如何表示,也不必关心字节的顺序或者其他任何细节。
那么我们在什么情况下需要使用到Serializable接口呢??
1、当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
2、当你想用套接字在网络上传送对象的时候;
3、当你想通过RMI传输对象的时候;
下面我们由一个问题的引出当我们想把的内存中的对象状态保存到一个文件中或者数据库中时候,Serializable接口能帮我们做些什么?
如上所述,读写对象会有什么问题呢?比如:我要将对象写入一个磁盘文件而后再将其读出来会有什么问题吗?别急,其中一个最大的问题就是对象引用!举个例子来说:假如我有两个类,分别是A和B,B类中含有一个指向A类对象的引用,现在我们对两个类进行实例化{ A a = new A(); B b = new B(); },这时在内存中实际上分配了两个空间,一个存储对象a,一个存储对象b,接下来我们想将它们写入到磁盘的一个文件中去,就在写入文件时出现了问题!因为对象b包含对对象a的引用,所以系统会自动的将a的数据复制一份到b中,这样的话当我们从文件中恢复对象时(也就是重新加载到内存中)时,内存分配了三个空间,而对象a同时在内存中存在两份,想一想后果吧,如果我想修改对象a的数据的话,那不是还要搜索它的每一份拷贝来达到对象数据的一致性,这不是我们所希望的!
以下序列化机制的解决方案:
1.保存到磁盘的所有对象都获得一个序列号(1, 2, 3等等)
2.当要保存一个对象时,先检查该对象是否被保存了。
3.如果以前保存过,只需写入"与已经保存的具有序列号x的对象相同"的标记,否则,保存该对象通过以上的步骤序列化机制解决了对象引用的问题!
在对对象进行实例化的过程中相关注意事项
a)序列化时,只对对象的状态进行保存,而不管对象的方法;
b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
d)并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:
1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输 等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
2. 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。
序列化的实现
将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流调用ObjectInputStream 的 readObject() 方法 。
例子:
Employee类:
public class Employee implements Serializable{ private String name; private double salary; public Employee(String name, double salary) { super (); this .name = name; this .salary = salary; } public void raiseSalary( double byPercent){ double temp = salary * byPercent / 100 ; salary += temp; } public String toString() { return getClass().getName() + "[ Name = " + name + ", salary = " + salary + "]" ; } }
Manager类:
public class Manager extends Employee { private Employee secretary; public Manager(String name, double salary) { super (name, salary); secretary = null ; } public Employee getSecretary() { return secretary; } public void setSecretary(Employee secretary) { this .secretary = secretary; } public String toString() { return super .toString() + "[ secretary = " + secretary + "]" ; } }
MainTest类:
public class MainTest { public static void main(String[] args){ Employee employee = new Employee( "LiLei" , 1000 ); Manager manager1 = new Manager( "Jim" , 20000 ); manager1.setSecretary(employee); Employee[] staff = new Employee[ 2 ]; staff[0 ] = employee; staff[1 ] = manager1; try { ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream( "employee.dat" )); oos.writeObject(staff); oos.close(); ObjectInputStream ois = new ObjectInputStream( new FileInputStream( "employee.dat" )); Employee[] newStaff = (Employee[])ois.readObject(); ois.close(); newStaff[0 ].raiseSalary( 1000 ); for ( int i= 0 ; i<newStaff.length; i++) System.out.println(newStaff[i]); }catch (Exception e) { e.printStackTrace(); } } }
修改默认的序列化机制 在序列化的过程中,有些数据字段我们不想将其序列化,对于此类字段我们只需要在定义时给它加上transient关键字即可,对于transient字段序列化机制会跳过不会将其写入文件,当然也不可被恢复。但有时我们想将某一字段序列化,但它在SDK中的定义却是不可序列化的类型,这样的话我们也必须把他标注为transient,可是不能写入又怎么恢复呢?好在序列化机制为包含这种特殊问题的类提供了如下的方法定义: private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException; private void writeObject(ObjectOutputStream out) throws IOException; (注:这些方法定义时必须是私有的,因为不需要你显示调用,序列化机制会自动调用的) 使用以上方法我们可以手动对那些你又想序列化又不可以被序列化的数据字段进行写出和读入操作。 下面是一个典型的例子,java.awt.geom包中的Point2D.Double类就是不可序列化的,因为该类没有实现Serializable接口,在我的例子中将把它当作LabeledPoint类中的一个数据字段,并演示如何将其序列化 LabeledPoint类:
public class LabeledPoint implements Serializable{ private String label; transient private Point2D.Double point; public LabeledPoint(String label, double x, double y) { super (); this .label = label; this .point = new Point2D.Double(x,y); } private void writeObject(ObjectOutputStream oos) throws IOException{ oos.defaultWriteObject(); oos.writeDouble(point.getX()); oos.writeDouble(point.getY()); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException{ ois.defaultReadObject(); double x = ois.readDouble() + 1.0 ; double y = ois.readDouble() + 1.0 ; point = new Point2D.Double(x,y); } public String toString() { return getClass().getName() + "[ Label = " + label + ", point.getX() = " + point.getX() + ", point.getY() = " + point.getY() + "]" ; } }
transientTest类:
public class transientTest { public static void main(String[] args){ LabeledPoint label = new LabeledPoint( "Book" , 5.0 , 5.0 ); try { System.out.println("before:\n" + label); ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream( "c:\\label.txt" )); oos.writeObject(label); oos.close(); System.out.println("after:\n" + label); ObjectInputStream ois = new ObjectInputStream( new FileInputStream( "c:\\label.txt" )); LabeledPoint label1 = (LabeledPoint)ois.readObject(); ois.close(); System.out.println("after add 1.0:\n" + label); }catch (Exception e) { e.printStackTrace(); } } }
就其本身来说,对象的序列化是非常有趣的,因为利用它可以实现“有限持久化”。请记住“持久化”意味着对象的“生存时间”并不取决于程序是否正在执行——它存在或“生存”于程序的每一次调用之间。通过序列化一个对象,将其写入磁盘,以后在程序重新调用时重新恢复那个对象,就能圆满实现一种“持久”效果。之所以称其为“有限”,是因为不能用某种“persistent”(持久)关键字简单地定义一个对象,并让系统自动照看其他所有细节问题(尽管将来可能成为现实)。相反,必须在自己的程序中明确地序列化和组装对象。
语言里增加了对象序列化的概念后,可提供对两种主要特性的支持。Java 1.1的“远程方法调用”(RMI)使本来存在于其他机器的对象可以表现出好像就在本地机器上的行为。将消息发给远程对象时,需要通过对象序列化来传输参数和返回值。
对象的序列化也是Java Beans必需的,后者由Java 1.1引入。使用一个Bean时,它的状态信息通常在设计期间配置好。程序启动以后,这种状态信息必须保存下来,以便程序启动以后恢复;具体工作由对象序列化完成。
对象的序列化处理非常简单,只需对象实现了Serializable接口即可(该接口仅是一个标记,没有方法)。在Java 1.1中,许多标准库类都发生了改变,以便能够序列化——其中包括用于基本数据类型的全部封装器、所有集合类以及其他许多东西。甚至Class对象也可以序列化。
为序列化一个对象,首先要创建某些OutputStream对象,然后将其封装到ObjectOutputStream对象内。此时,只需调用writeObject()即可完成对象的序列化,并将其发送给OutputStream。相反的过程是将一个InputStream封装到ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得的是指向一个上溯造型Object的句柄,所以必须下溯造型,以便能够直接设置。
对象序列化特别“聪明”的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象内包含的所有句柄并保存那些对象;接着又能对每个对象内包含的句柄进行追踪;以此类推。我们有时将这种情况称为“对象网”,单个对象可与之建立连接。而且它还包含了对象的句柄数组以及成员对象。若必须自行操纵一套对象序列化机制,那么在代码里追踪所有这些链接时可能会显得非常麻烦。在另一方面,由于Java对象的序列化似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算法自动维护整个对象网。下面这个例子对序列化机制进行了测试。它建立了许多链接对象的一个“Worm”(蠕虫),每个对象都与Worm中的下一段链接,同时又与属于不同类(Data)的对象句柄数组链接:
Data类:
public class Data implements Serializable { private int i; Data(int x) { i = x; } public String toString() { return Integer.toString(i); } }
Worm类:
public class Worm implements Serializable{ private static int r() { return ( int ) (Math.random() * 10 ); } private Data[] d = { new Data(r()), new Data(r()), new Data(r()) }; private Worm next; private char c; Worm(int i, char x) { System.out.println(" Worm constructor: " + i); c = x; if (--i > 0 ) next = new Worm(i, ( char ) (x + 1 )); } Worm() { System.out.println("Default constructor " ); } public String toString() { String s = ": " + c + "( " ; for ( int i = 0 ; i < d.length; i++) s += d[i].toString(); s += ") " ; if (next != null ) s += next.toString(); return s; } public static void main(String[] args) { Worm w = new Worm( 6 , 'a' ); System.out.println("w = " + w); try { ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( "worm.out " )); out.writeObject("Worm storage " ); out.writeObject(w); out.close(); ObjectInputStream in = new ObjectInputStream( new FileInputStream( "worm.out " )); String s = (String) in.readObject(); Worm w2 = (Worm) in.readObject(); System.out.println(s + ", w2 = " + w2); } catch (Exception e) { e.printStackTrace(); } try { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); out.writeObject("Worm storage " ); out.writeObject(w); out.flush(); ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream(bout.toByteArray())); String s = (String) in.readObject(); Worm w3 = (Worm) in.readObject(); System.out.println(s + ", w3 = " + w3); } catch (Exception e) { e.printStackTrace(); } } }