Java 序列化浅析

Java序列化是JVM的内部机制,是一种高效的对象复制机制。序列化用于在不同的JVM之间传递状态的信息,实现跨平台的Java对象传输,用于RMI的实现,也可用于保存JVM中运行对象的状态。序列化只是序列化类中非static,非transient的对象,对于方法,或使用static、transient修饰的变量则跳过序列化。需要序列化的类需要实现Serializable或Externalizable。Serializable是标记接口,实现标记接口不需要实现任何方法,只要类“标记”了该接口,就具有对象序列化的能力,但实现了Serializable接口的类不具备控制序列化过程的能力。实现Externalizable接口使得用户可以手动控制类中哪些对象需要序列化,哪些不需要。

浅复制和深复制

浅复制使用Object类中的clone方法。对于基本数据类型,clone方法是复制数据,对于引用数据类型,clone方法则是复制指针。如果一个类中存在引用类型,同时,希望clone也复制该引用类型的数据,需要类中的clone方法。对于引用类的处理,前一种就是浅复制,后一种就是clone的深复制。另一种实线深复制的方式就是序列化,只要引用类型本身是可以序列化的,实现深复制是非常方便且高效的

下面是一个浅复制基本数组的实验:

public class CloneTest {
    private static void printArray(int[] a) {
        for(int i = 0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
        System.out.println();
    }
    public static void main(String args[]) {
        int[] a = new int[]{1,2,3,4,5};
        int[] b = a.clone();
        /*修改数组a的值*/
        a[0] = 2;
        a[1] = 4;
        a[2] = 6;
        a[3] = 8;
        a[4] = 10;
        System.out.println("Array b is ");
        printArray(b);
        System.out.println("Array a is ");
        printArray(a);
        //System.out.println(a==b);
    }
}

实验的输出结果为:

Array b is
1 2 3 4 5
Array a is
2 4 6 8 10

对于引用类型,可以被clone的类都实现了Cloneable接口,并且实现了public Object clone() throws CloneNotSupportedException的方法(String类是特殊的,本身并没有实现该方法)。
类Student

public class Student{
    private String id;
    private String name;
    private int classno;

    public Student (String id , String name , int classno) {
        this.id = id;
        this.name = name;
        this.classno = classno;
    }

    public String getID() {
        return this.id;
    }

    public String toString() {
        return this.id+" "+this.name+" "+this.classno;
    }

    public void setName(String name) {
        this.name = name;
    }    
}

调用和测试类:

public class BaseClass implements Cloneable {
    Student stu;
    int x;
    public BaseClass(Student stu , int x) {
        this.stu = stu;
        this.x = x;
    }

    public BaseClass(){
    }

    public void setStu(Student stu) {
        this.stu = stu;
    }

    public void setX(int x) {
        this.x = x;
    }

    public Object clone() throws CloneNotSupportedException {
        BaseClass bc = (BaseClass)super.clone();
        return bc;
    }

    public String toString() {
        return stu.toString()+" "+x;
    }

    public static void main(String args[]) throws CloneNotSupportedException {
        Student stu = new Student("1001","Prin",100);
        BaseClass bc = new BaseClass(stu,1);
        BaseClass bc2 = (BaseClass) bc.clone();
        System.out.println(bc2);
        /*修改stu对象的name属性,测试bc和bc2是否都指向stu*/
        stu.setName("Aline");
        System.out.println(bc2);       
    }
}

运行结果为:

1001 Prin 100 1
1001 Aline 100 1

String类没有可用的clone方法,当对象中的String属性拷贝时,直接是新建一个String对象,并把内容复制过去。当单对象是其他引用类型时,浅复制仅仅复制引用。如果需要复制引用类型,则需要让Student也实现Cloneable方法,并且修改BaseClass的clone实现。具体如下
Student类:

public class Student implements Cloneable{
......
    public Object clone() throws CloneNotSupportedException {
        Student stu = (Student) super.clone();
        return stu;
    }
......
}

BaseClass类:

public class BaseClass implements Cloneable {
    public Object clone() throws CloneNotSupportedException {
        BaseClass bc = (BaseClass)super.clone();
       /*修改bc中的stu内容,使用Student的clone*/
        bc.stu = (Student) stu.clone();
        return bc;
    }
}

再一次运行BaseClass的main,其结果为:

1001 Prin 100 1
1001 Prin 100 1

实现了Student的深复制。

使用Serializable来实现深复制,则Student和BaseClass都需要实现Serializable接口。
修改Student类的class声明为:public class Student implements Serializable
修改BaseClass类的class声明为:public class BaseClass implements Serializable
测试深复制的代码main方法如下:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import newenv.Student;
/** * * @author Philis.Liu */
public class BaseClass implements Serializable {
......
    public static void main(String args[]) throws CloneNotSupportedException {
        /*将变量放在try-catch-finally之外*/
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        BaseClass bc = null;
        BaseClass bc2 = null;       
        try {
            /*初始化变量*/
            Student stu = new Student("1001","Prin",100);
            bc = new BaseClass(stu,1);
            /*序列化变量到磁盘文件*/
            oos = new ObjectOutputStream(new FileOutputStream("base.txt"));
            oos.writeObject(bc);
            /*将变量考出磁盘至内存*/
            ois = new ObjectInputStream(new FileInputStream("base.txt"));
            bc2 = (BaseClass)ois.readObject();  
            /*修改拷贝前的变量状态值*/
            stu.setName("Aline");
        } catch (IOException ex) {
            Logger.getLogger(BaseClass.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(BaseClass.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
            /*关流*/
                oos.close();
                ois.close();
            } catch (IOException ex) {
                Logger.getLogger(BaseClass.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
            /*输出测试*/
            System.out.println(bc);            
            System.out.println(bc2);
    }
}

测试结果为:
1001 Aline 100 1
1001 Prin 100 1
深复制成功!

继承类的序列化

对于继承基类的类,最好是基类和继承类都实现了序列化,这样使用继承类类序列化基类时就不会出现变量丢失的问题。如果基类没有实现序列化,而基层类实现了序列化,那么,就需要在子类中重写writeObject和readObject方法,将基类的变量一一序列化。同时,父类需要有一个public或protected的无参构造方法。因为ObjectInputStream在初始化非Serializable的类型时,将调用该类的无参构造方法来生成一个对象,再对该对象进行赋值。
以下是没有Serializable的基类代码:

public class BaseClass {
    Student stu;
    int x;
    public BaseClass(Student stu , int x) {
        this.stu = stu;
        this.x = x;
    }

    public BaseClass(){
    }

    public void setStu(Student stu) {
        this.stu = stu;
    }

    public void setX(int x) {
        this.x = x;
    }

    public String toString() {
        return stu.toString()+" "+x;
    }

以下是扩展类的代码(实现了Serializable)

public class ExtendedClass extends BaseClass implements Serializable{
    String str;
    public ExtendedClass(Student stu, int x, String str) {
        super(stu, x);
        this.str = str;
    }

     private void writeObject(ObjectOutputStream oos) throws IOException {
         oos.defaultWriteObject();
         oos.writeObject(this.stu);
         oos.writeInt(this.x);
     }

     private void readObject(ObjectInputStream ois) throws  IOException, ClassNotFoundException {
         ois.defaultReadObject();
         Student stu = (Student)ois.readObject();
         int x = ois.readInt();
         super.setStu(stu);
         super.setX(x);
     }
}

以下是简单测试的代码。

public class SerialTest {
    public static void main(String args[]) {
        Student stu = new Student("1001","Prin",301);
        ExtendedClass bc = new ExtendedClass(stu,100,"student");
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("base.txt"));
            oos.writeObject(bc);
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("base.txt"));
            ExtendedClass bc2 = (ExtendedClass)ois.readObject();
            System.out.println(bc2.str);
            System.out.println(bc2.stu);
        } catch (IOException ex) {
            Logger.getLogger(SerialTest.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(SerialTest.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

测试结果为:
student
1001 Prin 301
复制成功!

readResolve和writeReplace

readResolve用于在反序列化过程中,用一个新的对象代替反序列化的对象;writeReplace用于在序列化过程中,用一个对象代替本来序列化的对象。这两个方法的使用场景之一就是深复制过程中单例模式的保持。实现了Serializable接口的单利模式虽然能够成功复制实例,但却违反了单例模式只能有一个实例的原则,为此,需要在类中添加readResolve和writeReplace的实现。

private Object writeReplace() throws ObjectStreamException {
    return this.instance;
}

private Object readResolve() throws ObjectStreamException {
    return this.instance;
}

控制类的序列化

如果想要让某些变量以某种规则进行序列化,或者控制某些变量不进行序列化。如果是Serializable实现的类,则可以通过transient或static先让默认的序列化规则跳过序列化这些变量,再通过write方法对规则处理后的值进行手工序列化。另一种办法是实现Externalizable接口,该方式可以实现对序列化过程的完全控制。
以下是Externalizable的简单实验

public class ExtSerialClass implements Externalizable{
    private static final long serialVersionUID = 2L;
    private String str ;
    private int num;
    /*必须是public,必须是无参*/
    public ExtSerialClass() {    
        System.out.println("Loading No-Arg Constructor");
    }
    public ExtSerialClass(String str , int num) {
        this.str = str;
        this.num = num;
        System.out.println("Load Arg Constructor");
    }

    public String getStr() {
        return str;
    }

    public int getNum() {
        return num;
    }

    public void setStr(String str) {
        this.str = str;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public String toString(){
        return this.str + this.num;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Carring Write External");
        out.writeObject(str);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("Carring Read External");
        this.str = (String)in.readObject();
    }

    public static void main(String args[]) {
        ExtSerialClass esc = new ExtSerialClass("Prin",100);
        System.out.println(esc);
        try {
            FileOutputStream ofs = new FileOutputStream("t1");
            ObjectOutputStream oos = new ObjectOutputStream(ofs);
            oos.writeObject(esc);
            FileInputStream fis = new FileInputStream("t1");
            ObjectInputStream ois = new ObjectInputStream(fis);
            ExtSerialClass esc2 = (ExtSerialClass) (ois.readObject());
            //if(esc2==null) System.out.println("Read Fail");
            System.out.println(esc2);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(ExtSerialClass.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ExtSerialClass.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(ExtSerialClass.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

测试结果为:
Load Arg Constructor
Prin 100
Carring Write External
Loading No-Arg Constructor
Carring Read External
Prin 0
前一个的num为100,后一个为0,可见,实现了num变量的序列化控制。

你可能感兴趣的:(java,jvm)