Java 克隆

1.为什么要克隆?

新new一个不好吗?new一个的状态是初始值,如果改变了某个属性值,则需要通过相同的改变操作使得new出来的对象和现对象值相同。克隆可直接复制当前对象的任何值。并且初始化可能费时间比较多。克隆有浅克隆和深克隆。


2.浅克隆

需要重写Cloneable接口中的clone()方法。

package my_test;

public class TestCopy {
    public static void main(String[] args) throws Exception{
        Monkey m1=new Monkey();
        m1.setId(66);
        Monkey m2=(Monkey) m1.clone();
        Monkey m3=(Monkey)m2.clone();
        m2.setId(99);
        Monkey m4=m1;
        System.out.println("m1.id="+m1.getId()+" getClass()="+m1.getClass());
        System.out.println("m2.id="+m2.getId()+" getClass()="+m2.getClass());
        System.out.println("m3.id="+m3.getId()+" getClass()="+m3.getClass());
        System.out.println("m3.id="+m3.getId()+" getClass()="+m4.getClass());
        System.out.println(m1==m2);
        System.out.println(m1==m4);
        System.out.println(m1);
        System.out.println(m2);
        System.out.println(m3);
        System.out.println(m4);
    }
}

class Monkey implements Cloneable {
    private int id;
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
}
View Code

输出:

m1.id=66 getClass()=class my_test.Monkey
m2.id=99 getClass()=class my_test.Monkey
m3.id=66 getClass()=class my_test.Monkey
m3.id=66 getClass()=class my_test.Monkey
false
true
my_test.Monkey@15db9742
my_test.Monkey@6d06d69c
my_test.Monkey@7852e922
my_test.Monkey@15db9742

m2是通过m1克隆的,m3是通过m2克隆的,m4和m1指向同一个堆内存,再修改m2的值,通过返回toString()方法可知。m1、m2、m3地址各不相同,m1和m4地址相同,修改m2的值,不影响m1和m3,getClass()返回的类型相同。表明通过clone()方法克隆出的对象是与原对象是独立的,开辟了新的堆内存。

在Monkey类里添加引用类型Peach进行测试

package my_test;

public class TestCopy {
    public static void main(String[] args) throws Exception{
        Peach peach=new Peach();
        peach.setName("猕猴桃");
        Monkey m1=new Monkey();
        m1.setId(66);
        m1.setP(peach);
        Monkey m2=(Monkey)m1.clone();
        System.out.println("m1.id="+m1.getId()+"  m1.p="+m1.getP()+"  p.name="+m1.getP().getName());
        System.out.println("m2.id="+m2.getId()+"  m2.p="+m2.getP()+"  p.name="+m2.getP().getName());
        m1.setId(99);
        peach.setName("水蜜桃");
        System.out.println("m1.id="+m1.getId()+"  m1.p="+m1.getP()+"  p.name="+m1.getP().getName());
        System.out.println("m2.id="+m2.getId()+"  m2.p="+m2.getP()+"  p.name="+m2.getP().getName());
    }
}

class Monkey implements Cloneable {
    private int id;
    private Peach p;
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public Object clone() throws CloneNotSupportedException{
        return super.clone();
    }
    public void setP(Peach p) {
        this.p=p;
    }
    public Peach getP() {
        return p;
    }
}

class Peach{
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

输出:

m1.id=66 m1.p=my_test.Peach@15db9742 p.name=猕猴桃
m2.id=66 m2.p=my_test.Peach@15db9742 p.name=猕猴桃
m1.id=99 m1.p=my_test.Peach@15db9742 p.name=水蜜桃
m2.id=66 m2.p=my_test.Peach@15db9742 p.name=水蜜桃

m2是m1的克隆对象,m1和m2相互独立,但是 类中的引用类peach 是同一片内存,所以peach内容一改,m1和m2中的peach都改。

通过Clone()方法实现的浅克隆

在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;

如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址

简单来说,在浅克隆中,当对象被复制时 只 复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。即复制不完全,不够深刻。


3.深克隆

要完完全全深深刻刻地全部复制,也可通过Clone()方法在浅复制中嵌套浅复制。

package my_test;

public class TestCopy {
    public static void main(String[] args) throws Exception{
        Peach peach=new Peach();
        peach.setName("猕猴桃");
        Monkey m1=new Monkey();
        m1.setId(66);
        m1.setP(peach);
        Monkey m2=(Monkey)m1.clone();
        System.out.println("m1.id="+m1.getId()+"  m1.p="+m1.getP()+"  p.name="+m1.getP().getName());
        System.out.println("m2.id="+m2.getId()+"  m2.p="+m2.getP()+"  p.name="+m2.getP().getName());
        m1.setId(99);
        peach.setName("水蜜桃");
        System.out.println("m1.id="+m1.getId()+"  m1.p="+m1.getP()+"  p.name="+m1.getP().getName());
        System.out.println("m2.id="+m2.getId()+"  m2.p="+m2.getP()+"  p.name="+m2.getP().getName());
    }
}

class Monkey implements Cloneable {
    private int id;
    private Peach p;
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    public void setP(Peach p) {
        this.p=p;
    }
    public Peach getP() {
        return p;
    }
    public Object clone() throws CloneNotSupportedException{//修改猴子类中的Clone()方法
        Monkey monkey=null;
        try {
            monkey=(Monkey)super.clone();//先实现一下浅复制
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        monkey.p=(Peach)p.Clone();//深复制 = 浅复制 套 浅复制
        return monkey;
    }
}

class Peach implements Cloneable{//桃子类也实现来实现Clone()方法
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public Object Clone() {
        Peach peach=null;
        try {
            peach = (Peach)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return peach;
    }
}

输出:

m1.id=66 m1.p=my_test.Peach@15db9742 p.name=猕猴桃
m2.id=66 m2.p=my_test.Peach@6d06d69c p.name=猕猴桃
m1.id=99 m1.p=my_test.Peach@15db9742 p.name=水蜜桃
m2.id=66 m2.p=my_test.Peach@6d06d69c p.name=猕猴桃

此时改了m1中的p,不影响m2中的p,实现了完全复制。类中类内存也相互独立。

简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

用Clone()实现克隆的问题所在:如果多层引用类型,岂不是一层套一层套到天亮,为此有其他方法(序列化)可以实现深克隆。

package my_test;

import java.io.*;

public class TestCopy {
    public static void main(String[] args) throws Exception{
        Peach peach=new Peach();
        peach.setName("猕猴桃");
        Monkey m1=new Monkey();
        Monkey m2=null;
        m1.setId(66);
        m1.setP(peach);
        try {
            m2=(Monkey)m1.deepClone();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        System.out.println("1  m1.id="+m1.getId()+"  m1.p="+m1.getP()+"  p.name="+m1.getP().getName());
        System.out.println("2  m2.id="+m2.getId()+"  m2.p="+m2.getP()+"  p.name="+m2.getP().getName());
        
        m1.setId(99);
        peach.setName("水蜜桃");
        System.out.println("3  m1.id="+m1.getId()+"  m1.p="+m1.getP()+"  p.name="+m1.getP().getName());
        System.out.println("4  m2.id="+m2.getId()+"  m2.p="+m2.getP()+"  p.name="+m2.getP().getName());
    }
}

class Monkey implements Serializable {
    private int id;
    private Peach p;
    public void setId(int id) {
        this.id = id;
    }
    public int getId() {
        return this.id;
    }
    public void setP(Peach p) {
        this.p=p;
    }
    public Peach getP() {
        return this.p;
    }
    public Object deepClone() throws IOException,ClassNotFoundException,OptionalDataException {
        //将对象写入流中
        ByteArrayOutputStream bao=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(bao);
        oos.writeObject(this);
        
        //将对象从流中取出
        ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(bis);
        return (ois.readObject());
    }
}

class Peach implements Serializable{//类中的成员类也需要 实现这个接口
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

输出:

m1.id=66 m1.p=my_test.Peach@55f96302 p.name=猕猴桃
m2.id=66 m2.p=my_test.Peach@214c265e p.name=猕猴桃
m1.id=99 m1.p=my_test.Peach@55f96302 p.name=水蜜桃
m2.id=66 m2.p=my_test.Peach@214c265e p.name=猕猴桃

通过序列化实现深克隆,成员的引用类型太多则不需要像Clone()那样实现太多方法。

实现步骤:

  1. 需要克隆的类无论是主类还是成员类都要实现Serializable接口
  2. 写一个deepClone()方法搞定一切,代码照抄,异常抛出照抄

参考:https://www.cnblogs.com/Qian123/p/5710533.html#_label0

原文地址:https://www.cnblogs.com/shoulinniao/p/11742401.html