条目13:谨慎重写clone方法
浅拷贝和深拷贝
浅拷贝(Shallow Copy)
浅拷贝 只复制对象本身,而不复制对象引用的成员。 对于引用类型的字段,浅拷贝会将原对象的引用复制到新对象中,而不会创建新对象实例。因此,原对象和拷贝对象中的引用字段将指向相同的内存地址。
浅拷贝的特点:
- 复制对象的时候,如果是基本数据类型会被完全复制。
- 对于引用数据类型,比如数组,集合,自定义对象等,都是复制引用而不是实际的数据对象。
- 浅拷贝通常是通过
Object.clone()
方法实现的。
示例:
java
class Person {
String name;
int[] age;
public Person(String name, int[] age) {
this.name = name;
this.age = age;
}
// 浅拷贝
public Person shallowCopy() {
try {
Person cloned = (Person) super.clone(); // 复制对象
return cloned;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
public class Main {
public static void main(String[] args) {
int[] ages = {25, 30, 35};
Person person1 = new Person("John", ages);
// 浅拷贝
Person person2 = person1.shallowCopy();
// 修改 person2 的 age 数组
person2.age[0] = 40;
System.out.println("person1's age: " + person1.age[0]); // 40
System.out.println("person2's age: " + person2.age[0]); // 40
}
}
person1
和 person2
的 age
数组是共享的,因为浅拷贝仅复制了 age
数组的引用。当 person2
修改了 age[0]
的值时,person1
的 age[0]
也发生了变化。
深拷贝
深拷贝是指不仅复制对象本身,还递归地复制对象所引用的所有对象。
深拷贝的特点:
- 深拷贝会复制对象及其所有引用对象。
- 每个引用类型字段都会被复制为一个全新的实例,因此原对象和拷贝对象中的引用字段指向不同的内存地址。
- 深拷贝通常需要手动实现,尤其是在对象中包含其他引用类型。
示例:
java
class Person implements Cloneable {
String name;
int[] age;
public Person(String name, int[] age) {
this.name = name;
this.age = age;
}
// 深拷贝
@Override
public Person clone() {
try {
Person cloned = (Person) super.clone(); // 复制对象
cloned.age = this.age.clone(); // 深拷贝数组
return cloned;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
public class Main {
public static void main(String[] args) {
int[] ages = {25, 30, 35};
Person person1 = new Person("John", ages);
// 深拷贝
Person person2 = person1.clone();
// 修改 person2 的 age 数组
person2.age[0] = 40;
System.out.println("person1's age: " + person1.age[0]); // 25
System.out.println("person2's age: " + person2.age[0]); // 40
}
}
person1
和 person2
的 age
数组是完全独立的,因为我们在 clone()
方法中对 age
数组进行了深拷贝。修改 person2
的 age[0]
不会影响 person1
的 age[0]
。
Coneable接口
clone()
方法是 Object
类的一部分,因此所有的 Java 类都可以通过实现 Cloneable
接口来使得自己支持克隆。然而,问题在于 Cloneable
接口本身并没有提供任何方法,它只是一个标志,表示该类允许被克隆。要想正确的实现克隆,需要我们需要在类中覆盖 clone()
方法。如果直接使用弗雷的clone()
方法,可能会在某些情况下得到不符合预期的结果。
建议
- 考虑使用构造函数代替
clone()
方法 - 如果决定重写
clone()
方法,需要调用super.clone()
来确保父类对象的字段也会被复制,如果没有,可能会导致父类无法正确的被克隆。 - 如果要处理字段是引用类型(对象)的情况时,需要对这些字段创建新的实例,从而确保是深拷贝。
clone()
方法必须声明为public
,因为它是从Object
类继承来的,默认是protected
,因此在覆盖时需要改变它的访问修饰符。clone()
方法必须抛出CloneNotSupportedException
异常,这是因为Cloneable
接口并不是强制要求实现的,如果一个类没有实现Cloneable
接口而调用clone()
,将会抛出这个异常。- 要么完全重写,要么不重写。如果重写的话,就需要全面考虑所有的字段。特别是当对象包含复杂的嵌套结构时,确保每一个引用字段都能正确的被复制。
- 复制最好通过构造器或者工厂来提供。