Java_IO序列化(19)

Serializable

序列化是将对象转换为字节流,反序列化是将字节流转换为对象

要让一个类可以被序列化,需要让这个类实现 Serializable 接口,因为 Serializable 是一个标识接口,所以不用重写任何方法

1
public interface Serializable {}

反序列化是通过序列化的二进制数据来创建对象,不会调用类的构造方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class Test {
  public static void main(String[] args) {
    testWrite();
    testRead();
  }
  private static void testWrite() {
    try {
      //通过ObjectOutputStream序列化后再通过FileOutputStream写入文件
      ObjectOutputStream out = new ObjectOutputStream(
        new FileOutputStream("file.txt"));
      //序列化基本类型
      out.writeBoolean(true);
      out.writeByte((byte)65);
      out.writeChar('a');
      out.writeInt(12345);
      out.writeFloat(3.14F);
      out.writeDouble(3.14159D);
      //序列化自定义对象
      Student stu = new Student("张三");
      out.writeObject(stu);
      //序列化HashMap对象
      HashMap map = new HashMap();
      map.put("one", "red");
      map.put("two", "green");
      map.put("three", "blue");
      out.writeObject(map);
      out.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }
  private static void testRead() {
    try {
      //通过FileInputStream读取文件后再通过ObjectOutputStream反序列化
      ObjectInputStream in = new ObjectInputStream(
        new FileInputStream("file.txt"));
      //反序列化基本类型
      System.out.println(in.readBoolean());
      System.out.println((in.readByte() & 0xff)); //保证二进制数据一致性
      System.out.println(in.readChar());
      System.out.println(in.readInt());
      System.out.println(in.readFloat());
      System.out.println(in.readDouble());
      //反序列化自定义对象
      Student stu = (Student) in.readObject();
      System.out.println(stu);
       //反序列化HashMap对象,对象会被自动提升为Object类
      HashMap map = (HashMap) in.readObject();
      Iterator iter = map.entrySet().iterator();
      while (iter.hasNext()) {
        Map.Entry entry = (Map.Entry)iter.next();
        System.out.println(entry.getKey() + ": " + entry.getValue());
      }
      in.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
class Student implements Serializable {
  private String name;
  public Student(String name) {
    this.name = name;
  }
  @Override
  public String toString() {
    return name;
  }
}

transient 和 static

序列化是针对对象而言,所以对 static 属性无效,还可以通过 transient 关键字修饰属性,指定不序列化某个属性,例如,将 Student 类修改如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Student implements Serializable {
  private String name;
  private static int width = 100;
  private transient int height;
  public Student(String name, int height) {
    this.name = name;
    this.height = height;
  }
  @Override
  public String toString() {
    return "[" + name + ": (" + width + ", " + height + ") ]";
  }
}

height 被 transient 修饰,不会被序列化,反序列化后被赋为默认值 0,age 被 static 修饰,不会被序列化,但其值在序列化之前就定义在类中了,跟对象无关,可以通过定义 writeObject 和 readObject 方法并传入 ObjectOutputStream 的方式手动地保存 static 和 transient 属性的值 例如,将 Student 类修改如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Student implements Serializable {
  private String name;
  private static int width = 100;
  private transient int height;
  public Student(String name, int height) {
    this.name = name;
    this.height = height;
  }
  @Override
  public String toString() {
    return "[" + name + ": (" + width + ", " + height + ") ]";
  }
  //writeObject、readObject返回值为void,定义为private防止对象被恶意修改
  private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject(); //写入默认可以序列化的字段
    out.writeInt(height + 100);
    out.writeInt(width + 100);
  }
  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject(); //读出默认可以序列化的字段
    height = in.readInt() - 50;
    width = in.readInt() - 50;
  }
}

在序列化过程中,如果被序列化的类定义了 writeObject 和 readObject 方法,JVM 就会调用对象类里定义的 writeObject 和 readObject 方法,进行自定义序列化和反序列化,如果类中没有定义,则调用 ObjectOutputStream 和 ObjectInputStream 的 defaultWriteObject 和 defaultReadObject 方法进行默认序列化和反序列化

Externalizable

除了使用关键字 transient ,还可以通过 Externalizable 实现部分属性序列化,Externalizable 接口继承了 Serializable,使用 Externalizable 需要实现 writeExternal 和 readExternal 方法,由于readExternal 方法是通过反射来创建对象的,所以一定要有默认的无参构造方法

1
2
3
4
public interface Externalizable extends java.io.Serializable {
  void writeExternal(ObjectOutput out) throws IOException;
  void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

例如,将 Student 类修改如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Student implements Externalizable {
  private String name;
  private static int width = 100;
  private transient int height;
  //注意使用Externalizable一定要有默认无参构造方法
  public Student() {}
  public Student(String name, int height) {
    this.name = name;
    this.height = height;
  }
  @Override
  public String toString() {
    return "[" + name + ": (" + width + ", " + height + ") ]";
  }
  @Override
  public void writeExternal(ObjectOutput out) throws IOException {
    out.writeObject(name);
    out.writeInt(width + 100);
    out.writeInt(height + 100);
  }
  @Override
  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    name = (String) in.readObject();
    width =  in.readInt() - 50;
    height = in.readInt() - 50;
  }
}

序列化 ID

对于一个序列化了的对象,如果其类中的定义被修改了,将产生版本兼容问题,serialVersionUID 则是用来解决版本兼容问题的,serialVersionUID 如果不同则不能进行反序列化,如果没有显式地定义 serialVersionUID,JVM 则会创建一个对于类的改变非常敏感的默认 serialVersionUID,类稍微变化一点内容就会报 InvalidClassException 异常而不能反序列化,显式地定义如下

1
private static final long serialVersionUID = 1L;

以上内容是玉山整理的笔记,如有错误还请指出