Java克隆与对象序列化

Object类的clone()方法

浅克隆

//浅克隆
/*
* 1.Object类中的clone()方法为protected的
* 2.需要实现克隆功能的类必须实现Cloneable接口
* */
class Kid{
    public int age = 10;
}
class Person implements Cloneable{

    public int age = 10;
    public Kid kid = new Kid();

    protected Person clone(){
        try {
            return (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}
        Person p = new Person();
        Person clonep = p.clone();
        System.out.println("p.age:"+p.age+" p.kid.age:"+p.kid.age);
        System.out.println("clonep.age:"+clonep.age+" clonep.kid.age:"+clonep.kid.age);
/*Output:
p.age:10 p.kid.age:10
clonep.age:10 clonep.kid.age:10*/

        p.age = 15;
        p.kid.age=15;
        System.out.println("clonep.age:"+clonep.age+" clonep.kid.age:"+clonep.kid.age);
/*
Output:
clonep.age:10 clonep.kid.age:15

测试代码中我克隆了Person对象实例p,将Person的另一个引用clonep指向克隆后的对象。然后我先打印p和clonep的age值与其中引用对象kid的age值。随后我修改p中两者的值为15。再通过打印clonep的两者的值可以发现clonep中kid对象的age值变成了15。

这种克隆就叫做浅克隆,它可以克隆对象时,会自动克隆作用域中的基础类型(包含String类型,因为String类是不可变类,它在克隆时会自动出现拷贝一份对象)。

但是它却无法克隆引用对象,因为每一个对象在Java虚拟机中都是独立的(成员对象的类型没有实现Cloneable接口,而且Object类的clone()方法时受保护的,在克隆对象中并不能调用其成员对象的clone()方法)。

所以此时克隆出来的对象会共享其成员对象,实际上成员对象并没有发生克隆。

 

深克隆

//深拷贝

/*
*1.为每一个成员对象的类实现Cloneable接口
*2.每一个存在与可克隆类的成员对象的类都需要重写相应的clone()方法
*3.clone()方法的修饰符要修改为Public
* */
class Kid implements Cloneable{
    public int age = 10;
    
    public Kid clone(){
        try {
            return (Kid)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}
class Person implements Cloneable{

    public int age = 10;
    public Kid kid = new Kid();

    public Person clone(){
        try {
/*
* 之所以将clone()方法修饰符改为public就是为了能在此处调用
* 由此处代码可以知道我们可以选择进行克隆的域
* */
            Person clonep = (Person) super.clone();
            clonep.kid = clonep.kid.clone();
            return clonep;

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}
        Person p = new Person();
        Person clonep = p.clone();
        System.out.println("p.age:"+p.age+" p.kid.age:"+p.kid.age);
        System.out.println("clonep.age:"+clonep.age+" clonep.kid.age:"+clonep.kid.age);
/*Output:
p.age:10 p.kid.age:10
clonep.age:10 clonep.kid.age:10*/

        p.age = 15;
        p.kid.age=15;
        System.out.println("clonep.age:"+clonep.age+" clonep.kid.age:"+clonep.kid.age);
/*
Output:
clonep.age:10 clonep.kid.age:10
*/

这时再执行之前的测试代码,发现当我改变p中基础类型和成员对象的值时,clonep中对应的值没有发生改变,这就实现了深克隆。

 


对象序列化实现深克隆

 

使用Serializable接口

利用序列化机制实现深克隆相比较重写clone()方法来说要安全、简单,但是效率不高。因为clone()方法实现的克隆是利用的本地方法,效率比基于Java虚拟机规范的序列化机制要高很多。

/*序列化*/
/*
* 1.对象实现序列化,其和成员对象类都需要实现Serializable接口
* 2.利用ObjectInputStream和ObjectOutputStream实现对象的反序列化与序列化
* */
class Kid implements Serializable {
    public int age = 10;

    public Kid(){
        System.out.println("Kid Constructor");
    }
}
class Person implements Serializable{
    public int age = 10;
    public Kid kid = new Kid();

    public Person(){
        System.out.println("Person Constructor");
    }
}
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("456.txt"));
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("456.txt"));
        System.out.println("--------------------");
        Person p = new Person();
        System.out.println("--------------------");
        out.writeObject(p);
        Person clonep = (Person)in.readObject();
        System.out.println("--------------------");
/*
* Output:
--------------------
Kid Constructor
Person Constructor
--------------------
--------------------
* */
        System.out.println("p.age:"+p.age+" p.kid.age:"+p.kid.age);
        System.out.println("clonep.age:"+clonep.age+" clonep.kid.age:"+clonep.kid.age);
/*Output:
p.age:10 p.kid.age:10
clonep.age:10 clonep.kid.age:10
*/
        p.age = 15;
        p.kid.age=15;
        System.out.println("p.age:"+p.age+" p.kid.age:"+p.kid.age);
        System.out.println("clonep.age:"+clonep.age+" clonep.kid.age:"+clonep.kid.age);
/*Output:
p.age:15 p.kid.age:15
clonep.age:10 clonep.kid.age:10
* */

使用Serializable接口序列化显示是深克隆,但是读者可以注意到,Person类的构造函数只在实例化p时调用了,在克隆对象时并没有发生构造器的调用。

 

使用Externalizable接口

Externalizable接口实现了Serializable接口,并且声明了writeExternal(ObjectOutput out)和readExternal(ObjectInput in)两个方法。利用这两个方法我们可以选择性的克隆对象的域。

class Kid implements Externalizable {
    /*
    * 如果未在writeExternal()和readExternal()要求写入读取相应域,
    * 那么对应的域将为原始值(如下面age的值10)或者该类型的初始值。
    * */
    public int age = 10;
    public Kid(){
        System.out.println("Kid Constructor");
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Kid writerExternal");
//        out.writeInt(age);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("Kid readExternal");
//          age = in.readInt();
    }
}
class Person implements Externalizable{
    /*
    * 如果未在writeExternal()和readExternal()要求写入读取age域
    * 这里克隆后的age值将为int型的初始值0。
    * */
    public int age;
    public Kid kid = new Kid();
    public Person(){
        System.out.println("Person Constrcutor");
    }
    public void setAge(int age){
        this.age = age;
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("Person writerExternal");
//        out.writeInt(age);
//        out.writeObject(kid);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        System.out.println("Person readExternal");
//        age = in.readInt();
//        kid = (Kid)in.readObject();
    }
}
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("456.txt"));
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("456.txt"));
        System.out.println("---------------");
        Person p = new Person();
        System.out.println("---------------");
        p.setAge(10);
        out.writeObject(p);
        Person clonep = (Person)in.readObject();
        System.out.println("---------------");
        System.out.println(clonep.age);
/*
* Output:
---------------
Kid Constructor
Person Constrcutor
---------------
Person writerExternal
Kid Constructor
Person Constrcutor
Person readExternal
---------------
0
* */

/*
*去掉代码中的行注释后Output:
---------------
Kid Constructor
Person Constrcutor
---------------
Person writerExternal
Kid writerExternal
Kid Constructor
Person Constrcutor
Person readExternal
Kid Constructor
Kid readExternal
---------------
10
**/

 

这里我们输出clonep.age的值为int类型的初始值0,说明没有对其克隆没有成功。如果我们把代码中行注释去掉,那么clonep.age的值会变为10,这时克隆行为就发生了。所以利用Externalizable接口我们可以选择控制克隆的对象域。

相比较Serializable接口,使用Externalizable接口会在克隆对象时调用所有默认的构造函数,也许你担心这会导致初始化值会使克隆操作发生错误,其实不需要担心,因为从结果可以看到 readExternal()方法是在构造方法后才调用的。

 

使用transient(瞬时)关键字

class Kid implements Serializable {
    public int age = 10;

}

class Person implements Serializable{
/*
* 瞬时关键字可以用在不想被序列化的域上
* */
    public transient int age = 10;
    public Kid kid = new Kid();

}
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("456.txt"));
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("456.txt"));
        Person p = new Person();
        out.writeObject(p);
        Person clonep = (Person)in.readObject();
        System.out.println("p.age:"+p.age+" p.kid.age:"+p.kid.age);
        System.out.println("clonep.age:"+clonep.age+" clonep.kid.age:"+clonep.kid.age);
/*Output:
p.age:10 p.kid.age:10
clonep.age:0 clonep.kid.age:10
*/

由测试代码结果可知,transient修饰的域并没有被克隆,而是使用了其类型的初始值。

 

添加私有的readObject()和writeObject()方法

class Kid implements Serializable {
    public int age = 10;
    private void writeObject(ObjectOutputStream out)throws IOException{
        out.defaultWriteObject();
    }
    private void readObject(ObjectInputStream in)throws IOException,ClassNotFoundException{
        in.defaultReadObject();
    }
}

class Person implements Serializable{
/*
* 使用私有的writeObject和readObject会代替默认的序列化输入输出。
* 但是在添加方法时方法签名一定要一致,
* 即返回值要是void,修饰为private,参数为ObjectOutputStream或ObjectInputStream
* */
    public transient int age = 10;
    public int nianji = 6;
    public Kid kid = new Kid();
    private void writeObject(ObjectOutputStream out)throws IOException{
        out.defaultWriteObject();
//      out.writeInt(age);
    }
    private void readObject(ObjectInputStream in)throws IOException,ClassNotFoundException{
       in.defaultReadObject();
//     age = in.readInt();
    }

}
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("456.txt"));
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("456.txt"));
        Person p = new Person();
        out.writeObject(p);
        Person clonep = (Person)in.readObject();
        System.out.println("clonep.age:"+clonep.age+" clonep.nianji:"+clonep.nianji);

/*Output:
clonep.age:0  clonep.nianji:6
*/

/*
*去掉代码中的行注释后Output:
clonep.age:10  clonep.nianji:6
* */

添加私有的readObject()和writeObject()方法还可以序列化被transient修饰过的域。

posted @ 2018-12-15 14:07  问月晚安  阅读(609)  评论(0编辑  收藏  举报