java基础复习----复制、浅拷贝与深拷贝

前言

​   以前遇到过一个BUG,大概是这样的:

@Data
public class Company {
    private String companyName;
    private String companyAddr;
    //公司员工
    private List<Person> personList;

    public Company(String companyName, String companyAddr, List<Person> personList) {
        this.companyName = companyName;
        this.companyAddr = companyAddr;
        this.personList = personList;
    }
@Data
public class Person {
    private String name;
    private int age;
    private String addr;

    public Person(String name, int age, String addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }
 public static void main(String args[]){

        List<Person> personList = new ArrayList<>();
        Person person1 = new Person("jack",23,"北京");
        Person person2 = new Person("lisa",24,"上海");
        personList.add(person1);
        personList.add(person2);

        //创建一个对象company1
        Company company1 = new Company("xxx科技公司","北京",personList);
        //创建一个对象company2,并把1赋值给2
        Company company2 = company1;
        //改变company1中的公司地址
        company1.setCompanyAddr("深圳");
        System.out.println(company1 == company2);
        System.out.println("company1:"+company1.toString());
        System.out.println("company2:"+company2.toString());
    }

  就像上面的代码那样,当我后面改变company1任意属性值时发现company2中的属性值也跟着改变了,这不是我想要的​结果😵

在这里插入图片描述
​   原因分析:对象在使用“=”进行赋值时,采用的是引用传递(传递的是对象的一个引用),实际上两个对象指向的还是堆内存中的**同一个对象**,所以当其中一个改变时,另一个也会跟着改变。

在这里插入图片描述

​   于是我想到了用浅拷贝或者深拷贝,但是实际上对这些概念还是很模糊,所以改完问题又去系统地学习了一下😏。

浅拷贝与深拷贝

​   上面的问题是使用深拷贝解决的,为什么不用浅拷贝呢?在 Java 中,除了基本数据类型(元类型)之外,还存在 类的实例对象 这个引用数据类型。在处理基本数据类型(如int,char,double)时,都是采用值传递,实际上是拷贝它的值。除此之外的其他类型都是按引用传递(传递的是对象的一个引用),实际上拷贝的知识对象的引用。对象在使用“=”赋值时也采用引用传递

​   因为上面的对象company中还有一个属性personList,所以 使用浅拷贝 后,改变其他属性(基本数据类型)没有影响,但改变personList时另外一个的personList也会跟着改变。

浅拷贝

概念

 浅拷贝就像上面说的一样,使用浅拷贝,拷贝前后的对象不是同一个对象。

 ①对于对象中基本数据类型的属性,因为是值传递的,所以直接将属性值赋值给新的对象。其中一个对象修改该值,不会影响另外一个。

 ②对于对象中的引用类型的属性,比如数组,集合或者类对象,因为是引用传递,所以浅拷贝只是把该属性的内存地址赋值给了新对象,它们指向了同一内存空间。改变其中一个对象中的该属性,另外一个就会受到影响。

浅拷贝的实现方法

​  要进行拷贝的类实现 Cloneable 接口,并覆写 clone() 方法。

@Data
public class Company implements Cloneable{
    //公司姓名
    private String companyName;
    //公司地址
    private String companyAddr;
    //公司员工
    private List<Person> personList;

    public Company(String companyName, String companyAddr, List<Person> personList) {
        this.companyName = companyName;
        this.companyAddr = companyAddr;
        this.personList = personList;
    }
	//重写clone()
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

测试方法

public static void main(String args[]) throws CloneNotSupportedException {

        List<Person> personList = new ArrayList<>();
        Person person1 = new Person("jack",23,"北京");
        Person person2 = new Person("lisa",24,"上海");
        Person person3 = new Person("jone",23,"北京");
        personList.add(person1);
        personList.add(person2);

        //创建一个对象company1
        Company company1 = new Company("xxx科技公司","北京",personList);
        //使用clone()获取对象company2
        Company company2 = (Company) company1.clone();
        //改变company2中的公司地址
        company2.setCompanyAddr("深圳");
        //改变company2中的personList
        List<Person> list = company2.getPersonList();
        list.add(person3);
        System.out.println(company1 == company2);
        System.out.println("company1:"+company1.toString());
        System.out.println("company2:"+company2.toString());
    }

结果
在这里插入图片描述
  可以看到两个对象是不相同的,但实际上两个对象中的personList还是指向一个地址(上面例子的代码中忘写了,可以试一下company1.getPersonList() == company2.getPersonList() 返回的是**true**,改变基本类型属性值不影响,但改变company2的PersonList时,会影响到company1中的PersonList。

  当然也有解决方法,那就是Person类也实现 Cloneable 接口,并覆写 clone() 方法,然后在Companyclone()方法中拿到拷贝后的对象,对拷贝后的对象的成员变量再调用clone()方法。(这里已经相当于深拷贝了)。

@Data
public class Person implements Cloneable{
    private String name;
    private int age;
    private String addr;

    public Person(String name, int age, String addr) {
        this.name = name;
        this.age = age;
        this.addr = addr;
    }

    //复写clone
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
@Data
public class Company implements Cloneable{
    //公司姓名
    private String companyName;
    //公司地址
    private String companyAddr;
    //公司员工
    private List<Person> personList;

    public Company(String companyName, String companyAddr, List<Person> personList) {
        this.companyName = companyName;
        this.companyAddr = companyAddr;
        this.personList = personList;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Company company = (Company) super.clone();
        List<Person> list = new ArrayList<>();
        //依次重写personList中的对象Person 再set到company中,
        //在调用company.clone()方法时也调用了person.clone();
        if (!CollectionUtils.isEmpty(company.getPersonList())){
            for (Person person : company.getPersonList()){
                person = (Person) person.clone();
                list.add(person);
            }
            company.setPersonList(list);
        }
        return company;
    }
}

  这里因为我的Company中的引用类型的变量是list,所以麻烦了一点,如果只是一个Person对象,就是这样:

private Person person;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Company company = (Company) super.clone();
        company.person = (Person)person.clone();     
        return company;
    }

 测试类和上面用的测试类一样,输出结果:
在这里插入图片描述
 这样,两个对象就不会互相影响了。company1.getPersonList() == company2.getPersonList()返回true。

深拷贝

概念

① 对于基本数据类型的成员变量,因为是值传递,所以是直接将属性值赋值给新的对象。其中一个对象修改该值,不会影响另外一个(这一点和浅拷贝是一样的)。

② 对于需要拷贝的对象中的引用类型的成员变量,比如集合,类对象等,深拷贝会新建一个对象空间,然后拷贝里面的内容,即它们也指向了不同的内存空间。改变其中一个,另一个不会受到影像(比如浅拷贝中的例子,如果使用深拷贝,)。

深拷贝的实现方法

三种深拷贝实现的方法:

① 第一种就是上面浅拷贝的那种解决方案,就是给每一层对象都要实现实现 Cloneable 接口,并覆写 clone() 方法,比较费劲,不推荐。

② 序列化与反序列化:使用org.apache.commons.lang.SerializationUtils的clone(Object obj)方法,拷贝的对象以及它的子对象都要实现Serializable接口。

Company company2 = (Company) SerializationUtils.clone(company1);

③ 把要深拷贝的对象从Object转成json,然后转回Object(推荐)

  String json = JSON.toJSONString(company1);
  Company company2 = JSON.parseObject(json, Company.class);
使用json的方式比序列化快一点。

具体代码就不贴了,测试方法和上面例子一样的😀。

总结

复制:创建了两个一摸一样的对象,地址相同,值也相同,改变其中一个对象的任一属性,另外一个也随之改变。

 java的浅拷贝和深拷贝:区别就是在拷贝对象的时候,对引用类型拷贝引用还是拷贝值。

 如果拷贝前后两个对象地址不同,但只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,则是浅拷贝。
 反之,拷贝前后两个对象地址不一样,对基本数据类型进行了拷贝,且在对该对象中的引用数据类型进行拷贝的时候,对该引用指向的对象也进行了拷贝,则是深拷贝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值