Java中的 深拷贝 与 浅拷贝
深入深拷贝与浅拷贝之前,先复习一下Java中的访问修饰符,特别是protected,再去理解深/浅拷贝的知识。
Java中的访问修饰符
-
public:
用public修饰的类、类属变量及方法:包内及包外的任何类(包括子类和普通类)均可以访问;
-
protected:
用protected修饰的类、类属变量及方法,包内的任何类及包外那些继承了该类的子类才能访问,protected重点突出继承;
protected修饰符的修饰的成员变量和方法也称为受保护的成员变量和方法,
受保护的成员变量和方法:
-
可以在本类或同一个包中的其它类(包括子类)中通过类的实例进行访问
-
也可以被同一个包中的类或不同包中的类继承,但是不能在不同包中的其它类(包括子类)中通过该类的实例进行访问。
-
-
default:
如果一个类、类属变量及方法没有用任何修饰符(即没有用public、protected及private中任何一种修饰),则其访问权限为default(默认访问权限)。默认访问权限的类、类属变量及方法,包内的任何类(包括继承了此类的子类)都可以访问它,而对于包外的任何类都不能访问它(包括包外继承了此类的子类)。default重点突出包;
-
private:
用private修饰的类、类属变量及方法,只有本类可以访问,而包内包外的任何类均不能访问它。
对象克隆
Object类中实现了一种叫做clone的方法,这种方法clone出来的新对象为浅拷贝,而通过自己定义的clone方法为深拷贝。
实际上,clone() 方法是 Object类中的一个protected方法,这说明你的代码不能直接调用,只有Employee
类可以克隆Employee
对象。
这个限制是有原因的,看看Object类如何实现clone() 。它对这个对象一无所知,所以只能逐个域进行拷贝。
如果含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样一来,原对象和克隆的对象仍然会共享一些信息。
这就是浅拷贝
对于子对象属于不可变类,如String,浅拷贝是安全的。
浅拷贝
public class Address{
private String city;
public Address(String city) {
this.city = city;
}
// ... getter and setter
// ... toString
}
public class Employee implements Cloneable{
private String name;
private int age;
private Address address;
public Employee(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
// ... getter and setter
// ... toString
@Override
protected Employee clone(){
Employee employee = null;
try {
employee = (Employee)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return employee;
}
}
@Test
public void testClone(){
System.out.println("克隆之前");
Address address = new Address("深圳市");
Employee employee_before = new Employee("been", 18, address);
System.out.println("employee_before: "+ employee_before);
System.out.println("克隆之后");
Employee employee_after = employee.clone();
employee1.getAddress().setCity("广州市");
employee1.setName("mad");
employee1.setAge(23);
System.out.println("employee_after: " + employee_after);
System.out.println("employee_before: " + employee_before);
}
输出结果会发现,第一个员工对象的名字和年龄和第二个员工对象的名字和年龄不一致,说明name(基本类型)和age(基本类型)克隆成功,即修改第二个员工的名字和年龄不会影响到第一个员工的名字和年龄。
但是,修改完第二个员工的地址后发现第一个员工的地址也发生了变化,说明 Address(引用类型)克隆不成功,即修改的是同一个对象,如果想把Address(引用类型)克隆成功,需要深拷贝。
深拷贝
要想实现 Address(引用类型)的深克隆,首先让Address类实现 Cloneable 接口,重写clone方法
public class Address implements Cloneable{
private String city;
public Address(String city) {
this.city = city;
}
// ... getter and setter
// ... toString
@Override
protected Address clone(){
Address address = null;
try {
address = (Address)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return address;
}
}
在Employee类的clone方法里添加一行代码:employee.address = address.clone();
public class Employee implements Cloneable{
private String name;
private int age;
private Address address;
public Employee(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
// ... getter and setter
// ... toString
@Override
protected Employee clone(){
Employee employee = null;
try {
employee = (Employee)super.clone();
employee.address = address.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return employee;
}
}
这次修改第二个员工的地址不会影响第一个员工的address,说明深克隆成功。
总结:
如果需要克隆的类中只有基本类型的话,那么只在该类中实现Cloneable接口即可,如果该类中除了包含基本类型之外,还有引用类型的话,需要将该引用对象也要实现Cloneable接口,并重写clone方法。