源码介绍
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
x.clone() != x
will be true
译文
创建并返回此对象的副本。“复制”的确切含义取决于此类的运行时类,即是浅copy还是深copy。
x.clone() != x
will be true
克隆对象和原对象不是同一个对象,占用不同的内存地址。
clone()方法的作用是什么
1、想一想,为什么需要克隆?为什么不重新new一个?道理很简单,目的是想要两个相同的对象,重新new一个还得自己重新赋值,太麻烦。
2、已经存在一个对象A,现在需要一个与对象A完全相同的B对象,并对B对象的值进行修改,但是A对象的原有的属性值不能改变。这时,如果使用java提供的对象赋值语句,当修改B对象值后,A对象的值也会被修改。那么应该如何实现创建一个和对象A完全相同的对象B,而且修改对象B时,对象A的属性值不被改变呢?
要实现这一功能,可以使用Object类中的clone方法。clone方法可以完成对象的浅克隆。所谓浅克隆就是说被克隆的对象的各个属性都是基本类型,而不是引用类型(接口、类、数组),如果存在引用类型的属性,则需要进行深克隆。
怎么使用
由于 Object 本身没有实现 Cloneable 接口,所以不重写 clone 方法并且进行调用的话会发生 CloneNotSupportedException 异常。
如下所示:
package protect.package1;
public class Person {
public static void main(String[] args) throws CloneNotSupportedException {
Person p=new Person();
p.clone();
}
}
以上代码Person类没有实现Cloneable 接口,直接调用clone方法会抛出CloneNotSupportedException异常。
Exception in thread "main" java.lang.CloneNotSupportedException: protect.package1.Person
at java.lang.Object.clone(Native Method)
at protect.package1.Person.main(Person.java:8)
故要使类具有克隆能力能力时,需要实现Cloneable接口(该接口是一个空接口,只是用来表明这个对象是允许克隆的)。如果实现了这个接口,类和它所有的超类都无需调用构造器就可以创建对象。
一般使用中为什么要重写
让其它类能调用这个类的clone()方法(具体可见不重写clone方法时的可见性),重写之后要把clone()方法的属性设置为public。
不重写clone方法时的可见性
clone方法是protected方法,可见性是java.lang包及其所有子类
子类指的是在子类中可以访问,如下所示,clone方法在Test1中进行调用时,是不可见的,所以会编译失败。
同一个包访问,doSomeThing方法也是protected方法,但是Test1类和Father类是在同一个包下,所以是可见的。如图三所示,Test2和Father类不在同一个包下,故在Test2中,doSomeThing方法是不可见的,故编译失败。
图三
浅克隆
浅克隆:原对象和克隆对象引用地址不同,但对象内的引用成员引用地址相同
例子如下
Teacher类
package object;
public class Teacher implements Cloneable{
private String name;
private Integer age;
private Course course;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Course getCourse() {
return course;
}
public void setCourse(Course course) {
this.course = course;
}
@Override
protected Teacher clone() throws CloneNotSupportedException {
return (Teacher) super.clone();
}
@Override
public String toString() {
return "Teacher [name=" + name + ", age=" + age + ", course=" + course + "]";
}
}
Course类
package object;
public class Course {
private String name;
private Integer id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "Course [name=" + name + ", id=" + id + "]";
}
}
测试类
package object;
public class CloneTest {
public static void main(String[] args) {
Teacher t1 = new Teacher();
t1.setName("Kevin");
t1.setAge(22);
Course c1 = new Course();
c1.setName("Math");
c1.setId(66);
t1.setCourse(c1);
System.out.println("teacher1"+t1);
try{
Teacher t2 = t1.clone();
System.out.println("Clone teacher2 from teacher1...");
System.out.println("teacher2"+t2);
System.out.println("Alter teacher2...");
t2.setName("Ryan");
t2.setAge(18);
//修改courese属性
t2.getCourse().setName("English");
t2.getCourse().setId(88);
System.out.println("teacher1"+t1);
System.out.println("teacher2"+t2);
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
}
}
测试结果
当我们修改克隆对象teacher2的course属性的时候,teacher1的course属性也被修改了。可以推断出teacher2和teacher1的引用属性course具有相同的地址,即是同一个course对象。
由此我们可以推断,调用clone方法产生的效果是:在内存中开辟一块和原始对象一样的空间,然后拷贝原始对象中的内容。但是对于基本数据类型,这样的操作是没有问题的,但对于非基本类型,它们保存的仅仅是对象的引用,这就是为什么clone后的非基本类型变量和原始对象中相应的变量会指向的是同一个对象。
深克隆
深克隆:原对象和克隆对象引用地址不同,且对象内的引用成员引用地址也不同
浅克隆的例子稍微修改一下就行
1、Teacher类clone方法修改成如下所示
2、Course类
添加实现接口如下所示
public class Course implements Cloneable
添加本类的clone方法
protected Course clone() throws CloneNotSupportedException {
return (Course) super.clone();
}
测试类和上面的一致结果如下
由以上运行结果可知,进行深度克隆后,对clone产生的teacher2对象的course属性进行修改时,并未影响到原对象teacher1的course属性。
若Course类中还含有成员引用,则又需要再让它实现Cloneable接口重写clone方法,这样代码会显得很臃肿,且繁琐。
利用序列化和反序列化实现深克隆
在Teacher中添加如下方法并且实现Serializable 接口(空接口,表明该类可序列化)
public class Teacher implements Serializable
public Teacher myClone(){
Teacher stu = null;
try {
//将对象序列化到流里
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(this);
//将流反序列化成对象
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
ObjectInputStream ois = new ObjectInputStream(is);
stu = (Teacher) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return stu;
}
需要注意的是成员引用也需要实现Serializable接口
public class Course implements Serializable
这种方法是利用序列化对象后可将其拷贝到流里,而原对象仍在jvm中,然后从流中将其反序列化成另一个对象到jvm中,从而实现深克隆。
深度克隆的其他情况
基本类型包括Integer,Long等对应的引用类型和String类型可以自动实现深度克隆
StringBuffer不能实现深度克隆(StringBuffer没有重写clone()方法,并且StringBuffer还是一个final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone),如果一个类中包括该类型的属性,要么只能实现浅clone,要么自己重新生成对象: new StringBuffer(oldValue.toString()); 进行赋值。
总结
- 克隆可分为浅克隆和深克隆,实际应用中一般使用深克隆
- 深克隆有两种实现方法。实现Cloneable接口和利用序列化和反序列化(简单方便)
参考