javaSE

直接赋值、浅拷贝和深拷贝

2021-12-28  本文已影响0人  virtual灬zzZ

直接赋值

平时最常用的方式,在代码中的体现是Persona = new Person();Person b = a,是一种简单明了的方式,但是它只是拷贝了对象引用地址而已,并没有在内存中生成新的对象,只是一个地址两个别名罢了,改变一个,另一个都会改变。


public class DirectAssign {
    public static void main(String[] args) {
        DirectTeacher t1 = new DirectTeacher("Chinese", 3, 5);
        DirectTeacher t2 = t1;
        System.out.println("t1 : " + t1 + " , t2 : " + t2);

        t2.setFloor(4);
        t2.setLevel(2);
        t2.setSubject("Math");
        System.out.println("t1 : " + t1 + " , t2 : " + t2);
    }
    /*
     * t1 : DirectTeacher{subject='Chinese', level=3, floor=5} , t2 : DirectTeacher{subject='Chinese', level=3, floor=5}
     * t1 : DirectTeacher{subject='Math', level=2, floor=4} , t2 : DirectTeacher{subject='Math', level=2, floor=4}
     */
}

class DirectTeacher {
    private String subject;
    private Integer level;
    private int floor;

    public DirectTeacher(String subject, Integer level, int floor) {
        this.subject = subject;
        this.level = level;
        this.floor = floor;
    }

getter、setter略

浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。


实现浅拷贝,只需要实现cloneable接口并且重写clone方法,clone方法是属于object类的,况且它是protected修饰的,不能直接调用的,所以需要重写,clone还需要靠实现cloneable接口调用底层的,还有一点就是,验证深浅拷贝,需要一个可变对象,用不可变对象是验证不出来的。看下面例子,注意,数组对象是可变对象来的。

public class ShallowDeepCopy {
    /**
     * 之所以定义那么多class,因为需要重写clone方法,原本的Object对象的clone方法是protected的,不能直接调用的。
     */
    public static void main(String[] args) throws CloneNotSupportedException {
        ShallowStudent ss1 = new ShallowStudent("lily", 12, 155, new int[]{50, 60, 70});
        //这是浅拷贝
        ShallowStudent ss2 = (ShallowStudent) ss1.clone();
        System.out.println("ss1: " + ss1 + ",detail: " + ss1.showShallowStudentDetail() + ",ss1 hashcode: " + ss1.hashCode());
        System.out.println("ss2: " + ss2 + ",detail: " + ss2.showShallowStudentDetail() + ",ss2 hashcode: " + ss2.hashCode());
        System.out.println("ss1.height == ss2.height? " + (ss1.getHeight() == ss2.getHeight()));
        System.out.println("ss1.age == ss2.age? " + (ss1.getAge() == ss2.getAge()));
        System.out.println("ss1.name == ss2.name? " + (ss1.getName() == ss2.getName()));
        System.out.println("ss1.cmeScores == ss2.cmeScores? " + (ss1.getCmeScores() == ss2.getCmeScores()));

        System.out.println("change ss1 age = 15 , name = orion , height = 162 , cmeScores[0] = 22:");
        ss1.setAge(15);
        ss1.setName("orion");
        ss1.setHeight(162);
        ss1.getCmeScores()[0] = 22;
        System.out.println("ss1: " + ss1 + ",detail: " + ss1.showShallowStudentDetail() + ",ss1 hashcode: " + ss1.hashCode());
        System.out.println("ss2: " + ss2 + ",detail: " + ss2.showShallowStudentDetail() + ",ss2 hashcode: " + ss2.hashCode());
        /**
         *
         * 结果如下:
         ss1: com.orion.ShallowStudent@28d93b30,detail: ShallowStudent{name='lily', cmeScores=[50, 60, 70], age=12, height=155},ss1 hashcode: 685325104
         ss2: com.orion.ShallowStudent@1b6d3586,detail: ShallowStudent{name='lily', cmeScores=[50, 60, 70], age=12, height=155},ss2 hashcode: 460141958
         ss1.height == ss2.height? true
         ss1.age == ss2.age? true
         ss1.name == ss2.name? true
         ss1.cmeScores == ss2.cmeScores? true
         change ss1 age = 15 , name = orion , height = 162 , cmeScores[0] = 22:
         ss1: com.orion.ShallowStudent@28d93b30,detail: ShallowStudent{name='orion', cmeScores=[22, 60, 70], age=15, height=162},ss1 hashcode: 685325104
         ss2: com.orion.ShallowStudent@1b6d3586,detail: ShallowStudent{name='lily', cmeScores=[22, 60, 70], age=12, height=155},ss2 hashcode: 460141958
         *
         * 结论:ShallowStudent实现了cloneable接口,重写了clone方法,创建1个对象,另一个clone,它们在栈中是分配两个地址的,
         * 都是指向同一个堆地址,height是int基本类型,其他都是对象类型,height直接比较value没问题,而改变了ss2的name为什么ss1没有改变呢,
         * 因为string是不可变类,它会再生一个string,而ss2改变了指向,所以ss1不变,ss2变,age也是integer不可变类也是同样道理,
         * 还有ss1和ss2的name、age用==来比较的话,结果是true,证明它们指向的地址是一样的,
         * 而数组对象cmeScores不是不可变对象,ss1和ss2指向同一个地址,改变了它,两个都是会发生变化的。
         *
         * 所以,我们来验证深浅拷贝,需要一个可变对象,用不可变对象是验证不出来的。
         */
    }
}

/**
 * 非常需要注意的是,重写clone方法,是要实现cloneable接口的,
 * 如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常
 */
class ShallowStudent implements Cloneable {
    private String name;
    private Integer age;
    private int height;
    private int[] cmeScores;

    public ShallowStudent(String name, Integer age, int height, int[] cmeScores) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.cmeScores = cmeScores;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int[] getCmeScores() {
        return cmeScores;
    }

    public void setCmeScores(int[] cmeScores) {
        this.cmeScores = cmeScores;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public String showShallowStudentDetail() {
        return "ShallowStudent{" +
                "name='" + name + '\'' +
                ", cmeScores=" + Arrays.toString(cmeScores) +
                ", age=" + age +
                ", height=" + height +
                '}';
    }
}

深拷贝:

深拷贝是一种完全拷贝,无论是值类型还是引用类型都会完完全全的拷贝一份,在内存中生成一个新的对象,简单点说就是拷贝对象和被拷贝对象没有任何关系,互不影响


这里需要注意一下:为什么要弄别的对象做属性,比如Father、Son类,因为上面浅拷贝的例子,它的属性都是不可变对象,就算改变了它们,它们底层是生成了一个新的对象并应用它。现在用Father、Son类做参数,目的就是为了使用可变对象做属性来测试深浅拷贝。

注意下面,grandfather、son实现cloneable接口并重写clone方法,father是没有的。

public classDeepCopy {
    /**
     * 之所以定义那么多class,因为需要重写clone方法,原本的Object对象的clone方法是protected的,不能直接调用的。
     */
    public static void main(String[] args) throws CloneNotSupportedException {
        System.out.println("=====================不使用数组,使用2个自定义对象,可变对象参数不实现cloneable接口=====================");

        GrandFather gf1 = new GrandFather(70, "grandFather", new Father(40, "father"));
        GrandFather gf2 = (GrandFather) gf1.clone();
        System.out.println("gf1: " + gf1 + ",detail: " + gf1.showGrandFatherDetail() + ",gf1 hashcode: " + gf1.hashCode());
        System.out.println("gf2: " + gf2 + ",detail: " + gf2.showGrandFatherDetail() + ",gf2 hashcode: " + gf2.hashCode());
        System.out.println("clone gf2 change gfName = fakeGf , gfAge = 80 , father set fName = fakeF and fAge = 50");

        gf2.setGfName("fakeGf");
        gf2.setGfAge(80);
        gf2.getFather().setfAge(50);
        gf2.getFather().setfName("fakeF");

        System.out.println("gf1: " + gf1 + ",detail: " + gf1.showGrandFatherDetail() + ",gf1 hashcode: " + gf1.hashCode());
        System.out.println("gf2: " + gf2 + ",detail: " + gf2.showGrandFatherDetail() + ",gf2 hashcode: " + gf2.hashCode());

        /**
         * 运行结果:
         gf1: com.orion.GrandFather@4554617c,detail: GrandFather{gfAge=70, gfName='grandFather', father=Father{fAge=40, fName='father'}},gf1 hashcode: 1163157884
         gf2: com.orion.GrandFather@74a14482,detail: GrandFather{gfAge=70, gfName='grandFather', father=Father{fAge=40, fName='father'}},gf2 hashcode: 1956725890
         clone gf2 change gfName = fakeGf , gfAge = 80 , father set fName = fakeF and fAge = 50
         gf1: com.orion.GrandFather@4554617c,detail: GrandFather{gfAge=70, gfName='grandFather', father=Father{fAge=50, fName='fakeF'}},gf1 hashcode: 1163157884
         gf2: com.orion.GrandFather@74a14482,detail: GrandFather{gfAge=80, gfName='fakeGf', father=Father{fAge=50, fName='fakeF'}},gf2 hashcode: 1956725890
         *
         * 结论:
         * GrandFather有实现cloneable接口并且重写clone方法,而它的属性Father对象是没有实现cloneable接口的,
         * 克隆体gf2改变了father属性的子属性时,gf1也被改变了,结果可证,这也是一个浅拷贝。
         *
         */
        System.out.println("=====================不使用数组,使用2个自定义对象,可变对象参数实现cloneable接口=====================");

        GrandFather gf3 = new GrandFather(99, "grandFather", new Father(44, "father"), new Son(22, "son"));
        GrandFather gf4 = (GrandFather) gf3.clone();
        System.out.println("gf3: " + gf3 + ",detail: " + gf3.showGrandFatherDetail() + ",gf3 hashcode: " + gf3.hashCode());
        System.out.println("gf4: " + gf4 + ",detail: " + gf4.showGrandFatherDetail() + ",gf4 hashcode: " + gf4.hashCode());
        System.out.println("clone gf4 change gfName = trueGF , gfAge = 88 , father set fName = trueF and fAge = 55 , son set sName= trueS and sAge = 33");

        gf4.setGfName("trueGF");
        gf4.setGfAge(88);
        gf4.getFather().setfAge(55);
        gf4.getFather().setfName("trueF");
        gf4.getSon().setsAge(33);
        gf4.getSon().setsName("trueS");

        System.out.println("gf3: " + gf3 + ",detail: " + gf3.showGrandFatherDetail() + ",gf3 hashcode: " + gf3.hashCode());
        System.out.println("gf4: " + gf4 + ",detail: " + gf4.showGrandFatherDetail() + ",gf4 hashcode: " + gf4.hashCode());
        /**
         * 运行结果:
         gf3: com.orion.GrandFather@1540e19d,detail: GrandFather{gfAge=99, gfName='grandFather', father=Father{fAge=44, fName='father'}, son=Son{sAge=22, sName='son'}},gf3 hashcode: 356573597
         gf4: com.orion.GrandFather@677327b6,detail: GrandFather{gfAge=99, gfName='grandFather', father=Father{fAge=44, fName='father'}, son=Son{sAge=22, sName='son'}},gf4 hashcode: 1735600054
         clone gf4 change gfName = trueGF , gfAge = 88 , father set fName = trueF and fAge = 55 , son set sName= trueS and sAge = 33
         gf3: com.orion.GrandFather@1540e19d,detail: GrandFather{gfAge=99, gfName='grandFather', father=Father{fAge=55, fName='trueF'}, son=Son{sAge=22, sName='son'}},gf3 hashcode: 356573597
         gf4: com.orion.GrandFather@677327b6,detail: GrandFather{gfAge=88, gfName='trueGF', father=Father{fAge=55, fName='trueF'}, son=Son{sAge=33, sName='trueS'}},gf4 hashcode: 1735600054
         *
         * 结论:
         * GrandFather有实现cloneable接口并且重写clone方法,而它的属性Father对象是没有实现cloneable接口的,属性son有实现cloneable方法并重写clone方法
         * 同时GrandFather的clone方法也有子属性son的clone方法,如果子属性的子属性还有,就要一层一层嵌下去,比较麻烦,所以还有一种是serialize的方法进行深拷贝。
         * 由结果可以知道,实现了cloneable接口的son属性在被gf4改变的时候,gf3没有改变,说明他们的各自的son是独立的。
         * 所以实现深拷贝,就都需要实现cloneable接口并且重写clone方法,父类的clone方法还需要对子属性的clone。
         */
    }
}

class GrandFather implements Cloneable {
    private int gfAge;
    private String gfName;
    private Father father;
    private Son son;

    public GrandFather(int gfAge, String gfName) {
        this.gfAge = gfAge;
        this.gfName = gfName;
    }

    public GrandFather(int gfAge, String gfName, Father father) {
        this.gfAge = gfAge;
        this.gfName = gfName;
        this.father = father;
    }

    public GrandFather(int gfAge, String gfName, Son son) {
        this.gfAge = gfAge;
        this.gfName = gfName;
        this.son = son;
    }

    public GrandFather(int gfAge, String gfName, Father father, Son son) {
        this.gfAge = gfAge;
        this.gfName = gfName;
        this.father = father;
        this.son = son;
    }

    public int getGfAge() {
        return gfAge;
    }

    public void setGfAge(int gfAge) {
        this.gfAge = gfAge;
    }

    public String getGfName() {
        return gfName;
    }

    public void setGfName(String gfName) {
        this.gfName = gfName;
    }

    public Father getFather() {
        return father;
    }

    public void setFather(Father father) {
        this.father = father;
    }

    public Son getSon() {
        return son;
    }

    public void setSon(Son son) {
        this.son = son;
    }

    public String showGrandFatherDetail() {
        return "GrandFather{" +
                "gfAge=" + gfAge +
                ", gfName='" + gfName + '\'' +
                (father == null ? "" : ", father=" + father.showFatherDetail()) +
                (son == null ? "" : ", son=" + son.showSonDetail()) +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        GrandFather grandFather = (GrandFather) super.clone();
        //深拷贝,除了子对象属性要实现cloneable接口重写clone方法,自身也要做同样操作,而且还要在自己的clone方法中写上子属性的
        //clone方法,有多少就嵌入多少,一直传递。
        if (son != null) {
            grandFather.son = (Son) son.clone();
        }
        return grandFather;
    }
}

class Father {
    private int fAge;
    private String fName;

    public Father(int fAge, String fName) {
        this.fAge = fAge;
        this.fName = fName;
    }

    public int getfAge() {
        return fAge;
    }

    public void setfAge(int fAge) {
        this.fAge = fAge;
    }

    public String getfName() {
        return fName;
    }

    public void setfName(String fName) {
        this.fName = fName;
    }

    public String showFatherDetail() {
        return "Father{" +
                "fAge=" + fAge +
                ", fName='" + fName + '\'' +
                '}';
    }
}

class Son implements Cloneable {
    private int sAge;
    private String sName;

    public Son(int sAge, String sName) {
        this.sAge = sAge;
        this.sName = sName;
    }

    public int getsAge() {
        return sAge;
    }

    public void setsAge(int sAge) {
        this.sAge = sAge;
    }

    public String getsName() {
        return sName;
    }

    public void setsName(String sName) {
        this.sName = sName;
    }

    public String showSonDetail() {
        return "Son{" +
                "sAge=" + sAge +
                ", sName='" + sName + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

序列化方式的深拷贝

实现 Serializable 接口方式也可以实现深拷贝,而且这种方式还可以解决多层克隆的问题,多层克隆就是引用类型里面又有引用类型,层层嵌套下去,用 Cloneable 方式实现还是比较麻烦的,一不小心写错了就不能实现深拷贝了,使用 Serializable 序列化的方式就需要所有的对象对实现 Serializable 接口,比较方便。

/**
 * 序列化方式进行深拷贝,避免层层clone,各个对象都要实现序列化Serializable接口
 *
 * @author Administrator
 */
public class SerializableDeepCopy {
    public static void main(String[] args) {
        SmartPhone sp = new SmartPhone("smartPhone", 1000);
        Phone phone1 = new Phone("phone", 5000, sp);
        Phone phone2 = phone1.cloneit();
        System.out.println("phone1: " + phone1 + ",detail: " + phone1.showPhoneDetail() + ",ss1 hashcode: " + phone1.hashCode());
        System.out.println("phone2: " + phone2 + ",detail: " + phone2.showPhoneDetail() + ",ss2 hashcode: " + phone2.hashCode());
        System.out.println("change phone2 name = fakePhone2 , name = 3000 , set smartPhone name = fakeSmartPhone2 , holdTime = 9000");
        phone2.setName("fakePhone2");
        phone2.setHoldTime(3000);
        phone2.getSp().setName("fakeSmartPhone2");
        phone2.getSp().setHoldTime(9000);
        System.out.println("phone1: " + phone1 + ",detail: " + phone1.showPhoneDetail() + ",ss1 hashcode: " + phone1.hashCode());
        System.out.println("phone2: " + phone2 + ",detail: " + phone2.showPhoneDetail() + ",ss2 hashcode: " + phone2.hashCode());
        /**
         * 运行结果:
           phone1: com.orion.Phone@7f31245a,detail: Phone{name='phone', holdTime=5000, sp=SmartPhone{name='smartPhone', holdTime=1000}},ss1 hashcode: 2133927002
           phone2: com.orion.Phone@4f3f5b24,detail: Phone{name='phone', holdTime=5000, sp=SmartPhone{name='smartPhone', holdTime=1000}},ss2 hashcode: 1329552164
           change phone2 name = fakePhone2 , name = 3000 , set smartPhone name = fakeSmartPhone2 , holdTime = 9000
           phone1: com.orion.Phone@7f31245a,detail: Phone{name='phone', holdTime=5000, sp=SmartPhone{name='smartPhone', holdTime=1000}},ss1 hashcode: 2133927002
           phone2: com.orion.Phone@4f3f5b24,detail: Phone{name='fakePhone2', holdTime=3000, sp=SmartPhone{name='fakeSmartPhone2', holdTime=9000}},ss2 hashcode: 1329552164
         *
         * 可见,通过serializable的深拷贝是成功的,phone2是phone1克隆出来,但phone2变化并不影响到phone1。
         */
    }

}

class Phone implements Serializable {
    private String name;
    private int holdTime;
    private SmartPhone sp;

    public Phone(String name, int holdTime, SmartPhone sp) {
        this.name = name;
        this.holdTime = holdTime;
        this.sp = sp;
    }

    public Phone cloneit() {
        Phone phone = null;
        try {
            // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(this);
            // 将流序列化成对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            return (Phone) ois.readObject();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return phone;
    }

    public String showPhoneDetail() {
        return "Phone{" +
                "name='" + name + '\'' +
                ", holdTime=" + holdTime +
                (sp == null ? "" : ", sp=" + sp.showSmartPhoneDetail()) +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public int getHoldTime() {
        return holdTime;
    }

    public void setHoldTime(int holdTime) {
        this.holdTime = holdTime;
    }

    public SmartPhone getSp() {
        return sp;
    }

    public void setSp(SmartPhone sp) {
        this.sp = sp;
    }
}

class SmartPhone implements Serializable {
    private String name;
    private int holdTime;

    public SmartPhone(String name, int holdTime) {
        this.name = name;
        this.holdTime = holdTime;
    }

    public String getName() {
        return name;
    }

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

    public int getHoldTime() {
        return holdTime;
    }

    public void setHoldTime(int holdTime) {
        this.holdTime = holdTime;
    }

    public String showSmartPhoneDetail() {
        return "SmartPhone{" +
                "name='" + name + '\'' +
                ", holdTime=" + holdTime +
                '}';
    }
}

参考:

Java 之浅拷贝、深拷贝,你到底知多少?
深拷贝与浅拷贝详解
clone方法 --深拷贝与浅拷贝

上一篇 下一篇

猜你喜欢

热点阅读