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