在JDK或者其他地方,相信各位读者都经常遇到Serializable这个词,这就是java中的序列化接口,定义在java.IO.*下面。
1.证明Serializable确实可以使对象保存状态信息。
SerializabeTest.java
package froest.serializable;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;class Data implements Serializable{ /** * */ private static final long serialVersionUID = 2018026507498303285L; private int i; Data(int x) { i = x; } public String toString() { return Integer.toString(i); }}public class SerializableTest implements Serializable { /** * */ private static final long serialVersionUID = 1303931195327594338L; /** * 产生随机整数 * @return */ private static int r() { return (int) (Math.random() * 10); } /** * 设置随机数数组,保证每次生成的数都是不相同的 */ private Data[] d = { new Data(r()), new Data(r()), new Data(r()) }; /** * 用于连接到下一个SerializableTest */ private SerializableTest next; private char c; /** * 用于设置一个链表,把多个SerializableTest连接起来 * @param i 需要生成i个SerializableTest对象 * @param x 用于表现每个SerializableTest对象的属性的区别 */ SerializableTest(int i,char x){ System.out.println("SerializableTest.Constructor:"+i); c = x; if(--i > 0){ next = new SerializableTest(i,(char)(x+1)); } } SerializableTest(){ System.out.println("Default constructor"); } /** * 把SerializableTest中的Data数组组织成String字符串,用于打印输出 */ public String toString(){ String s = ":" + c + "("; for(int i = 0; i < d.length; i++){ s += d[i].toString(); } s += ")"; if(null != next){ s += next.toString(); } return s; } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub SerializableTest st = new SerializableTest(6,'a'); System.out.println("st = "+ st); System.out.println("===================================="); /** * 用文件的方式实现序列化 */ try{ ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("SerializableTestFile.out")); out.writeObject("st"); out.writeObject(st); out.close(); ObjectInputStream in = new ObjectInputStream( new FileInputStream("SerializableTestFile.out")); String name1 = (String)in.readObject(); SerializableTest st1 = (SerializableTest)in.readObject(); System.out.println("name = " + name1 + ",st1 = " + st1); }catch(Exception e){ e.printStackTrace(); } System.out.println("==================================="); /** * 用字节流的方式实现序列化 */ try{ ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); out.writeObject("st"); out.writeObject(st); out.flush(); ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream(bout.toByteArray())); String name1 = (String)in.readObject(); SerializableTest st1 = (SerializableTest)in.readObject(); System.out.println("name = " + name1 + ",st = " + st1); }catch(Exception e){ e.printStackTrace(); } /** * 获得的结果都是一样的,说明档对象序列化之后,可以通过文件或者字节流再次得到对象的状态信息 * 因为此对象的Data数组是用随机数创建的,结果相同说明没有调用对象的构建器,连默认构建器都不会调用 */ }}
2.关于序列化后的对象所处的文件位置
看例子:
Alien.java
此类不做任何事情,只用于实现序列化接口,可以被保存入IO流
package froest.serializable.find;import java.io.Serializable;public class Alien implements Serializable { /** * 序列化搜索类 * * 若果这个文件在froest.serializable.xfile下面,把Alien.java,FreezeAlien.java,ThawAlien.java都编译一下 * 运行可以运行的 * 但是把Alien.java移动到froest.serializable.find目录下面 * FreezeAlien.java不编译,直接运行ThawAlien.java * 发现抛出异常ClassNotFoundException:froest.serializable.xfiles.Alien,发生在in.readObject()处 * 但是Alien.class已经存在在bin.froest.serializable.find目录下面,所以问题不是出在Alien.class上面,而是在files上面 * files文件是Alien.java在froest.serializable.xfile下面编译产生的,所以files中的对象所在的位置是在froest.serializable.xfile中, * 而现在froest.serializable.xfile包中并不能找到Alien.class文件,所以报ClassNotFoundException */}
FreezeAlien.java
package froest.serializable.find;import java.io.FileOutputStream;import java.io.ObjectOutputStream;public class FreezeAlien { /** * @param args */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("files")); Alien alien = new Alien(); out.writeObject(alien); //out.close(); }}
ThawAlien.java
package froest.serializable.xfiles;import java.io.FileInputStream;import java.io.ObjectInputStream;import froest.serializable.find.Alien;public class ThawAlien { /** * @param args */ public static void main(String[] args) throws Exception{ // TODO Auto-generated method stub ObjectInputStream in = new ObjectInputStream(new FileInputStream("files")); Alien alien1 = (Alien)in.readObject(); //System.out.println("alien = "+alien1); System.out.println("alien = "+alien1.getClass().toString()); }}
3.除了用Serializable实现序列化,还可以用Externalizable来实现实现序列化不会调,但是有一点区别:Serializable用默认的构造器,而Externalizable实现序列化会调用默认的构造器。
如果是用实现Serializable接口的序列化,那么如果不对属性做处理的话,所有的属性都会被保存下来,如果有敏感的信息不希望被保存的话,可以使用transient关键字,这样保存序列化对象的时候就不会把这个属性的状态信息保存起来了;另外如果这个属性字段用了transient关键字,而特殊情况下又需要保存这个属性的状态信息,那么可以在需要序列化的类中添加私有的writeObject()和readObject()方法即可。
如果用实现Externalizable接口的序列化,那么可以控制哪些字段做保存,不需要使用transient关键字,但是需要在需要实现序列化的类中添加readExternal()和writeExternal()方法来实现
下面看下我写的一些例子,在程序里面有时碰到问题就会去验证,所以加上了一些说明性文字:
Blip1.java
package froest.serializable.externalizable;import java.io.Externalizable;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInput;import java.io.ObjectInputStream;import java.io.ObjectOutput;import java.io.ObjectOutputStream;/** * 此例说明了实现Externalizable方法后,当恢复对象的时候会调用相应类的构造器,所以如果在构造器中没有初始化, * 那么会得到0或者null * @author froest * */class Blip1 implements Externalizable { private int i; public Blip1() { System.out.println("Blip1 constructor"); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("Blip1.readExternal"); } public void writeExternal(ObjectOutput out) throws IOException { System.out.println("Blip1.writeExternal"); } public void setI(int i) { this.i = i; } public int get() { return i; }}class Blip2 implements Externalizable { public Blip2() { System.out.println("Blip2.constructor"); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("Blip2.readExternal"); } public void writeExternal(ObjectOutput out) throws IOException { System.out.println("Blip2.writeExternal"); }}public class Blips { public static void main(String[] args) { System.out.println("constructor Object"); Blip1 b1 = new Blip1(); b1.setI(100); Blip2 b2 = new Blip2(); try { ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("Blip.x")); System.out.println("saving object"); out.writeObject(b1); out.writeObject(b2); out.close(); ObjectInputStream in = new ObjectInputStream(new FileInputStream( "Blip.x")); System.out.println("getting object"); System.out.println("recovering b1"); b1 = (Blip1) in.readObject(); System.out.println(b1.get()); System.out.println("recovering b2"); b2 = (Blip2) in.readObject(); } catch (Exception e) { e.printStackTrace(); } }}
Blip3.java
package froest.serializable.externalizable;import java.io.Externalizable;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInput;import java.io.ObjectInputStream;import java.io.ObjectOutput;import java.io.ObjectOutputStream;/** * 用Externalizable实现对象的序列化 * 1.需要在readObject中对需要恢复的对象进行读取,否则数据都是取自默认构造器中 * 2.如果readObject中读取了需要的数据,那么即使默认构造器对该数据进行了改变, * 也是无效的,保存什么数据,就输出什么数据, * 3.如果在构造器(非默认)中某个属性未初始化,那么这个属性会赋值为0或者null序列化, * 当读取数据的时候即使默认构造器对这个数据进行了初始化,也是没用的,属性的值依旧是 * 读取出来的0或者null * @author froest * */public class Blip3 implements Externalizable { private int i; private String s; public Blip3(){ this.i = 3; System.out.println("Blip3 constructor"); } public Blip3(int i,String s){ System.out.println("Blip3(int ,String)"); this.i = i+1; this.s = s; } public String toString(){ return s + " " + i; } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println("readExternal"); /** * 1.如果把in.readInt()注释掉,那么程序运行时将报错java.io.OptionalDataException * 而去掉注释,那么又是可以的,说明序列化后的对象属性必须按顺序读取,第一个属性取完了 * 才能取第二个属性,而且如果第一个属性未被读取,第二个属性也是不可以被读取的 * 2.如果i的值需要使用默认构造器来重新初始化,那么可以用in.readInt()来去除第一个值,但是不赋值给i * 这样既可以取得序列化对象中得下一个属性值,又可以用默认构造器来重新给i赋值 */ i = in.readInt(); s = (String)in.readObject(); } public void writeExternal(ObjectOutput out) throws IOException { System.out.println("writeExternal"); out.writeInt(i); out.writeObject(s); } /** * @param args */ public static void main(String[] args) { System.out.println("constructor objects"); Blip3 blip3 = new Blip3(47,"A string"); System.out.println(blip3); try{ System.out.println("save object"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("Blip3.out")); out.writeObject("save blip3"); out.writeObject(blip3); out.close(); System.out.println("read object"); ObjectInputStream in = new ObjectInputStream(new FileInputStream("Blip3.out")); /** *String ss = (String)in.readObject();这行代码必须存在,如果我注释 *或者放到Blip3 b = (Blip3)in.readObject();之后,那么都将报错,由此可以判断 *序列化到文件是有顺序的,必须什么顺序存入,就什么顺序取出,如果之前那个不取出, *下一个也不能被取出 */ String ss = (String)in.readObject(); Blip3 b = (Blip3)in.readObject(); System.out.println(ss); System.out.println(b); }catch(Exception e){ e.printStackTrace(); } }}
SerialCtr.java
package froest.serializable;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;public class SerialCtr implements Serializable { private static final long serialVersionUID = 5442274055003105383L; private String name; private transient String password; public SerialCtr(String a,String b){ this.name = a; this.password = b; } public String toString(){ return "Not transient : " + name + "\n transient : " + password; } private void writeObject(ObjectOutputStream out) throws IOException{ /** * 1.如果对象实现Serializable,但是又在自己的对象中定义了writeObject()和readObject()方法 * 那么编译器就会放弃Serializable接口的默认的序列化机制,但是可以在对象的writeObject()和readObject()中 * 调用out.defaultWriteObject()和in.defaultReadObject()方法来开启默认的序列化机制;如果对于transient字段 * 我们可以调用对象的writeObject()和readObject()方法来序列化,这就实现了transient属性不能序列化的缺点,因为 * 有的时候需要让transient属性页得到序列化 * 2.在这里的readObject()方法可以只取出一个属性,因为这是在序列化对象的内部,而序列化对象的外部是 * 把这整个序列化对象取出来,所以这里与顺序和取多少个属性无关(其他例子有说到顺序的问题,这是一点区别) */ out.defaultWriteObject(); out.writeObject(password); } private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{ in.defaultReadObject(); password = (String)in.readObject(); } /** * @param args */public static void main(String[] args) { SerialCtr s = new SerialCtr("LH","1qaz"); System.out.println("Before Serial: \n" + s); //ByteArrayOutputStream buf = new ByteArrayOutputStream(); try{ //ObjectOutputStream out = new ObjectOutputStream(buf); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("SerialExternal.out")); out.writeObject(s); out.close(); //ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray())); ObjectInputStream in = new ObjectInputStream(new FileInputStream("SerialExternal.out")); SerialCtr s1 = (SerialCtr)in.readObject(); System.out.println("After Serial: \n" + s1); }catch(Exception e){ e.printStackTrace(); } }}
4.若果一个类A中有类B和类C的引用,而类B中有类D的数组的引用,类C中有类E的引用,那么序列化机制是否可以正确的还原呢?
还是看下书上的例子吧:
package froest.serializable;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;import java.util.Vector;class House implements Serializable{ private static final long serialVersionUID = 7726379233155420025L;}class Animal implements Serializable{ private static final long serialVersionUID = 8278386775550453403L; private String name; private House house; public Animal(String name,House house){ this.name = name; this.house = house; } public String toString(){ return name + "[" + super.getClass() + "], " + house +"\n"; }}@SuppressWarnings("unchecked")public class MyWorld { public static void main(String[] args) { House house = new House(); Vector animals = new Vector(); animals.add(new Animal("dog", house)); animals.add(new Animal("cat", house)); animals.add(new Animal("mouse", house)); System.out.println(animals); try{ ByteArrayOutputStream buf1 = new ByteArrayOutputStream(); ObjectOutputStream out1 = new ObjectOutputStream(buf1); out1.writeObject(animals); out1.writeObject(animals); ByteArrayOutputStream buf2 = new ByteArrayOutputStream(); ObjectOutputStream out2 = new ObjectOutputStream(buf2); out2.writeObject(animals); out1.close(); out2.close(); ObjectInputStream in1 = new ObjectInputStream(new ByteArrayInputStream(buf1.toByteArray())); ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(buf2.toByteArray())); Vector animal1 = (Vector)in1.readObject(); Vector animal2 = (Vector)in1.readObject(); Vector animal3 = (Vector)in2.readObject(); /** * 1.animal1和animal2是同一个数据流中的对象,animal3是另一个数据流的对象,当对象的被恢复的时候可以看到 * house地址的区别:animal1和animal2中是相同的,而他们跟animal3是不同的,所以在同一个数据流中,相同的 * 对象不会重复出现。 * 2.当数据流不同的时候,所生成的对象网也会不同 */ System.out.println(animal1); System.out.println(animal2); System.out.println(animal3); System.out.println(animals); }catch(Exception e){ e.printStackTrace(); } }}
只要将对象序列化到单独一个数据流里面,就能恢复获得与以前一样的对象网,不会不慎造成对象的重复。要想保存系统状态,最安全的方法是将构成系统状态的所有对象都置入单个集合内,并在一次操作力完成那个集合的写入,这样就只需一次方法调用便可恢复。
这是我在序列化学习中学到的一些知识,若有不正确之处,望大家斧正,小菜拜谢。
------>froest