public class Person implements Serializable{ int age; String address; double height; public Person(int age, String address, double height) { this.age = age; this.address = address; this.height = height; }}public class SerializableTest{ public static void main(String[] args) throws IOException, IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( "d:/data.txt")); Person p = new Person(25,"China",180); oos.writeObject(p); oos.close(); }}执行结果:
![]()
1、对象序列化之后,写入的是一个二进制文件,所有打开乱码是正常现象,不过透过乱码我们还是可以看到文件中存储的就是我们创建的那个对象那个。
2、Person对象实现了Serializable接口,这个接口没有任何方法需要被实现,只是一个标记接口,表示这个类的对象可以被序列化。
![]()
3、在该程序中,我们是调用ObjectOutputStream对象的writeObject()方法输出可序列化对象的。该对象还提供了输出基本类型的方法。
writeInt
writeUTF(String str)
writeFloat(float val)
二、接下来我们来看下从文件中反序列化对象的过程:
1 public class SerializableTest 2 { 3 public static void main(String[] args) throws IOException, IOException, 4 ClassNotFoundException 5 { 6 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( 7 "d:/data.txt")); 8 Person p = new Person(25, "China", 180); 9 oos.writeObject(p);10 oos.close();11 12 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(13 "d:/data.txt"));14 Person p1 = (Person) ois.readObject();15 System.out.println("age=" + p1.age + ";address=" + p1.address16 + ";height=" + p1.height);
ois.close();17 }18 }执行结果:
age=25;address=China;height=180.0
1、从第12行开始就是反序列化的过程。其中输入流用到的是ObjectInputStream,与前面的ObjectOutputStream相对应。
2、在调用readObject()方法的时候,有一个强转的动作。所以在反序列化时,要提供java对象所属类的class文件。
3、如果使用序列化机制向文件中写入了多个对象,在反序列化时,需要按实际写入的顺序读取。
对象引用的序列化1、上面介绍对象的成员变量都是基本数据类型,如果对象的成员变量是引用类型,会有什么不同吗?
这个引用类型的成员变量必须也是可序列化的,否则拥有该类型成员变量的类的对象不可序列化。
2、在引用对象这个地方,会出现一种特殊的情况。例如,有两个Teacher对象,它们的Student实例变量都引用了同一个Person对象,而且该Person对象还有一个引用变量引用它。如下图所示:
![]()
这里有三个对象per、t1、t2,如果都被序列化,会存在这样一个问题,在序列化t1的时候,会隐式的序列化person对象。在序列化t2的时候,也会隐式的序列化person对象。在序列化per的时候,会显式的序列化person对象。所以在反序列化的时候,会得到三个person对象,这样就会造成t1、t2所引用的person对象不是同一个。显然,这并不符合图中所展示的关系,也违背了java序列化的初衷。
为了避免这种情况,JAVA的序列化机制采用了一种特殊的算法:
1、所有保存到磁盘中的对象都有一个序列化编号。
2、当程序试图序列化一个对象时,会先检查该对象是否已经被序列化过,只有该对象从未(在本次虚拟机中)被序列化,系统才会将该对象转换成字节序列并输出。
3、如果对象已经被序列化,程序将直接输出一个序列化编号,而不是重新序列化。
自定义序列化1、前面介绍可以用transient关键字来修饰实例变量,该变量就会被完全隔离在序列化机制之外。还是用前面相同的程序,只是将address变量用transient来修饰:
1 public class Person implements Serializable 2 { 3 int age; 4 transient String address; 5 double height; 6 public Person(int age, String address, double height) 7 { 8 this.age = age; 9 this.address = address;10 this.height = height;11 }12 } 1 public class SerializableTest 2 { 3 public static void main(String[] args) throws IOException, IOException, 4 ClassNotFoundException 5 { 6 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( 7 "d:/data.txt")); 8 Person p = new Person(25, "China", 180); 9 oos.writeObject(p);10 oos.close();11 12 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(13 "d:/data.txt"));14 Person p1 = (Person) ois.readObject();15 System.out.println("age=" + p1.age + ";address=" + p1.address16 + ";height=" + p1.height);
ois.close();17 }18 }序列化的结果:
![]()
反序列化结果:
age=25;address=null;height=180.0
2、在二进制文件中,没有看到”China”的字样,反序列化之后address的value值为null。
3、这说明使用tranisent修饰的变量,在经过序列化和反序列化之后,JAVA对象会丢失该实例变量值。
鉴于上述的这种情况,JAVA提供了一种自定义序列化机制。这样程序就可以自己来控制如何序列化各实例变量,甚至不序列化实例变量。
在序列化和反序列化过程中需要特殊处理的类应该提供如下的方法,这些方法用于实现自定义的序列化。
writeObject()
readObject()
这两个方法并不属于任何的类和接口,只要在要序列化的类中提供这两个方法,就会在序列化机制中自动被调用。
其中writeObject方法用于写入特定类的实例状态,以便相应的readObject方法可以恢复它。通过重写该方法,程序员可以获取对序列化的控制,可以自主决定可以哪些实例变量需要序列化,怎样序列化。该方法调用out.defaultWriteObject来保存JAVA对象的实例变量,从而可以实现序列化java对象状态的目的。
1 public class Person implements Serializable 2 { 3 /** 4 * 5 */ 6 private static final long serialVersionUID = 1L; 7 int age; 8 String address; 9 double height;10 public Person(int age, String address, double height)11 {12 this.age = age;13 this.address = address;14 this.height = height;15 }16 17 //JAVA BEAN自定义的writeObject方法18 private void writeObject(ObjectOutputStream out) throws IOException19 {20 System.out.println("writeObejct ------");21 out.writeInt(age);22 out.writeObject(new StringBuffer(address).reverse());23 out.writeDouble(height);24 }25 26 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException27 {28 System.out.println("readObject ------");29 this.age = in.readInt();30 this.address = ((StringBuffer)in.readObject()).reverse().toString();31 this.height = in.readDouble();32 }33 } 1 public class SerializableTest 2 { 3 public static void main(String[] args) throws IOException, IOException, 4 ClassNotFoundException 5 { 6 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( 7 "d:/data.txt")); 8 Person p = new Person(25, "China", 180); 9 oos.writeObject(p);10 oos.close();11 12 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(13 "d:/data.txt"));14 Person p1 = (Person) ois.readObject();15 System.out.println("age=" + p1.age + ";address=" + p1.address16 + ";height=" + p1.height);17 ois.close();18 }19 }序列化结果:
![]()
反序列化结果:
![]()
1、这个地方跟前面的区别就是在Person类中提供了writeObject方法和readObject方法,并且提供了具体的实现。
2、在ObjectOutputStream调用writeObject方法执行过程,肯定调用了Person类的writeObject方法,因为在控制台上将代码中第20行的日志输出了。
3、自定义实现的好处是:程序员可以更加精细或者说可以去定制自己想要实现的序列化,如例子中将address变量值反转。利用这种特点,我们可以在序列化过程中对一些敏感信 息做特殊的处理。
4、在这里因为我们在要序列化的类中提供了这两个方法,所以被调用了,如果不提供,我认为会默认调用ObjectOutputStream/ObjectInputStream提供的这两个方法。
![]()
序列化问题1、静态变量不会被序列化。
2、子类序列化时:
如果父类没有实现Serializable接口,没有提供默认构造函数,那么子类的序列化会出错;
如果父类没有实现Serializable接口,提供了默认的构造函数,那么子类可以序列化,父类的成员变量不会被序列化。
如果父类实现了Serializable接口,则父类和子类都可以序列化。
【转载】原文地址:https://blog.csdn.net/qq_38977097/article/details/80912895