序列化--Serializable接口和Externalizable接口

来源:互联网 发布:淘宝店铺群导航条 编辑:程序博客网 时间:2024/05/01 02:32

序列化--Serializable接口和Externalizable接口
序列化就是将对象转化为流的过程;反序列化就是把流中的数据转化为对象。
要实现序列化与反序列化,则类必须实现Serializable接口或Externalizable接口。如果某个类是可序列化的,则它的子类也可以被序列化。
1 通过实现Serializable接口来序列化对象
 1)序列化只针对非transient和非static的字段。因为序列指的是对象的持久化,而static字段是不属于对象的,所以不会被序列化。通过实现Serializable接口来序列化对象,在反序列化过程中不会调用被序列化对象所属类的任何构造方法,而使直接从对象序列化的二进制码中重构对象。
 2)一般情况下序列化会按照默认的方式进行(即,不会对transient修饰的字段序列化)。若要对transient修饰的字段序列化,则要在类中定义两个方法readObject(ObjectInputStream in)和writeObject(ObjectOutStream out)。必须指出的是在类中定义的readObject(ObjectInputStream in)和writeObject(ObjectOutStream out)方法必须是用private修饰的。然后可以在这两个方法中调用java.io.ObjectOutputStream和java.io.ObjectInputStream中的out.defaultWriteObject()和in.defaultReadObject()方法(如果不想调用它们也可以),它们会按照默认的序列化机制执行序列化;如果要序列化transient字段,则要在这些方法的后面添加相应的代码来实现对transient字段的序列化。当调用对象流中的readObject(ObjectInputStream in)和writeObject(ObjectOutStream out)方法时会检查类自己是否已经实现了这两个方法,如果已经实现了,则调用类中已经定义的readObject(ObjectInputStream in)和writeObject(ObjectOutStream out)方法。
 3) 如果在内存中对象所属的类还没有被加载,那么会先加载并初始化这个类(这是很显然的,因为在反序列化的时候你怎么知道要将流中的对象转化为什么类型。)。如果在classpath中不存在相应的类文件,那么会抛出classNotFoundException。

//Demonstrate Serialization
package bag1;

import java.io.*;

class Demo implements Serializable {
 int a;
 transient int b;

 public Demo() {
  System.out.println("Demo's default constructor was invoked");
 }

 public Demo(int a, int b) {
  System.out.println("Demo's constructor was invoked");
  this.a = a;
  this.b = b;
 }
 private void readObject(ObjectInputStream in) throws IOException,
   ClassNotFoundException {
  in.defaultReadObject();
  b = in.readInt();
 }

 private void writeObject(ObjectOutputStream out) throws IOException {
  out.defaultWriteObject();
  out.writeInt(b);
 }

 public String toString() {
  return "int a = " + a + ", transient int b = " + b;
 }
}
class SubDemo extends Demo {
 public SubDemo() {
  System.out.println("SubDemo's default constructor was invoked");
 }

 public SubDemo(int a, int b) {
  super(a, b);
  System.out.println("SubDemo's constructor was invoked");
  
 }
}
public class SerialTest{
 public static void main(String[] args) throws Exception {
  //Demo demo = new Demo(66, 99);
  SubDemo subDemo = new SubDemo(22, 55);
  System.out.println("before serialization");
  //System.out.println(demo);
  System.out.println(subDemo);
  ByteArrayOutputStream bout = new ByteArrayOutputStream();
  ObjectOutputStream oos = new ObjectOutputStream(bout);
  //oos.writeObject(demo);
  oos.writeObject(subDemo);

  ObjectInputStream ois = new ObjectInputStream(
    new ByteArrayInputStream(bout.toByteArray()));
  //Demo newDemo = (Demo) ois.readObject();
  SubDemo newSubDemo = (SubDemo) ois.readObject();
  System.out.println("after serialization");
  //System.out.println(newDemo);
  System.out.println(newSubDemo);

  oos.close();
  ois.close();
 }
}


执行结果:
Demo's constructor was invoked
SubDemo's constructor was invoked
before serialization
int a = 22, transient int b = 55
after serialization
int a = 22, transient int b = 55
可以看出transient字段按照自己制定的序列化机制也被序列化了。

2 通过Externalizable接口实现序列化
Externalizable接口是Serializable接口的子接口。它有两个方法writeExternal(ObjectOutput out) throws IOException和readExternal(ObjectInput in) throws IOException, ClassNotFoundException(在实现它们的时候,必须和此处方法的签名完全一致)。Externalizable接口与Serializable接口最明显的不同是在反序列化的时候会调用类的默认的构造方法,然后再调用readExternal(ObjectInput in) throws IOException, ClassNotFoundException。


//Demonstrate Externalization
package bag1;

import java.io.*;

class Demo implements Externalizable {
 int a;
 transient int b;

 public Demo() {
  System.out.println("Demo's default constructor was invoked");
 }

 public Demo(int a, int b) {
  System.out.println("Demo's constructor was invoked");
  this.a = a;
  this.b = b;
 }

 public void readExternal(ObjectInput in) throws IOException {
  System.out.println("public void readExternal(ObjectInput in) throws IOException");
  a = in.readInt();
  b = in.readInt();
 }

 public void writeExternal(ObjectOutput out) throws IOException {
  System.out.println("public void writeExternal(ObjectOutput out) throws IOException");
  out.writeInt(a);
  out.writeInt(b);
 }

 public String toString() {
  return "int a = " + a + ", transient int b = " + b;
 }
}

class SubDemo extends Demo {
 public SubDemo() {
  System.out.println("SubDemo's default constructor was invoked");
 }

 public SubDemo(int a, int b) {
  super(a, b);
  System.out.println("SubDemo's constructor was invoked");
 }
}

public class ExternalTest {
 public static void main(String[] args) throws Exception {
  // Demo demo = new Demo(66, 99);
  SubDemo subDemo = new SubDemo(22, 55);
  System.out.println("before serialization");
  // System.out.println(demo);
  System.out.println(subDemo);
  ByteArrayOutputStream bout = new ByteArrayOutputStream();
  ObjectOutputStream oos = new ObjectOutputStream(bout);
  // oos.writeObject(demo);
  oos.writeObject(subDemo);

  ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(
    bout.toByteArray()));
  // Demo newDemo = (Demo) ois.readObject();
  SubDemo newSubDemo = (SubDemo) ois.readObject();
  System.out.println("after serialization");
  // System.out.println(newDemo);
  System.out.println(newSubDemo);

  oos.close();
  ois.close();
 }
}

执行结果:
Demo's constructor was invoked
SubDemo's constructor was invoked
before serialization
int a = 22, transient int b = 55
public void writeExternal(ObjectOutput out) throws IOException
Demo's default constructor was invoked
SubDemo's default constructor was invoked
public void readExternal(ObjectInput in) throws IOException
after serialization
int a = 22, transient int b = 55

可以看出调用了Demo类和SubDemo类的默认的构造方法。所以必须而且只能将他们默认的构造方法的访问权限设置为public,如果把默认的构造方法的访问权限设置为private、默认或protected级别,则会抛出java.io.InvalidException: no valid constructor异常。

3 当类中定义了一个public static final long serialVersionUID = xxxL时,在被序列化的时候将不会再计算新的序列化版本的值,而是会直接使用这个值。序列化的版本是唯一的。如果要使类的升级版本兼容其早期的版本,则每个版本中的serialVersionUID都要和第一个类的一样,这样就可以读取不同版本的对象。如果类的方法发生了改变,则新对象的读取不会有丝毫问题。对象流会将当前版本的对象中的字段和流中的对象的字段做比较,如果两个对象字段的名称都一样但是类型不完全一样(这两个对象是不兼容的),则对象流不会进行转换。如果当前版本对象有流中的对象不存在的字段,则增加的字段会设为默认值;如果流内的对象有当前版本不存在的字段,则省略多余的字段。但是这样的转换很可能是不安全的,必须确保方法有足够的健壮性!

本人的一点小小心得,如有不正确的地方请指正!

原创粉丝点击