Java对象深拷贝和浅拷贝

前言

拷贝功能,在电脑上我们经常用到,也许换个名字更容易理解,那就是复制功能。我们平常复制以后就是两个独立的文件,修改其中一个文件的内容并不会影响另一个文件的内容。我们平常在写代码的时候,也经常会有这样的场景,把一个对象完完全全的复制一份出来,那么我们Java里面的对象拷贝是啥样的呢?

拷贝方式

浅拷贝

我们先不说什么事浅拷贝,我们先举个栗子🌰

1: 同类型对象直接将值赋给另一个对象

        Son son = new Son()
                .setName("小明");
                
        Person person = new Person()
                .setName("苏雨")
                .setAge(20)
                .setSon(son);
                
        Person copyPerson = person;

        System.out.println("源对象:" + person);

        copyPerson.setName("苏苏苏");

        person.getSon().setName("大明");

        System.out.println("修改了拷贝对象name属性和源对象内置对象的属性");
        System.out.println("源对象:"+person);
        System.out.println("拷贝对象:"+copyPerson);

控制台打印结果:

原始对象:Person(name=苏雨, age=20, son=Son(name=小明))
修改了原始对象name属性和内置对象的属性
原始对象:Person(name=苏雨, age=20, son=Son(name=大明))
拷贝对象:Person(name=苏苏苏, age=20, son=Son(name=大明))

2: spring中的BeanUtils.copyProperties()也是一种拷贝方式

        Son son = new Son()
                .setName("小明");

        Person person = new Person()
                .setName("苏雨")
                .setAge(20)
                .setSon(son);
        
        Person copyPerson = new Person();

        BeanUtils.copyProperties(person,copyPerson);

        System.out.println("原始对象:" + person);

        copyPerson.setName("苏苏苏");

        person.getSon().setName("大明");

        System.out.println("修改了原始对象name属性和内置对象的属性");
        System.out.println("原始对象:"+person);
        System.out.println("拷贝对象:"+copyPerson);

控制台打印结果:

原始对象:Person(name=苏雨, age=20, son=Son(name=小明))
修改了原始对象name属性和内置对象的属性
原始对象:Person(name=苏雨, age=20, son=Son(name=大明))
拷贝对象:Person(name=苏苏苏, age=20, son=Son(name=大明))

🌰子中发现这里我修改了拷贝对象的属性,怎么连原始对象的属性值都被改变了。这里拷贝其实是拷贝了原始对象所指向的内存地址值,也就是说虽然我们拷贝了一个对象,但是这两个对象实际上还是指向的同一内存地址。所以他们俩会一直有福同享有难同当,不管谁修改了值,双方的值都会被修改,换做人来说的话,有点类似两个人只有一个身份证一样,谁做了啥坏事的话,都需要一起蹲局子。(这里比喻的不是很形象)

浅拷贝总结

不在内存中创建新的对象,对基本数据类型进行值传递,对引用数据类型进行内存地址传值,此为浅拷贝。

在这里插入图片描述

此时源对象听了就不乐意了,我让拷贝对象把我的属性复制过去就算了,我可不想跟他公用一个身份,有什么办法可以让他复制完了就跟我没有任何关系嘛?这个当然有,我们继续往下面看。

这就是接下来我们要将的深拷贝,

深拷贝

深拷贝总结

创建一个新的对象,对基本数据类型进行值传递,对引用数据类型,并复制其内容,此为深拷贝。

在这里插入图片描述

深拷贝实现方式

1: 通过构造函数来实现,前提是被拷贝对象要有有参的构造函数(源对象的内置对象也需要有有参构造函数)


@Data
@Accessors(chain = true)
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Son {

    private String name;

}

@Data
@Accessors(chain = true)
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Person {

    private String name;

    private int age;

    private Son son;
}

//测试代码
 Son son = new Son("小明");

 Person person = new Person()
         .setName("苏雨")
         .setAge(20)
         .setSon(son);

 Person copyPerson = new Person(person.getName(),person.getAge(),new Son(person.getSon().getName()));
 System.out.println("原始对象:" + person);

 copyPerson.setName("苏苏苏");
 person.getSon().setName("大明");

 System.out.println("修改了拷贝对象name属性和源对象的内置对象属性");
 System.out.println("源对象:"+person);
 System.out.println("拷贝对象:"+copyPerson);

2: 重载clone()方法(源对象及其内置对象也需要重载)

@Data
@Accessors(chain = true)
@ToString
public class Son implements Cloneable{

    private String name;

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

@Data
@Accessors(chain = true)
@ToString
public class Person implements Cloneable{

    private String name;

    private int age;

    private Son son;

    @Override
    protected Person clone() throws CloneNotSupportedException {
        Person person = (Person)super.clone();
        person.setSon(this.son.clone());
        return person;
    }
}

//测试代码
Son son = new Son()
        .setName("小明");

Person person = new Person()
        .setName("苏雨")
        .setAge(20)
        .setSon(son);

Person copyPerson = person.clone();
System.out.println("原始对象:" + person);

copyPerson.setName("苏苏苏");
person.getSon().setName("大明");

System.out.println("修改了拷贝对象name属性和源对象的内置对象属性");
System.out.println("源对象:"+person);
System.out.println("拷贝对象:"+copyPerson);

3: 通过Jackson序列化进行深拷贝(这个只要项目中引入了Jackson包就行)

Person person = new Person()
       .setName("苏雨")
       .setAge(20);

// 使用Jackson序列化进行深拷贝
ObjectMapper objectMapper = new ObjectMapper();
Person copyPerson = objectMapper.readValue(objectMapper.writeValueAsString(person), Person.class);

System.out.println("原始对象:" + person);

copyPerson.setName("苏苏苏");

System.out.println("修改了原始对象name属性");
System.out.println("原始对象:"+person);
System.out.println("拷贝对象:"+copyPerson);

4: 通过fastjson将对象装换为字符串,再通过JSON.parseObject()转换为对象(依赖fasjjson jar包)

        Son son = new Son()
                .setName("小明");

        Person person = new Person()
                .setName("苏雨")
                .setAge(20)
                .setSon(son);

        Person copyPerson = JSON.parseObject(JSON.toJSONString(person),Person.class);
        System.out.println("原始对象:" + person);

        copyPerson.setName("苏苏苏");
        person.getSon().setName("大明");

        System.out.println("修改了拷贝对象name属性和源对象的内置对象属性");
        System.out.println("源对象:"+person);
        System.out.println("拷贝对象:"+copyPerson);

5: Apache Commons Lang序列化进行深拷贝(需要依赖commons-lang)
对象需要实现序列化接口,这里不贴出所有代码啦,有兴趣的可以去了解一下

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.10</version>
</dependency>

//拷贝核心代码
 Person copyPerson = SerializationUtils.clone(person);

控制台打印结果

原始对象:Person(name=苏雨, age=20, son=Son(name=小明))
修改了拷贝对象name属性和源对象的内置对象属性
源对象:Person(name=苏雨, age=20, son=Son(name=大明))
拷贝对象:Person(name=苏苏苏, age=20, son=Son(name=小明))

通过上面这些方法我们都可以实现对象的深拷贝,我个人比较喜欢的就是使用fasjjson的序列化方式来进行深拷贝,要问为啥的话,因为我每个项目中,fasjjson这个jar一定会引入的。

结语

如果有说的不对的请指出来,不要让我误人子弟啦 😃

参考文档:

https://www.cnblogs.com/xinruyi/p/11537963.html
https://juejin.im/post/59912ca86fb9a03c5754ded7

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
浅拷贝深拷贝Java中用于复制对象的两种不同方式。浅拷贝是指复制对象时,仅复制对象的引用而不复制实际的值。因此,当原始对象的引用类型属性发生变化时,浅拷贝对象也会跟着变化。而深拷贝则是复制对象时,不仅复制对象的引用,还会递归地复制引用类型属性的值。这样,深拷贝对象与原始对象完全隔离,互不影响。 在Java中,可以通过一些方式来实现深拷贝,比如使用序列化和反序列化,或者通过递归复制引用类型属性的值。需要注意的是,如果被拷贝的对象中有transient变量,由于transient变量无法被序列化,使用序列化和反序列化方式将无法拷贝transient变量。 选择何种拷贝方式取决于具体的需求。如果对象的属性全是基本类型,可以使用浅拷贝。如果对象有引用类型属性,并且这些属性在任何时候都不会发生变化,那么可以使用浅拷贝。但如果对象的引用类型属性经常改变,为了避免对原始对象造成影响,应该使用深拷贝。 总而言之,深拷贝浅拷贝是用于复制Java对象的两种方式。浅拷贝只复制引用,而深拷贝会递归复制引用类型属性的值。选择拷贝方式应该根据具体需求来确定。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [java深拷贝浅拷贝](https://blog.csdn.net/weixin_45771158/article/details/125730734)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [Java中的深拷贝浅拷贝](https://blog.csdn.net/qq_43460095/article/details/125766762)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值