深拷贝与浅拷贝
1、什么是深拷贝什么是浅拷贝
浅拷贝是指创建一个新的数据结构,然后将原始数据结构中的元素复制到新的数据结构中。但是,如果原始数据结构中的元素是对象或引用类型,浅拷贝只会复制对象的引用而不是对象本身。这意味着新旧数据结构中的对象会引用同一个对象,因此对于对象内部的修改会在两个数据结构中都反映出来。
深拷贝则更彻底,它会递归地复制原始数据结构中的所有元素,包括嵌套的对象和引用类型。这样,深拷贝会创建一个完全独立的新数据结构,新旧数据结构中的对象之间没有任何关联。因此,对于深拷贝后的数据结构做任何修改都不会影响原始数据结构。
区别
深拷贝和浅拷贝之间的主要区别在于复制对象内部引用的方式以及对于嵌套对象的处理方式:
- 复制对象内部引用的方式:
- 浅拷贝只会复制原始数据结构中的对象引用,而不会递归地复制对象本身。这意味着新旧数据结构中的对象引用相同的对象,因此对于对象内部的修改会反映在两个数据结构中。
- 深拷贝会递归地复制原始数据结构中的所有对象,包括嵌套的对象和引用类型。这样,新数据结构中的对象是全新的副本,与原始数据结构中的对象没有关联。
- 对于嵌套对象的处理方式:
- 浅拷贝中,嵌套对象的复制方式与原始对象相同,即也是复制引用。这导致嵌套对象在新旧数据结构中共享同一个对象,所以对嵌套对象的修改会在两个数据结构中都反映出来。
- 深拷贝中,嵌套对象会被递归地复制,从而生成新的独立对象。这保证了新数据结构中的对象和原始数据结构中的对象是相互独立的,对其中一个的修改不会影响另一个。
2、Object的clone方法
clone()
方法是 Java 中的一个方法,用于创建对象的拷贝。它用于实现对象的克隆,可以通过克隆得到一个与原始对象相似的新对象,以及保留原始对象的状态。在实际开发中,可以根据需要选择浅拷贝或深拷贝来实现。
这里解释一下 clone()
方法的一些重要点:
-
clone()
方法的实现: 在 Java 中,要使用clone()
方法进行对象拷贝,需要满足两个条件:- 类需要实现
Cloneable
接口。这是一个标记接口,它表示类支持克隆操作。如果不实现这个接口,调用clone()
方法会抛出CloneNotSupportedException
异常。 - 在类中重写
clone()
方法。clone()
方法是Object
类中的一个受保护方法,因此在子类中需要重写该方法,并将其访问修饰符改为public
。在重写的方法中,可以调用super.clone()
方法来获得对象的浅拷贝,然后根据需要进行深拷贝。
- 类需要实现
-
浅拷贝和深拷贝: 默认情况下,
clone()
方法实现的是浅拷贝,即只复制对象的字段值和引用。如果对象内部包含其他对象(引用类型),那么原始对象和克隆对象会共享同一引用。这可能导致对内部对象的修改影响到两个对象。如果需要实现深拷贝,需要在
clone()
方法中手动递归复制内部的引用类型对象,确保克隆对象和原始对象之间没有任何关联。 -
注意事项:
clone()
方法是一个浅拷贝的基础实现,要实现深拷贝需要在适当的地方添加额外的逻辑。clone()
方法是Object
类的方法,因此所有 Java 类都继承了这个方法。但默认的实现只适用于简单的对象,对于复杂的对象或含有不可变状态的对象可能会适用得较好。- 如果需要更强大的拷贝功能,可以考虑使用第三方库,如 Apache Commons 的
SerializationUtils
,或者使用JSON进行拷贝以及实现自定义的克隆逻辑。
构建例子对象
@Data
@AllArgsConstructor
public class Person implements Cloneable{//需要实现Cloneable接口并且重写clone方法
private String name;
private String password;
private String age;
private Address address;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//Address对象
@Data
@AllArgsConstructor
public class Address {
private String province;
private String city;
private String county;
}
使用clone实现浅拷贝
public static void main(String[] args) {
Person person = new Person("xs", "123","21",new Address("湖北省", "武汉市", "-"));
try {
Person clone = (Person)person.clone();
System.out.println(clone == person);
System.out.println(clone.getAddress() == person.getAddress());
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
//程序输出
false
true
以上程序说明了clone方法拷贝出来的是一个新的对象,但是clone.getAddress() == person.getAddress()的结果为true说明二者使用的同一份的引用地址,引用还是指向的原来的对象。
对于以上的情况可能会出现的问题
public static void main(String[] args) {
Person person = new Person("xs", "123","21",new Address("湖北省", "武汉市", "-"));
try {
Person clone = (Person)person.clone();
clone.getAddress().setProvince("四川省");
System.out.println(person.getAddress());
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
//程序输出
Address(province=四川省, city=武汉市, county=-)
以上我们对克隆的地址进行修改,但是原来的person的地址也被修改了,这种情况要是自己不小心没注意到,可能就会出现大的事故,所以对于浅拷贝要慎用,以下推荐几种深拷贝的方法
3、深拷贝
以下推荐使用第三方库要完成,或者自己自定义
Apache Commons Lang
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
public static void main(String[] args) {
Person person = new Person("xs", "123", "21", new Address("湖北省", "武汉市", "-"));
Person clone = SerializationUtils.clone(person);
System.out.println(person == clone);
System.out.println(person.getAddress() == clone.getAddress());
}
//程序输出
false
false
以上使用Apache Commons Lang的SerializationUtils进行深拷贝,条件需要所以的类都实现Serializable接口,该方式使用的序列化和反序列化进行拷贝
JSON
以下使用fastjson2进行支持JSON的序列化,其他的json工具类似
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.25</version>
</dependency>
public static void main(String[] args) {
Person person = new Person("xs", "123", "21", new Address("湖北省", "武汉市", "-"));
Person clone = JSON.to(Person.class, JSON.toJSONString(person));
System.out.println(person == clone);
System.out.println(person.getAddress() == clone.getAddress());
}
//程序输出
false
false
通过结果可以看出,反序列化构建出来的对象,是全新的、深度拷贝的对象
4、总结
从以上内容可以总结出,使用clone方法进行拷贝是进行的浅拷贝,要是自己不小心的话,可能会出现非常严重的事故,后面我们介绍了几种进行深拷贝的方法。