Java对象克隆
Java提供了克隆的机制,Object类有clone方法,用于对象克隆。
clone()和Cloneable接口
Cloneable是一个用于标记的接口,接口没有任何声明方法
clone()方法,是Object类定义的方法(protected),该方法会将对象的所有字段进行复制
如果一个类没有实现Cloneable接口,并且它的实例调用了clone()方法,就会抛出异常
怎样克隆?
- 如果一个类允许被克隆,则要实现Cloneable接口。
-
覆盖clone()方法。Object中的clone()方法被protected修饰,覆盖时要用public修饰
class Test implements Cloneable{ private Date date; public Test clone() { Test clone = null; try { clone = (Test) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return clone; } }
调用clone()方法,就可以得到与母本独立的副本吗?
Object.clone()方法,对象所占内存空间的硬拷贝(直接复制二进制)这样显然是不行的,Object.clone()方法,只是将引用复制了,并没有创建新的Date对象,就导致复制出来的Test对象中的date,和母本指向同一个Date对象(母本.date == 副本.date)。
怎样保证母本和副本独立?(进行“深拷贝”,关于这个概念稍后再说)
引用类型不可以直接赋值
public class Clone {
public static void main(String[] args) {
Date d1 = new Date();
Date d2 = d1;
System.out.println("修改前--------------------------------");
System.out.println("d1:" + d1 + ", d2=" + d2);
System.out.println("只修改d1---------------------");
d1.setDate(1);
System.out.println("d1:" + d1 + ", d2=" + d2);
}
}
运行结果:
对于一个对象来说,如果直接复制引用,就会导致两个引用指向一个对象(在C里,就是两个指针指向同一个对象)。这样导致的结果就是,母本和副本是同一个对象,使用一个引用引起的变化,会导致另一个引用指向的同一个对象发生变化,显然副本和母本是不独立的。
对于引用类型(可变对象的引用类型),克隆时不能简单的使用直接赋值的方式,而是调用这个对象的clone()方法
class Test implements Cloneable{
private Date date;
public Test clone() {
Test clone = null;
try {
clone = (Test) super.clone();
clone.date = (Date) this.date.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
对于不可变对象的引用类型,例如String,克隆时可以和基本类型一样,使用直接赋值的方式
“浅拷贝”和“深拷贝”
“浅拷贝”:只是简单的复制,不会考虑副本和母本是否有公用的对象(直接调用Object.clone)。
“深拷贝”:保证副本和母本之间相互独立。
深拷贝覆盖clone()方法的规则:
- 首先,调用super.clone()方法。
- 对于可变对象的引用类型的字段,通过调用它的clone方法,进行克隆。
解析:
调用super.clone()方法,克隆基本类型和不可变对象的引类型字段;以及克隆父类成员。
如果一个对象可以被修改(任何一个字段),则这个类就是可变的,所以,对于可变的对象,要进行单独的克隆。
举例:
class Test implements Cloneable{
int a;
private Date date1;
private Date date2;
public Test clone() {
Test clone = null;
try {
clone = (Test) super.clone();
clone.date1 = (Date) this.date1.clone();
clone.date2 = (Date) this.date2.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
关于clone() 方法的注意事项
1.不会执行构造方法(函数)
public class Test implements Cloneable{
int a;
public Test(int a) {
this.a = a;
System.out.println("执行Test构造方法");
}
@Override
public Test clone() {
Test clone = null;
try {
clone = (Test)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
return clone;
}
public static void main(String[] args) {
Test t = new Test(1);
Test t1 = t.clone();
}
}
构造方法只执行了一次,对象克隆时,并没有执行构造方法。
2.被final修饰的字段无法“深拷贝”
public class Test implements Cloneable{
int a;
final Test b = null;
@Override
public Test clone() {
Test clone = null;
try {
clone = (Test)super.clone();
/**********下面这条语句会报错**********/
clone.b = this.b.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
return clone;
}
}
报错:不能对final字段赋值。
对final字段进行初始化只能在构造方法或声明的时候,初次之外,无法再次进行赋值。
但是,如果不进行“深拷贝”, 也就是删掉报错的那句话,就不会报错,但是这样就是“浅拷贝”,这个字段可以被多个对象修改,很不安全。