Java序列化 Serializable和Externalizable浅析

Serializable接口

public interface Serializable {
}

JDK源码里,Serializable接口只是一个空接口(标记接口),实际上,实现了Serializable接口的类,其序列化和反序列化过程,都是全自动的。

ObjectOutputStream#writeObject() 与 NotSerializableException

一个类必须实现Serializable接口才能序列化它的对象,不然调用ObjectOutputStream的writeObject方法会报错。

import java.io.*;

class book {
    private String bookName;
    private int bookNum;

    public book(String bookName,int bookNum) {
        this.bookName = bookName;
        this.bookNum = bookNum;
    }
}

public class test {
    public static void write() throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));
        out.writeObject(new book("如何实现财务自由",1));
    }
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        write();
    }
}

如上程序,运行后在ObjectOutputStream.writeObject()处报错Exception in thread “main” java.io.NotSerializableException: book。

当类定义加上Serializable后,程序不再报错:

class book implements Serializable {
    private String bookName;
    private int bookNum;

    public book(String bookName,int bookNum) {
        this.bookName = bookName;
        this.bookNum = bookNum;
    }
}

ObjectInputStream#readObject() 与 ClassNotFoundException

在运行下面程序之前,先把图中的book.class给删掉。
在这里插入图片描述

import java.io.*;

public class test1 {

    public static void read() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));
        book b = (book) in.readObject();
        System.out.println(b);
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        read();
    }
}

如上程序,运行后在ObjectInputStream.readObject()处报错Exception in thread “main” java.lang.ClassNotFoundException: book。注意报错是在readObject方法里,而与强转类型无关。

Serializable接口的类,不会调用任何构造器

需要注意的是,实现了Serializable接口的类的对象,在序列化的过程中,没有调用过任何构造器,包括默认构造器。

import java.io.*;

class writer implements Serializable{
    String writerName;
    int age;

    writer(String writerName, int age) {
        this.writerName = writerName;
        this.age = age;
    }
}

class book implements Serializable {
    private String bookName;
    private int bookNum;
    private writer w;

    public book() {System.out.println("这是默认构造器");}

    public book(String bookName,int bookNum, writer w) {
        System.out.println("这是有参构造器");
        this.bookName = bookName;
        this.bookNum = bookNum;
        this.w = w;
    }

}

public class test {
    public static void write() throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));
        // 只有下面new的时候,才调用了有参构造器一下
        out.writeObject(new book("如何实现财务自由",1, new writer("巴菲特",56)));  
    }

    public static void read() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));
        book b = (book) in.readObject();  //这里并没有调用任何构造器
        System.out.println(b);
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        write();
        read();
    }
}/*输出:
这是有参构造器
book@27d6c5e0
*/

通过输出可以看出,反序列化的过程中,任何构造器都没有调用过。

非基本类型成员变量,需实现Serializable接口

在最开始的例子中,book类有两个成员变量,其中有个String类型的成员变量,很明显,String不是一个基本类型。那让我们来看一下String的类定义:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

发现作为非基本类型的String确实实现了Serializable接口,但这不足以证明“非基本类型成员变量,需实现Serializable接口”。所以我们再反向验证一下:

import java.io.*;

class writer {  //需实现Serializable接口
    String writerName;
    int age;

    writer(String writerName, int age) {
        this.writerName = writerName;
        this.age = age;
    }
}

class book implements Serializable {
    private String bookName;
    private int bookNum;
    private writer w;  //添加一个writer成员

    public book(String bookName,int bookNum, writer w) {
        this.bookName = bookName;
        this.bookNum = bookNum;
        this.w = w;
    }

}

public class test {
    public static void write() throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));
        out.writeObject(new book("如何实现财务自由",1, new writer("巴菲特",56)));
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        write();
    }
}
  • 现令book类新增一个成员变量为自定义类writer,且writer没有实现Serializable接口。
  • 程序运行后,报错Exception in thread “main” java.io.NotSerializableException: writer。
  • 为writer类实现Serializable接口后,程序能运行成功。

泛型类型的成员变量,需实现Serializable接口

import java.io.*;

class Item {  //需实现Serializable接口
    String itemName;
    int quality;

    Item(String itemName, int quality) {
        this.itemName = itemName;
        this.quality = quality;
    }
}

class Factory<I> implements Serializable{
    I item;
    Factory(I item) {
        this.item = item;
    }
}

public class test2 {
    public static void main(String[] args) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));
        out.writeObject(new Factory<Item>(new Item("共享充电宝",2)));
    }
}

同样的,成员变量的类型为泛型类型,这个泛型类型也得实现Serializable接口。上面程序直接运行报错,但Item类加上Serializable接口后,则可运行成功。

import java.io.*;

class Key {}  //需实现Serializable接口

class MyHashSet<K> implements Serializable{
    static class Node<K> {  //需实现Serializable接口
        K key;
        Node(K key) {
            this.key = key;
        }
    }

    Node<K>[] nodes;
    MyHashSet(K... keys ) {
        nodes = new Node[keys.length];
        int index = 0;
        for(K k : keys) {
            nodes[index++] = new Node<K>(k);
        }
    }
}

public class test2 {
    public static void main(String[] args) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));
        out.writeObject(new MyHashSet<Key>( new Key(), new Key()));
    }
}

另一个例子,同样的,直接运行会报错,除非你把两个类定义(MyHashSet$NodeKey)添加上Serializable接口。

import java.io.*;
import java.util.*;

public class test3 {
    static class Key {}  //需实现Serializable接口
    static class Value {}  //需实现Serializable接口

    public static void main(String[] args) throws IOException {
        HashMap<Key,Value> map = new HashMap<Key,Value>();
        map.put(new Key(), new Value());
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));
        out.writeObject(map);
    }
}

直接使用HashMap,两个泛型类型是自定义的类,同样没有实现Serializable接口。直接运行会报错。

序列化保存所有对象网

一个对象包含若干个成员变量,成员变量又可能包含它的成员变量,这些持有的引用最终形成了一个无形的对象网,但Java的序列化过程却能把整个对象网络包含的所有对象全部序列化下来。

import java.io.*;
import java.util.*;

class Data implements Serializable{
    private int n;
    public Data(int n) { this.n = n; }
    public String toString() { return Integer.toString(n); }
}

public class Worm implements Serializable {
    private static Random rand = new Random(47);
    private Data[] d = {
            new Data(rand.nextInt(10)),
            new Data(rand.nextInt(10)),
            new Data(rand.nextInt(10))
    };
    private Worm next;
    private char c;
    // Value of i == number of segments
    public Worm(int i, char x) {
        print("Worm constructor: " + i);
        c = x;
        if(--i > 0)
            next = new Worm(i, (char)(x + 1));
    }
    public Worm() {
        print("Default constructor");
    }
    public String toString() {
        StringBuilder result = new StringBuilder(":");
        result.append(c);
        result.append("(");
        for(Data dat : d)
            result.append(dat);
        result.append(")");
        if(next != null)
            result.append(next);
        return result.toString();
    }
    public static void print(Object o) { System.out.println(o); }

    public static void main(String[] args)
            throws ClassNotFoundException, IOException {
        Worm w = new Worm(6, 'a');
        print("w = " + w);
        ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream("worm.out"));
        out.writeObject("Worm storage\n");
        out.writeObject(w);
        out.close(); // Also flushes output
        ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("worm.out"));
        String s = (String)in.readObject();
        Worm w2 = (Worm)in.readObject();
        print(s + "w2 = " + w2);
        ByteArrayOutputStream bout =
                new ByteArrayOutputStream();
        ObjectOutputStream out2 = new ObjectOutputStream(bout);
        out2.writeObject("Worm storage\n");
        out2.writeObject(w);
        out2.flush();
        ObjectInputStream in2 = new ObjectInputStream(
                new ByteArrayInputStream(bout.toByteArray()));
        s = (String)in2.readObject();
        Worm w3 = (Worm)in2.readObject();
        print(s + "w3 = " + w3);
    }
} /* Output:
Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :a(853):b(119):c(802):d(788):e(199):f(881)
Worm storage
w2 = :a(853):b(119):c(802):d(788):e(199):f(881)  //打印出的结果,和序列化之前的一样
Worm storage
w3 = :a(853):b(119):c(802):d(788):e(199):f(881)
*///:~
  • Worm类的构造器调用new,这说明是构造器的递归调用。不过有着if(--i > 0)的递归终点,所以整个递归过程只会执行 主函数中给定的数字的次数。
  • Worm对象有一个Worm成员变量,它们之间就形成了一个对象网。而且Worm对象还有着Data[]成员,整个对象网络包含的对象还不少。
  • 但即使是这样复杂的对象网络,Java的自动化序列化过程也能把所有对象都保存下来。这一点可以从打印结果看出来。
  • 注意到,第二次的序列化和反序列化,使用到了ByteArrayOutputStream/ByteArrayInputStream,这和之前没有区别,只是之前的过程是把字节流写入到了文件里,现在是把字节流写入到了内存中。

transient与writeObject/readObject

transient关键字

对象一旦经过序列化,即使是私有属性也会执行序列化,由于反射技术的存在,私有属性等同于裸奔在所有人面前。所以,为了保证某个属性根本不会执行序列化步骤,必须使用transient关键字。

import java.io.*;

class book implements Serializable {
    private String bookName;
    private int bookNum;
    private transient String writerName;  //此属性不参与序列化

    @Override
    public String toString() {
        return "book{" +
                "bookName='" + bookName + '\'' +
                ", bookNum=" + bookNum +
                ", writerName='" + writerName + '\'' +
                '}';
    }
    
    public book(String bookName,int bookNum,String writerName) {
        this.bookName = bookName;
        this.bookNum = bookNum;
        this.writerName = writerName;
    }

}

public class test {
    public static void write() throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));
        out.writeObject(new book("如何实现财务自由",1, "巴菲特"));
    }

    public static void read() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));
        book b = (book) in.readObject();
        System.out.println(b);
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        write();
        read();
    }
}/*输出:
book{bookName='如何实现财务自由', bookNum=1, writerName='null'}
*/

可见,在整个透明的序列化过程之后,读取出来的writerName为null。这说明带有transient关键字的成员变量,不会参与到序列化的过程中,所以,在反序列化之后,transient关键字的成员变量也只有初始化的值。

writeObject/readObject方法

  • 我们现在知道,当一个类实现了Serializable接口,那么在调用ObjectOutputStream.writeObject(该类的对象)时,整个序列化过程会全自动进行:序列化每个成员变量,如果成员变量又持有了别的对象的引用,那么这些对象又会被序列化,当然这个过程会除开带有transient关键字的成员变量
  • 但如果我们提供了writeObject/readObject方法后,那么在调用ObjectOutputStream.writeObject(该类的对象)时,将不再执行这个全自动过程,转而执行你自己实现的这两个方法。注意这两个方法的签名必须和如下签名一模一样,这有点神奇,其实它是使用到了反射技术来检测方法的签名。
    private void writeObject(java.io.ObjectOutputStream s)
        throws IOException {}
    private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {}
  • 如果你已经提供了这两个特定签名的方法,但你还是想使用一下 序列化的全自动过程,那么你可以在writeObject/readObject方法里,调用s.defaultWriteObject();s.defaultReadObject();
import java.io.*;

class book implements Serializable {
    private String bookName;
    private int bookNum;
    private transient String writerName;

    @Override
    public String toString() {
        return "book{" +
                "bookName='" + bookName + '\'' +
                ", bookNum=" + bookNum +
                ", writerName='" + writerName + '\'' +
                '}';
    }

    public book(String bookName,int bookNum,String writerName) {
        this.bookName = bookName;
        this.bookNum = bookNum;
        this.writerName = writerName;
    }

    private void writeObject(java.io.ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
        s.writeObject("序列化后:"+writerName);
    }
    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        writerName = (String) s.readObject();
    }
}

public class test {
    public static void write() throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));
        out.writeObject(new book("如何实现财务自由",1, "巴菲特"));
    }

    public static void read() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));
        book b = (book) in.readObject();
        System.out.println(b);
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        write();
        read();
    }
}}/*输出:
book{bookName='如何实现财务自由', bookNum=1, writerName='序列化后:巴菲特'}
*/
  • 因为提供了两种特定签名的writeObject/readObject方法,所以不会走 全自动序列化过程了,转而执行你自己的逻辑。
  • 在writeObject/readObject方法里,首先调用了s.defaultWriteObject();s.defaultReadObject();,所以还是执行了 全自动序列化过程,但带有transient关键字的属性没有序列化进去。
  • 为了保证带有transient关键字的属性能够参与序列化,需要自己实现逻辑,即自己调用ObjectOutputStream.writeObject()ObjectInputStream.readObject()

HashMap的序列化过程

如下为HashMap的序列化过程:

    transient Node<K,V>[] table;  //存储节点的table,但它又是必须序列化的成员

    private void writeObject(java.io.ObjectOutputStream s)
        throws IOException {
        int buckets = capacity();
        // Write out the threshold, loadfactor, and any hidden stuff
        s.defaultWriteObject();  //执行自动化过程
        s.writeInt(buckets);  //写入容量
        s.writeInt(size);  //写入大小
        internalWriteEntries(s);
    }

    void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {
        Node<K,V>[] tab;
        if (size > 0 && (tab = table) != null) {
            for (int i = 0; i < tab.length; ++i) {  //遍历整个table数组
                for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                    s.writeObject(e.key);  //先写入当前节点的key
                    s.writeObject(e.value);  //再写入当前节点的value
                }
            }
        }
    }

如下为HashMap的反序列化过程:

//注意,该函数我省略了部分代码
    private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        s.defaultReadObject();  //执行自动化过程
        s.readInt();                // 读出容量,但直接忽略掉
        int mappings = s.readInt(); // 读出大小

        // Read the keys and values, and put the mappings in the HashMap
        for (int i = 0; i < mappings; i++) {  //遍历整个table数组
            @SuppressWarnings("unchecked")
                K key = (K) s.readObject();  //先读出当前节点的key
            @SuppressWarnings("unchecked")
                V value = (V) s.readObject();  //先读出当前节点的value
            putVal(hash(key), key, value, false, false);
        }
    }

Externalizable接口

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

看来这个Externalizable不再是个空接口了,人家可是有方法的。

必须有public的默认构造器

import java.io.*;

class writer implements Serializable{
    String writerName;
    int age;

    @Override
    public String toString() {
        return "writer{" +
                "writerName='" + writerName + '\'' +
                ", age=" + age +
                '}';
    }

    writer(String writerName, int age) {
        this.writerName = writerName;
        this.age = age;
    }
}

class book implements Externalizable {
    private String bookName;
    private int bookNum;
    private writer w;

    @Override
    public String toString() {
        return "book{" +
                "bookName='" + bookName + '\'' +
                ", bookNum=" + bookNum +
                ", writer='" + w + '\'' +
                '}';
    }

    // public book () {}   //解开注释以运行成功

    public book(String bookName,int bookNum,writer w) {
        this.bookName = bookName;
        this.bookNum = bookNum;
        this.w = w;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(bookName);
        out.writeInt(bookNum);
        out.writeObject(w);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        bookName = (String) in.readObject();
        bookNum = in.readInt();
        w = (writer) in.readObject();
    }
}

public class test {
    public static void write() throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));
        out.writeObject(new book("如何实现财务自由",1, new writer("巴菲特",56)));
    }

    public static void read() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));
        book b = (book) in.readObject();
        System.out.println(b);
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        write();
        read();
    }
}
  • 当你直接运行此程序,发现会报错java.io.InvalidClassException: book; no valid constructor,除非你提供一个public的默认构造器。
  • 当你实现的是Externalizable接口时,所有成员都得自己实现序列化的逻辑,不然就不会参与序列化之中。
  • transient关键字就毫无意义了,特定签名的writeObject/readObject方法也毫无意义了。s.defaultWriteObject();s.defaultReadObject();也不能使用了,因为参数变成了ObjectOutputObjectInput接口了。
  • 而且注意,成员变量在定义时的初始化也会执行。(这好像是废话,因为肯定得先执行字段定义时初始化,再调用构造器)。修改部分代码以验证:
class writer implements Serializable{
    String writerName;
    int age;

    @Override
    public String toString() {
        return "writer{" +
                "writerName='" + writerName + '\'' +
                ", age=" + age +
                '}';
    }

    writer(String writerName, int age) {
        System.out.println("调用了writer的构造器");
        this.writerName = writerName;
        this.age = age;
    }
}

class book implements Externalizable {
    private String bookName;
    private int bookNum;
    private writer w = new writer("索罗斯",59);  //字段定义时的初始化
...
/*输出:
调用了writer的构造器
调用了writer的构造器
调用了writer的构造器   //第三次打印,是反序列化时,调用默认构造器之前打印出来的
book{bookName='如何实现财务自由', bookNum=1, writer='writer{writerName='巴菲特', age=56}'}
*/

serialVersionUID

当你实现了Serializable接口,你需要添加一个静态变量serialVersionUID,这个静态变量是用来验证版本一致性的,当传来的字节流中的serialVersionUID,和本地class文件的serialVersionUID不一致时,反序列化就会报错。

如果你没有显式地添加静态变量serialVersionUID,那么程序会为你隐式地自动生成一个serialVersionUID,但这却带来了极大的隐患。

import java.io.*;

class book implements Serializable {
    private String bookName;
    private int bookNum;
    // private int a;

    public book(String bookName,int bookNum) {
        this.bookName = bookName;
        this.bookNum = bookNum;
    }
}

public class test {
    public static void write() throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));
        out.writeObject(new book("如何实现财务自由",1));
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        write();
    }
}

如上程序执行完毕后,取消掉private int a;的注释。然后再执行如下代码:

import java.io.*;

public class test1 {

    public static void read() throws IOException, ClassNotFoundException {
        new book("打工是不可能打工的",1);
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));
        book b = (book) in.readObject();
        System.out.println(b);
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        read();
    }
}
  • new book("打工是不可能打工的",1);这里必须执行,不然不会更新本地的class文件。由于book多了一个变量,serialVersionUID发生了改变。
  • 运行后报错:java.io.InvalidClassException: book; local class incompatible: stream classdesc serialVersionUID = 3988735506709559426, local class serialVersionUID = 6344372820327052271
  • 同样的,Externalizable接口,也最好给定serialVersionUID,不然可能会发生同样的错误。

未实现Serializable的父类,不参与序列化

import java.io.*;
import java.util.*;

class Father {
    String FatherName;

    public Father(String fatherName) {
        FatherName = fatherName;
    }
}

class Son extends Father implements Serializable {
    String SonName;

    public Son(String fatherName, String sonName) {
        super(fatherName);
        SonName = sonName;
    }

    @Override
    public String toString() {
        return "Son{" +
                "FatherName='" + FatherName + '\'' +
                ", SonName='" + SonName + '\'' +
                '}';
    }
}

public class test4 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.out"));
        out.writeObject(new Son("爸爸","儿子"));
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.out"));
        Son s = (Son) in.readObject();
        System.out.println(s);
    }
}
  • 如上代码,父类并没有实现Serializable接口,直接运行时,会报错java.io.InvalidClassException: Son; no valid constructor,看来是没有默认构造器闹的。
  • 当你给如上代码的Father类加上implements Serializable时,程序就能直接运行成功了。且打印结果为Son{FatherName='爸爸', SonName='儿子'}
import java.io.*;
import java.util.*;

class Father {
    String FatherName;
    public Father() {}
    public Father(String fatherName) {
        FatherName = fatherName;
    }
}

class Son extends Father implements Serializable {
    String SonName;
    public Son() {
        super();
    }
    public Son(String fatherName, String sonName) {
        super(fatherName);
        SonName = sonName;
    }

    @Override
    public String toString() {
        return "Son{" +
                "FatherName='" + FatherName + '\'' +
                ", SonName='" + SonName + '\'' +
                '}';
    }
}

public class test4 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.out"));
        out.writeObject(new Son("爸爸","儿子"));
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.out"));
        Son s = (Son) in.readObject();
        System.out.println(s);
    }
}
  • 经过为父类和子类都添加上默认构造器后,程序能够运行成功。但注意,默认构造器可以不是public的了,private都可以。
  • 打印结果为Son{FatherName='null', SonName='儿子'},可见FatherName为null跟序列化没啥关系,都是因为你默认构造器啥也没干造成的。
  • 因为父类没有实现Serializable接口,所以程序是不会序列化父类对象的,但一个子类对象存在的前提就是得先有父类对象,所以就强制让你加一个子类的默认构造器,而子类的任何构造器都得调用到父类的构造器,所以,这样就保证了父类构造器能被调用。

父类实现了Serializable接口,子类也会是Serializable的

这个好像是废话,总之,从继承树上来说,子类是间接继承到Serializable了,所以子类肯定也可以序列化的。下一章的Shape和它的三个子类就体现了这一点。

静态变量不参与到序列化过程中

import java.io.*;
import java.util.*;

abstract class Shape implements Serializable {
    public static final int RED = 1, BLUE = 2, GREEN = 3;
    private int xPos, yPos, dimension;
    private static Random rand = new Random(47);
    private static int counter = 0;
    public abstract void setColor(int newColor);
    public abstract int getColor();
    public Shape(int xVal, int yVal, int dim) {
        xPos = xVal;
        yPos = yVal;
        dimension = dim;
    }
    public String toString() {
        return getClass() +
                "color[" + getColor() + "] xPos[" + xPos +
                "] yPos[" + yPos + "] dim[" + dimension + "]\n";
    }
    public static Shape randomFactory() {
        int xVal = rand.nextInt(100);
        int yVal = rand.nextInt(100);
        int dim = rand.nextInt(100);
        switch(counter++ % 3) {
            default:
            case 0: return new Circle(xVal, yVal, dim);
            case 1: return new Square(xVal, yVal, dim);
            case 2: return new Line(xVal, yVal, dim);
        }
    }
}

class Circle extends Shape {
    private static int color = RED;
    public Circle(int xVal, int yVal, int dim) {
        super(xVal, yVal, dim);
    }
    public void setColor(int newColor) { color = newColor; }
    public int getColor() { return color; }
}

class Square extends Shape {
    private static int color;
    public Square(int xVal, int yVal, int dim) {
        super(xVal, yVal, dim);
        color = RED;
    }
    public void setColor(int newColor) { color = newColor; }
    public int getColor() { return color; }
}

class Line extends Shape {
    private static int color = RED;
    public static void
    serializeStaticState(ObjectOutputStream os)
            throws IOException { os.writeInt(color); }
    public static void
    deserializeStaticState(ObjectInputStream os)
            throws IOException { color = os.readInt(); }
    public Line(int xVal, int yVal, int dim) {
        super(xVal, yVal, dim);
    }
    public void setColor(int newColor) { color = newColor; }
    public int getColor() { return color; }
}

public class StoreCADState {
    public static void main(String[] args) throws Exception {
        List<Shape> shapes = new ArrayList<Shape>();
        // Make some shapes:
        for(int i = 0; i < 10; i++)
            shapes.add(Shape.randomFactory());
        // Set all the static colors to GREEN:
        for(int i = 0; i < 10; i++)
            ((Shape)shapes.get(i)).setColor(Shape.GREEN);

        List<Class<? extends Shape>> shapeTypes =
                new ArrayList<Class<? extends Shape>>();
        // Add references to the class objects:
        shapeTypes.add(Circle.class);
        shapeTypes.add(Square.class);
        shapeTypes.add(Line.class);

        // Save the state vector:
        ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream("CADState.out"));
        out.writeObject(shapeTypes);
        Line.serializeStaticState(out);
        out.writeObject(shapes);
        // Display the shapes:
        System.out.println(shapes);
    }
} /* Output:
[class Circlecolor[3] xPos[58] yPos[55] dim[93]
, class Squarecolor[3] xPos[61] yPos[61] dim[29]
, class Linecolor[3] xPos[68] yPos[0] dim[22]
, class Circlecolor[3] xPos[7] yPos[88] dim[28]
, class Squarecolor[3] xPos[51] yPos[89] dim[9]
, class Linecolor[3] xPos[78] yPos[98] dim[61]
, class Circlecolor[3] xPos[20] yPos[58] dim[16]
, class Squarecolor[3] xPos[40] yPos[11] dim[22]
, class Linecolor[3] xPos[4] yPos[83] dim[6]
, class Circlecolor[3] xPos[75] yPos[10] dim[42]
]
*///:~
  • 这个例子来自Java编程思想,稍作改动。简单介绍一下就是:有个Shape父类,其有三个子类,这三个子类都新增了一个静态变量color,此代码把三个子类的Class对象序列化到文件,以观察反序列后静态变量。
  • 静态变量color会在字段定义就初始化,紧接着会被静态代码块修改(代码中没有静态代码块)。再然后,就是当构造器调用或函数调用时,也会改变静态变量(提供的成员方法setColor可修改,Square的构造器里会修改,Line的静态方法deserializeStaticState也会修改)。
  • 在调用for(int i = 0; i < 10; i++) ((Shape)shapes.get(i)).setColor(Shape.GREEN);的循环后,三个子类的静态变量color都会被修改为3,以至于在之后的打印结果里,所有静态变量都是3。在这个循环后,我们将Class对象放入List,然后将Class对象的List序列化,以观察反序列化后,序列化之前静态变量的修改是否能体现。
import java.io.*;
import java.util.*;

public class RecoverCADState {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws Exception {
        ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("CADState.out"));
        // Read in the same order they were written:
        List<Class<? extends Shape>> shapeTypes =
                (List<Class<? extends Shape>>)in.readObject();
        Line.deserializeStaticState(in);
        List<Shape> shapes = (List<Shape>)in.readObject();
        System.out.println(shapes);
    }
} /* Output:
[class Circlecolor[1] xPos[58] yPos[55] dim[93]
, class Squarecolor[0] xPos[61] yPos[61] dim[29]
, class Linecolor[3] xPos[68] yPos[0] dim[22]
, class Circlecolor[1] xPos[7] yPos[88] dim[28]
, class Squarecolor[0] xPos[51] yPos[89] dim[9]
, class Linecolor[3] xPos[78] yPos[98] dim[61]
, class Circlecolor[1] xPos[20] yPos[58] dim[16]
, class Squarecolor[0] xPos[40] yPos[11] dim[22]
, class Linecolor[3] xPos[4] yPos[83] dim[6]
, class Circlecolor[1] xPos[75] yPos[10] dim[42]
]
*///:~
  • 现在把序列化后的东西都反序列化取出来,观察打印结果:
    • Circlecolor为1,这是因为Circle的color在字段定义时就已经初始化为1了。
    • Squarecolor为0,这是因为Square的color在字段定义时没有初始化,但java会给它一个初始值0。
    • Linecolor为3,这是因为你调用了deserializeStaticState,将序列化时存储的一个int值取出来了,并赋值给color静态变量了。不然Linecolor也为1。
  • 综上,几乎可以认为, 序列化时并没有存储静态变量,因为反序列化时得到的静态变量,也只是从静态变量字段定义和静态代码块得到的。
  • 但上面这个程序并没有从List<Class<? extends Shape>> shapeTypes里得到静态变量,好像有点佐证不足,所以请看下面这个程序:
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;

public class RecoverCADState {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) throws Exception {
        ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("CADState.out"));
        // Read in the same order they were written:
        List<Class<? extends Shape>> shapeTypes =
                (List<Class<? extends Shape>>)in.readObject();
        for(Class<? extends Shape> clazz: shapeTypes) {
            System.out.println(clazz.getSimpleName());
            Field field = clazz.getDeclaredField("color");
            field.setAccessible(true);
            for(Field f: clazz.getFields()) {
                System.out.print(f.getName()+" ");
            }
            System.out.println();
            System.out.println(String.format("当前Class对象的类名是%s,通过Class对象获得静态变量color为%d",
                    clazz.getSimpleName(), (Integer)field.get(clazz)));  // (Integer)field.get(clazz)通过Class对象来获得静态变量
        }

        Circle circle = new Circle(1,2,3);
        System.out.println("调用构造器后 Circle类的color静态变量为"+circle.getColor());
        Square square = new Square(1,2,3);
        System.out.println("调用构造器后 Square类的color静态变量为"+square.getColor());
        Line line = new Line(1,2,3);
        System.out.println("调用构造器后 Line类的color静态变量为"+line.getColor());

        //Line.deserializeStaticState(in);
        in.readInt();
        List<Shape> shapes = (List<Shape>)in.readObject();
        System.out.println(shapes);
    }
} /* Output:
Circle
RED BLUE GREEN 
当前Class对象的类名是Circle,通过Class对象获得静态变量color为1
Square
RED BLUE GREEN 
当前Class对象的类名是Square,通过Class对象获得静态变量color为0
Line
RED BLUE GREEN 
当前Class对象的类名是Line,通过Class对象获得静态变量color为1
调用构造器后 Circle类的color静态变量为1
调用构造器后 Square类的color静态变量为1
调用构造器后 Line类的color静态变量为1
[class Circlecolor[1] xPos[58] yPos[55] dim[93]
, class Squarecolor[1] xPos[61] yPos[61] dim[29]
, class Linecolor[1] xPos[68] yPos[0] dim[22]
, class Circlecolor[1] xPos[7] yPos[88] dim[28]
, class Squarecolor[1] xPos[51] yPos[89] dim[9]
, class Linecolor[1] xPos[78] yPos[98] dim[61]
, class Circlecolor[1] xPos[20] yPos[58] dim[16]
, class Squarecolor[1] xPos[40] yPos[11] dim[22]
, class Linecolor[1] xPos[4] yPos[83] dim[6]
, class Circlecolor[1] xPos[75] yPos[10] dim[42]
]
*///:~
  • 此程序终于使用到了List<Class<? extends Shape>> shapeTypes,并且从里面的各个Class对象获得到了静态变量color,从打印结果看,终于坐实了:序列化时不存储静态变量,反序列化时得到的静态变量只是从静态变量字段定义和静态代码块获得的,换句话说,是从本地class文件中获得的,因为静态变量字段定义和静态代码块就存储在本地class文件里。
  • 不再调用Line.deserializeStaticState(in),所以Line的静态变量也不会修改了。但必须调用in.readInt()以保证后面能正常读取。
  • 最后打印对象时,Squarecolor为1,是因为调用了它的构造器。

Serializable与深拷贝/浅拷贝

//: io/MyWorld.java
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;

class House implements Serializable {}

class Animal implements Serializable {
  private String name;
  private House preferredHouse;
  Animal(String nm, House h) {
    name = nm;
    preferredHouse = h;
  }
  public String toString() {
    return name + "[" + super.toString() +  //super.toString()为了打印出子类对象的父类对象的地址
      "], " + preferredHouse + "\n";
  }
}

public class MyWorld {
  public static void main(String[] args)
  throws IOException, ClassNotFoundException {
    House house = new House();
    List<Animal> animals = new ArrayList<Animal>();
    animals.add(new Animal("Bosco the dog", house));
    animals.add(new Animal("Ralph the hamster", house));
    animals.add(new Animal("Molly the cat", house));
    print("animals: " + animals);
    ByteArrayOutputStream buf1 =
      new ByteArrayOutputStream();
    ObjectOutputStream o1 = new ObjectOutputStream(buf1);
    o1.writeObject(animals);
    o1.writeObject(animals); // Write a 2nd set
    // Write to a different stream:
    ByteArrayOutputStream buf2 =
      new ByteArrayOutputStream();
    ObjectOutputStream o2 = new ObjectOutputStream(buf2);
    o2.writeObject(animals);
    // Now get them back:
    ObjectInputStream in1 = new ObjectInputStream(
      new ByteArrayInputStream(buf1.toByteArray()));
    ObjectInputStream in2 = new ObjectInputStream(
      new ByteArrayInputStream(buf2.toByteArray()));
    List
      animals1 = (List)in1.readObject(),
      animals2 = (List)in1.readObject(),
      animals3 = (List)in2.readObject();
    print("animals1: " + animals1);
    print("animals2: " + animals2);
    print("animals3: " + animals3);
  }
} /* Output: (Sample)
animals: [Bosco the dog[Animal@addbf1], House@42e816
, Ralph the hamster[Animal@9304b1], House@42e816
, Molly the cat[Animal@190d11], House@42e816
]
animals1: [Bosco the dog[Animal@de6f34], House@156ee8e
, Ralph the hamster[Animal@47b480], House@156ee8e
, Molly the cat[Animal@19b49e6], House@156ee8e
]
animals2: [Bosco the dog[Animal@de6f34], House@156ee8e
, Ralph the hamster[Animal@47b480], House@156ee8e
, Molly the cat[Animal@19b49e6], House@156ee8e
]
animals3: [Bosco the dog[Animal@10d448], House@e0e1c6
, Ralph the hamster[Animal@6ca1c], House@e0e1c6
, Molly the cat[Animal@1bf216a], House@e0e1c6
]
*///:~
  • 代码来自Java编程思想,我觉得很经典。
  • List中添加了三个Animal对象,三者持有的House对象引用是一样的,也就是同一个House对象。
  • 从打印结果来看,对比animals和animals1:发现animals1反序列化取出的对象,完全是不同的了,也就是说,反序列化实现了深拷贝。而且反序列化还保持了同样的关系——三个Animal对象持有同一个House对象。
  • 从打印结果来看,对比animals1和animals2:发现两个打印结果完全一样,可以说是完全的浅拷贝。原因是这二者的输出流是同一个ByteArrayOutputStream buf1,对于同一个输出流,相同对象再次写入,不会发生拷贝。
  • 从打印结果来看,对比animals2和animals3:发现这二者又是深拷贝了,因为这二者对应着两个不同的输出流ByteArrayOutputStream buf1ByteArrayOutputStream buf2,不同的输出流相当于两个隔离的空间,自然会重新生成对象。
  • 把ByteArrayOutputStream换成FileOutputStream也是一样的。

其他

  • 类的静态变量不会参与到序列化过程中,也就是说,反序列化时,静态变量总是会从本地class文件读取出来。但serialVersionUID这个静态变量是个特例,在序列化过程中输出的字节流中,将带有serialVersionUID的信息。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章