Java 引用的复制,深复制和浅复制之间的区别 一篇就够了

复制对象

两个对象的引用相同,都指向同一个对象,叫引用的复制
只有一个对象,只不过是两个引用指向同一个对象
当一个对象发生改变,另一个对象也会发生改变

Person p = new Person(23, "zhang");
Person p1 = p;
 
System.out.println(p ==p1); //true

无论深复制还是浅复制都会新创建一个对象

什么是浅复制?

浅复制实现的两种方式:

1.通过Cloneable接口实现 代码实例:

首先,你要知道怎么实现克隆:实现Cloneable接口,在bean里面重写clone()方法,权限为public。

比如对象A里面有一个属性name 和并且引用了一个对象B,只需要重写A对象的clone方法即可实现浅复制

其中Student实现Cloneable接口,但是Professor 没有实现Cloneable接口的时候

package clone;
public class ShadowCopy {

    public static void main(String[] args) {
        Professor p1 = new Professor();
        p1.setName("Professor Zhang");
        p1.setAge(30);

        Student s1 = new Student();
        s1.setName("xiao ming");
        s1.setAge(18);
        s1.setProfessor(p1);

        System.out.println(s1);

        try {
            Student s2 = (Student) s1.clone();
            s2.setName("St  希尤");
            s2.setAge(1110);
            Professor p2 = s2.getProfessor();
            p2.setName("Professor Li");
            p2.setAge(45);
            s2.setProfessor(p2);
            System.out.println("复制后的:s1 = " + s1);
            System.out.println("复制后的:s2 = " + s2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
    /*
    Student [name=xiao ming, age=18, professor=Professor [name=Professor Zhang, age=30]]
    复制后的:s1 = Student [name=xiao ming, age=18, professor=Professor [name=Professor Li, age=45]]
    复制后的:s2 = Student [name=St  希尤, age=1110, professor=Professor [name=Professor Li, age=45]]
    */

    /*
    结合程序实例1和程序实例2,我们发现Student的字段如果不是一个引用时,修改clone()得到对象的该字段(name, age)时
    并不会影响原来的对象,但是当字段为一个引用时,修改clone()得到对象的该字段(professor)时并会影响原来的对象。
    上面实现的clone()方法为浅复制(shadow copy)。
    */
}

2.通过拷贝构造函数实现

原先的构造函数可能是包括全部属性的,但是如果通过拷贝构造函数的方式,只需要把当前类的写入即可
比如原先的是:

    public Teacher(Age age,String name) {
        this.age=age;
        this.name=name;
    }

只需要添加下面的即可实现浅复制:

    //拷贝构造方法
    public Teacher(Teacher p) {
        this.name=p.name;
        this.age=p.age;
    }
package clone;

import string.Person;

public class Teacher {

    private Age age;
    private String name;
    public Teacher(Age age,String name) {
        this.age=age;
        this.name=name;
    }
    //拷贝构造方法
    public Teacher(Teacher p) {
        this.name=p.name;
        this.age=p.age;
    }

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

    public String toString() {
        return this.name+" "+this.age;
    }

}



package clone;

public class Age {


    private int age;

    public Age(int age) {
        this.age = age;
    }

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

    public int getAge() {
        return this.age;
    }

    public String toString() {
        return getAge() + "";
    }
}



package clone;

public class TestConstructor {

    public static void main(String[] args) {
        Age a=new Age(20);
        Teacher p1=new Teacher(a,"小学老师");
        Teacher p2=new Teacher(p1);
        System.out.println("p1是"+p1);
        System.out.println("p2是"+p2);
        //修改p1的各属性值,观察p2的各属性值是否跟随变化
        p1.setName("高中老师");
        a.setAge(99);
        System.out.println("修改后的p1是"+p1);
        System.out.println("修改后的p2是"+p2);
    }
}
结果:
p1是小学老师 20
p2是小学老师 20
修改后的p1是高中老师 99
修改后的p2是小学老师 99


总结:

①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

深复制

深复制的两种方式:

方式一、 和浅复制一样实现Cloneable接口 ,条件如下

 	 A.深复制条件 ---父类和子类都要实现Cloneable()接口
	 B. 父类重写Clone接口的时候,要把子类带上,如:
父类重写clone代码
 public Object clone() throws CloneNotSupportedException{
        Student newStudent = (Student) super.clone();
        newStudent.professor = (Professor) professor.clone();
        return newStudent;
    }
3. 结果就是更改clone()得到的s2的任何字段都不会影响s1的字段,这也就是深复制的作用。

同样运行上面的代码结果:

Student [name=xiao ming, age=18, professor=Professor [name=Professor Zhang, age=30]]
false
复制后的:s1 = Student [name=xiao ming, age=18, professor=Professor [name=Professor Zhang, age=30]]
复制后的:s2 = Student [name=St  希尤, age=1110, professor=Professor [name=Professor Li, age=45]]

方式二、实现序列化接口

如果你这个要克隆的对象很复杂的话,你就不得不去每个引用到的对象去复写这个clone方法,这个太啰嗦来,改的地方,太多啦。还有个方法就是使用序列化来实现这个深拷贝
实现的效果,还是和上面的一样的,但是这个就简单多来,只需要给涉及到的每个引用类型,都去实现序列化接口就好啦。

@Data
public class Person implements Serializable {
   private static final long serialVersionUID = 7512746184754731714L;
   private YourDog dog;
   private String name ;
}
@Data
public class YourDog  implements Serializable{
    private static final long serialVersionUID = -990702342305081096L;
    private String dogName ;
}
public class CloneUtils {
    /**
     * 要实现深度克隆,必须要求,被克隆对象和被克隆对象里面被引用的对象都实现序列号接口
     * @param obj
     * @return
     */
    public static Object clone(Object obj) {
        Object cloneObj = null;
        try {
            //写入字节流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();

            //分配内存,写入原始对象,生成新对象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);

            //返回生成的新对象
            cloneObj = ois.readObject();
            ois.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}
import org.apache.commons.lang3.SerializationUtils;

public class CloneTest {
    public static void main(String[] args) {
        YourDog dog1 =new YourDog();
        dog1.setDogName("dog1");
        Person source = new Person();
        source.setName("p1");
        source.setDog(dog1);
        Person target = (Person) SerializationUtils.clone(source);
        System.out.println("被克隆对象: " + source.hashCode() + "\n" +
                "被克隆对象内的dog属性: " + source.getDog().hashCode() + "         " + source.toString());

        System.out.println("克隆出来的对象: " + target.hashCode() + "\n" +
                "克隆出来的对象内的dog属性: " + target.getDog().hashCode() + "         " + target.toString());

        System.out.println("------------------上面是直接进行深复制,不改变复制后的对象任何值----------------------");
        YourDog dog2 =new YourDog();
        dog2.setDogName("dog2");
        Person source2 = new Person();
        source2.setName("p2");
        source2.setDog(dog2);
        Person target2 = (Person) SerializationUtils.clone(source2);
        target2.setName("copy2");

        System.out.println("被克隆对象: " + source2.hashCode() + "\n" +
                "被克隆对象内的dog属性: " + source2.getDog().hashCode() + "         " + source2.toString());

        System.out.println("克隆出来的对象: " + target2.hashCode() + "\n" +
                "克隆出来的对象内的dog属性: " + target2.getDog().hashCode() + "         " + target2.toString());




        System.out.println("--------------上面是改变复制后对象的普通属性,不改变引用类属性--------------------------");
        YourDog dog3 =new YourDog();
        dog3.setDogName("dog3");
        Person source3= new Person();
        source3.setName("p3");
        source3.setDog(dog3);

        Person target3 = (Person) SerializationUtils.clone(source3);
        YourDog dog =new YourDog();
        dog.setDogName("dogCopy3");
        target3.setDog(dog);
        System.out.println("被克隆对象: " + source3.hashCode() + "\n" +
                "被克隆对象内的dog属性: " + source3.getDog().hashCode() + "         " + source3.toString());

        System.out.println("克隆出来的对象: " + target3.hashCode() + "\n" +
                "克隆出来的对象内的dog属性: " + target3.getDog().hashCode() + "         " + target3.toString());

        System.out.println("--------------上面是改变复制后对象的引用类的属性,不改变普通属性--------------------------");
    }
}

输出结果:

被克隆对象: 182262250
被克隆对象内的dog属性: 3089072         Person(dog=YourDog(dogName=dog1), name=p1)
克隆出来的对象: 182262250
克隆出来的对象内的dog属性: 3089072         Person(dog=YourDog(dogName=dog1), name=p1)
------------------上面是直接进行深复制,不改变复制后的对象任何值----------------------
被克隆对象: 182262310
被克隆对象内的dog属性: 3089073         Person(dog=YourDog(dogName=dog2), name=p2)
克隆出来的对象: 277105601
克隆出来的对象内的dog属性: 3089073         Person(dog=YourDog(dogName=dog2), name=copy2)
--------------上面是改变复制后对象的普通属性,不改变引用类属性--------------------------
被克隆对象: 182262370
被克隆对象内的dog属性: 3089074         Person(dog=YourDog(dogName=dog3), name=p3)
克隆出来的对象: -956785685
克隆出来的对象内的dog属性: 930131901         Person(dog=YourDog(dogName=dogCopy3), name=p3)
--------------上面是改变复制后对象的引用类的属性,不改变普通属性--------------------------
/**
 * 结果说明了:
 *   假设原来的对象 A 包含了一个字段 a1 ,和引用类型对象 B
 * 1. 即使是深度克隆,只要不改变被克隆对象的值,原来对象和克隆对象都是指向同一块内存区域,是一样的
 * 2. 如果只是改变了被克隆后对象的普通属性a1(基本类型),而不改变引用类型的变量B,那么原本的B不变,会克隆一个新的A对象
 * 3. 如果改变了引用类型的属性,不改变基本类型的,将会克隆出A和B两个新对象
 *
 */
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值