为什么要克隆
克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”时就需要clone方法了。那么把这个对象的临时属性一个一个的赋值给新new的对象呢?一来麻烦,二来,clone是一个native方法,在底层实现的,快。
常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。而通过clone方法赋值的对象跟原来的对象时同时独立存在的,但两个对象的成员在对象复制后的情况却有两种形式。
浅复制与深复制
浅复制与深复制的主要区别在于是否支持对引用类型的成员变量的复制。
浅复制:
- 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)。
- 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。
//复制自己,返回Object 类型的自己。
java.lang.Object;
protected native Object clone() throws CloneNotSupportedException;
//只有实现了Cloneable接口,并重写clone方法的类才会继承Object的clone方法
java.lang.Cloneable;
public interface Cloneable { //标记接口
}
浅复制:
public class COM {
public static void main(String[] args) throws Exception {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
addr.setAdd("西湖区");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
class Address {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
}
class Student implements Cloneable{ //步骤1
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() { //步骤2
Student stu = null;
try{
stu = (Student)super.clone(); //步骤2
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
Output:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:西湖区
解析:
学生1对象的addr修改后,学生2对象的addr也同时修改了。原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另外一块空间,将addr的值(“西湖区”)复制到新地址空间后,再将引用(地址)返回给复制对象的addr成员。
为了达到真正的复制对象,而不是纯粹复制对象的引用,需要将Address类也变成可复制化(实现接口,重写clone),并且修改Student的clone方法。
String类型的成员不用实现clone,因为String是不可变类型,一旦为原对象的String成员赋新值,原对象的String成员就不再与复制对象的String成员指向同一块空间。
深复制:
public class COM {
public static void main(String[] args) throws Exception {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
addr.setAdd("西湖区");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
class Address implements Cloneable{
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address addr = null;
try {
addr = (Address)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
//每个类的引用成员都要调用clone,而该成员的类要实现接口
stu.addr = (Address)addr.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
Output:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:杭州市
在浅克隆中,若原型对象的成员变量是值类型,则会复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象;也就是说原型对象和克隆对象的同一个引用类型的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象;深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
序列化实现深复制
当对象的引用类型成员变量里面还包含很多引用类型时,上述方法太麻烦。