天天看点

Java深拷贝与浅拷贝

前言

对象拷贝Object Copy是将一个对象的属性,拷贝到另一个相同类型的对象中。拷贝对象主要是为了在新的上下文环境中复用对象的部分或全部数据。其中对象拷贝有3种类型:深拷贝Deep Copy、浅拷贝Shallow Copy和延迟拷贝Lazy Copy。

浅拷贝

按位拷贝对象,它会创建一个新的对象,该对象有这原始对象属性值的备份。如果属性是基本类型,拷贝的就是基本类型的值。如果属性是内存地址(引用类型),拷贝的就是内存地址。所以,如果其中一个对象改变了这个地址,另一个对象也得“受牵连”。

Java深拷贝与浅拷贝

SourceObject有一个int类型的属性field1、一个引用属性refObj。当SourceObject做浅拷贝时,创建了CopiedObject。CopiedObject包含一个field1拷贝值的属性field2、指向refObj的引用。基本类型直接拷贝,引用类型指向相同地址。对SourceObject中的refObj做修改,CopiedObject也会改变。

实现浅拷贝

public class Subject {
    private String name;

    public Subject(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

           
public class Student implements Cloneable {

    //对象引用
    private Subject subj;
    private String name;

    public Student(String s, String sub) {
        this.subj = new Subject(sub);
        this.name = s;
    }

    public Subject getSubj() {
        return subj;
    }

    public void setSubj(Subject subj) {
        this.subj = subj;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    //重写clone方法
    public Object clone(){
        //浅拷贝
        try {
            //直接调用父类clone方法
            return super.clone();
        }catch (CloneNotSupportedException e){
            return null;
        }
    }
}

           
public class CopyTest {
    public static void main(String []args){

        //原始对象
        Student student = new Student("husky", "labrador");
        System.out.println(student.getName()+"---"+student.getSubj().getName());

        //拷贝对象
        Student cloneStudent = (Student) student.clone();
        System.out.println(cloneStudent.getName()+"---"+cloneStudent.getSubj().getName());

        //原始对象和拷贝对象是否一样
        System.out.println("两者是否一样:"+(student==cloneStudent));

        //原始对象和拷贝对象的name属性是否一致?
        System.out.println("name是否一致:"+(student.getName()==cloneStudent.getName()));

        //原始对象和拷贝对象的subj是否一致?
        System.out.println("subj属性:"+(student.getSubj()==cloneStudent.getSubj()));

        student.setName("大G");
        student.getSubj().setName("狂风桑");

        System.out.println("修改后的name:"+student.getName()+"<>"+student.getSubj().getName());
        System.out.println("修改后的subj"+cloneStudent.getName()+"<>"+cloneStudent.getSubj().getName());

    }
}
           

输出结果:

husky---labrador
husky---labrador
两者是否一样:false
name是否一致:true
subj属性:true
修改后的name:大G<>狂风桑
修改后的subjhusky<>狂风桑
           

使拷贝的Student类实现Clonable接口并重写clone方法,方法内调用super.clone()。从输出结果看到,对原始对象的name改变并不影响拷贝对象。但是引用对象subj的name改变,拷贝对象的也被牵连。

深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它引用的对象一起拷贝时就会发生深拷贝。深拷贝较浅拷贝而言,速度慢花销大。

Java深拷贝与浅拷贝

SourceObject有一个int类型的field1属性和一个引用类型属性refObj1,当做深拷贝时,创建了CopiedObject,包含一个field1拷贝值的属性field2和refObj1拷贝值的引用类型属性refObj2。对SourceObject中的refObj做的改变,不会影响到CopiedObject

实现深拷贝

public class Subject {
    private String name;

    public Subject(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
           
public class Student implements Cloneable {

    //对象引用
    private Subject subj;
    private String name;

    public Student(String s, String sub) {
        this.subj = new Subject(sub);
        this.name = s;
    }

    public Subject getSubj() {
        return subj;
    }

    public void setSubj(Subject subj) {
        this.subj = subj;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    //重写clone方法
    public Object clone(){
        //浅拷贝
        Student student = new Student(name, subj.getName());
        return student;
    }
}
           
public class CopyTest {
    public static void main(String []args){

        //原始对象
        Student student = new Student("husky", "labrador");
        System.out.println(student.getName()+"---"+student.getSubj().getName());

        //拷贝对象
        Student cloneStudent = (Student) student.clone();
        System.out.println(cloneStudent.getName()+"---"+cloneStudent.getSubj().getName());

        //原始对象和拷贝对象是否一样
        System.out.println("两者是否一样:"+(student==cloneStudent));

        //原始对象和拷贝对象的name属性是否一致?
        System.out.println("name是否一致:"+(student.getName()==cloneStudent.getName()));

        //原始对象和拷贝对象的subj是否一致?
        System.out.println("subj属性:"+(student.getSubj()==cloneStudent.getSubj()));

        student.setName("大G");
        student.getSubj().setName("狂风桑");

        System.out.println("修改后的name:"+student.getName()+"<>"+student.getSubj().getName());
        System.out.println("修改后的subj:"+cloneStudent.getName()+"<>"+cloneStudent.getSubj().getName());

    }
}
           

输出结果:

husky---labrador
husky---labrador
两者是否一样:false
name是否一致:true
subj属性:false
修改后的name:大G<>狂风桑
修改后的subj:husky<>labrador
           

通过序列化实现深拷贝

序列化就是将整个对象图写入到一个持久化存储文件中,并在需要的时候读取回来。读取回来时,意味着我需要整个对象图的一个拷贝。而且,通过序列化做深拷贝时,需要确定整个对象图中的所有类都是可序列化的。

public class ColoredCircle implements Serializable {
    private int x;
    private int y;

    public ColoredCircle(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

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

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "x:"+x+";y:"+y;
    }
}
           
public class DeepCopy {
    public static void main(String []args) throws IOException{
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;

        try {
            //创建原始的可序列化对象
            ColoredCircle c1 = new ColoredCircle(100, 100);
            System.out.println("原始可序列化对象c1:"+c1);

            ColoredCircle c2 = null;
            //通过序列化实现深拷贝
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);

            //序列化及传递这个对象
            oos.writeObject(c1);
            oos.flush();

            ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bin);
            //返回新的对象
            c2 = (ColoredCircle)ois.readObject();

            //检验内容是否相同
            System.out.println("copy:"+c2);

            //改变原始对象内容
            c1.setX(200);
            c1.setY(200);
            //查看每一个现在的内容
            System.out.println("现在的c1:"+c1);
            System.out.println("现在的c2:"+c2);
        }catch (Exception e){
            System.out.println("异常:"+e);
        }finally {
            oos.close();
            ois.close();
        }
    }
}
           

输出结果:

原始可序列化对象c1:x:100;y:100
copy:x:100;y:100
现在的c1:x:200;y:200
现在的c2:x:100;y:100
           

这里要先确认:

1.对象图中所有的类都是可序列化的

2.创建输入、输出流

3.使用这个输入、输出流来创建对象输入流、对象输出流

4.将要拷贝的对象传递给对象输出流

5.从对象输入流中读取新的对象,并转换回你所发送的对象的类

问题:

1.无法序列化transient变量

2.创建一个socket,序列化一个对象,通过socket传输,反序列化,过程很慢。

延迟拷贝

延迟拷贝是深拷贝与浅拷贝的组合,先使用速度快的浅拷贝,使用一个计数器来记录有多少对象来共享这个数据。当程序想要修改原始对象时,它会通过检查计数器决定数据是否被共享,并根据需要进行深拷贝。