java中的浅拷贝(浅复制)和深拷贝(深复制)

 2022-09-16
原文地址:https://blog.csdn.net/adparking/article/details/117225722

浅拷贝:
浅拷贝又称为浅复制,浅克隆,浅拷贝是指拷贝时只拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用所指向的对象,拷贝出来的对象的所有变量的值都含有与原来对象相同的值,而所有对其他对象的引用都指向原来的对象,简单地说,浅拷贝只拷贝对象不拷贝引用。

深拷贝:
深拷贝又称为深复制,深克隆,深拷贝不仅拷贝对象本身,而且还拷贝对象包含的引用所指向的对象,拷贝出来的对象的所有变量(不包含那些引用其他对象的变量)的值都含有与原来对象的相同的值,那些引用其他对象的变量将指向新复制出来的新对象,而不指向原来的对象,简单地说,深拷贝不仅拷贝对象,而且还拷贝对象包含的引用所指向的对象。

再简单的说就是浅拷贝只拷贝对象,不拷贝引用。深拷贝对象引用全拷贝

java中常用的拷贝操作有三种:(1)操作符= (2)拷贝构造函数 (3)clone( )方法,由于java不支持运算符重载,所以我们不能在自己定义的类中定级操作符=操作。拷贝构造函数就不多说了,我们经常遇到。主要说一下clone方法,如果我们想要使自己定义的对象能够实现深拷贝,就需要改写从Object类中继承来的clone方法,clone方法在Object类中是protected权限,是为了防止意外的支持clone操作,所以我们需要把它改写成public权限的

看如下代码:

    public class CloneTest{
        public static void main(String[] args) {
            People p = new People("xiaowang",10);
            Employee employee = new Employee("zhangsan", 20,p);
            Employee newEmployee = (Employee)employee.clone();
            newEmployee.p.name = "lisi";
            newEmployee.p.age = 30;
            System.out.println("employee.p.name="+employee.p.name+"
                     "+"employee.p.age="+employee.p.age);
            System.out.println("newEmployee.p.name="+newEmployee.p.name+"
                     "+"newEmployee.p.age="+newEmployee.p.age);
        }
    }
    
    class People
    {
        String name;
        int age;
        People(String name,int age)
        {
            this.name = name;
            this.age = age;
        }
    }
    class Employee implements Cloneable
    {
        String name;
        int age;
        People p;
    
        Employee(String name,int age,People p)
        {
            this.name = name;
            this.age = age;
            this.p = p;
        }
    
        public Object clone()
        {
            Employee obj = null;
            try
            {
                obj = (Employee)super.clone();  //Object中需要识别你要克隆的对象
            } catch (CloneNotSupportedException e) 
            {
                System.out.println(e.toString());
            }
            return obj;
        }
    }

运行结果为:
employee.p.name=lisi employee.p.age=30
newEmployee.p.name=lisi newEmployee.p.age=30

上述代码如果运行以后,会改变原来对象的employee.p.name和”employee.p.age,因为这是浅拷贝,只是拷贝了对象,而引用还是以前对象的引用,所以更改了现在对象的引用元对象的引用也是会改变
再来看一下深拷贝

    public class CloneTest {
        public static void main(String[] args) {
            People p = new People("xiaowang",10);
            Employee employee = new Employee("zhangsan", 20,p);
            Employee newEmployee = (Employee)employee.clone();
            newEmployee.p.name = "lisi";
            newEmployee.p.age = 30;
            System.out.println("employee.p.name="+employee.p.name+" "+"employee.p.age="+employee.p.age);
            System.out.println("newEmployee.p.name="+newEmployee.p.name+" "+"newEmployee.p.age="+newEmployee.p.age);
        }
    }
    
    class People implements Cloneable
    {
        String name;
        int age;
        People(String name,int age)
        {
            this.name = name;
            this.age = age;
        }
    
        public Object clone()
        {
            Object obj = null;
            try
            {
                obj = super.clone();
            } catch (CloneNotSupportedException e) 
            {
                System.out.println(e.toString());
            }
            return obj;
        }
    }
    
    class Employee implements Cloneable
    {
        String name;
        int age;
        People p;
    
        Employee(String name,int age,People p)
        {
            this.name = name;
            this.age = age;
            this.p = p;
        }
    
        public Object clone()
        {
            Employee obj = null;
            try
            {
                obj = (Employee)super.clone();  //Object中需要识别你要克隆的对象
            } catch (CloneNotSupportedException e) 
            {
                System.out.println(e.toString());
            }
            obj.p = (People)p.clone();
            return obj;
        }
    }

运行结果为:
employee.p.name=xiaowang employee.p.age=10
newEmployee.p.name=lisi newEmployee.p.age=30

现在我们改进成了深拷贝,就会发现不会改变原来对象的employee.p.name和”employee.p.age,说明我们深拷贝拷贝了对象和对象的引用

java里面还有一种实现深拷贝的方法,就是运用对象的序列化机制,记得我是在java核心技术卷第九版卷二第一章看到的,很有趣。
序列化机制有一种很有趣的用法:提供了一种克隆对象的简便途径,只要对应的类是可序列化的就可以,做法是:直接将对象序列化到输出流中,然后将其读回,这样产生的新的对象是对现有对象的一个深拷贝。在此过程中,我们不必将对象写到文件中,因为可以用ByteArrayOutputStream将数据保存到字节数组中。
示例代码:

    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    import java.util.Date;
    import java.util.GregorianCalendar;
    
    public class SerialCloneTest {
        public static void main(String[] args)
        {
            Employee harry = new Employee("Harry Hacker",35000,1929,10,1);
            Employee harry2 = (Employee)harry.clone();
            harry.raiseSalary(10);
            System.out.println(harry);
            System.out.println(harry2);
        }
    }
    class SerialCloneable implements Cloneable,Serializable
    {
        public Object clone()
        {
            try 
            {
                ByteArrayOutputStream bout = new ByteArrayOutputStream();
                ObjectOutputStream out = new ObjectOutputStream(bout);
                out.writeObject(this);
                out.close();
    
                ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
                ObjectInputStream in = new ObjectInputStream(bin);
                Object ret = in.readObject();
                in.close();
                return ret;
            } catch (Exception e)
            {
                return null;
            }
        }
    }
    
    class Employee extends SerialCloneable
    {
        private String name;
        private double salary;
        private Date hireDay;
    
        public Employee(String n,double s,int year,int month,int day)
        {
            name = n;
            salary = s;
            GregorianCalendar calendar = new GregorianCalendar(year,month - 1,day);
            hireDay = calendar.getTime();
        }
        public String getName() {
            return name;
        }
        public double getSalary() {
            return salary;
        }
        public Date getHireDay() {
            return hireDay;
        }
        public void raiseSalary(double byPercent)
        {
            double raise = salary * byPercent / 100;
        }
    
        public String toString()
        {
            return getClass().getName()+"[name="+name+",salary="+salary+",hireDay="+hireDay+"]";
        }
    }

上面的示例代码是自己照着书敲得,敲一遍也明白了点,不过我们应该当心用这种方法,尽管它很灵巧,但是它通常会比显示的构建新的对象并复制或克隆数据域的克隆方法慢很多