Clone克隆
1. 简述
- 克隆:使用clone()方法,可以快速的创建一个对象的副本,并且两个对象指向不同的内存地址,但克隆出来的对象的属性值是由被克隆对象初始化的。
- 实现克隆的条件:类必须实现Cloneable接口才能被克隆,类中必须重写clone()方法
- 异常:
Exception in thread "main" java.lang.CloneNotSupportedException
是因为在重写clone()方法类中没有实现Cloneable接口
2. 查看源码
- Cloneable接口源代码:
/**
* Cloneable此接口不包含clone()方法。 因此,只能通过实现该接口的事实来克隆对象是不可能的。
* 所以要实现克隆还需重写Object类中的clone()方法
*/
public interface Cloneable {
}
- clone()方法在Object类中的源代码:
/**
* clone()方法是由native关键字修饰的可见克隆是调用底层的C++实现的,不用调用构造函数
* 且native修饰的方法执行效率比非native修饰的高,故clone()方法是Java中实现复制对象最简单,最高效的手段
* clone()方法由protected修饰,当一个类在重写clone()方法时,需要修改成public访问修饰符,
* 方便其他类能够访问这个类的这个方法,避免出错
*/
protected native Object clone() throws CloneNotSupportedException;
3. New与 clone()的区别
-
new
new操作符的本意是分配内存。程序执行到new操作符时,首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对 象。
-
clone()
clone在第一步是和new相似的,都是分配内存,调用clone方法时,分配的内存和原对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。
-
复制引用和复制对象的区别
- 复制引用
- Student实体类代码:
package com.learn.beans; public class Student { // 学生属性 姓名 private String name; /** * 有参构造方法,初始化操作(给姓名赋值) * @param name */ public Student(String name) { this.name = name; } /** * 获取姓名 * @return */ public String getName() { return name; } /** * 设置姓名 * @param name */ public void setName(String name) { this.name = name; } }
- StudentTest代码:
package com.learn.test; import com.learn.beans.Student; public class StudentTest { public static void main(String[] args) { // 创建Student对象stu并初始化 name-->张三 Student stu = new Student("张三"); // 创建Student对象stuCopy并将stu赋给它 Student stuCopy = stu; // 输出打印stu对象的引用地址 System.out.println(stu); // 输出打印stuCopy对象的引用地址 System.out.println(stuCopy); // 输出打印stu对象的name System.out.println("未做更改前stu-->name为:"+stu.getName()); // 输出打印stuCopy对象的name System.out.println("未做更改前stuCopy-->name为:"+stuCopy.getName()); // 更改stuCopy的姓名 stuCopy.setName("李四"); // 输出打印stu对象的name System.out.println("更改后stu-->name为:"+stu.getName()); // 输出打印stuCopy对象的name System.out.println("更改后stuCopy-->name为:"+stuCopy.getName()); } }
- 结果:
com.learn.beans.Student@1540e19d com.learn.beans.Student@1540e19d 未做更改前stu-->name为:张三 未做更改前stuCopy-->name为:张三 更改后stu-->name为:李四 更改后stuCopy-->name为:李四
-
地址值是相同的,可以确定stu和stuCopy指向是同一个对象Student(“张三”)。内存中的情景如下图所示:
-
复制对象
-
Student实体类
package com.learn.beans; public class Student implements Cloneable{ // 学生属性 姓名 private String name; /** * 有参构造方法,初始化操作(给姓名、年龄赋值) * @param name */ public Student(String name) { this.name = name; } /** * 获取姓名 * @return */ public String getName() { return name; } /** * 设置姓名 * @param name */ public void setName(String name) { this.name = name; } /** * 重写clone方法 * @return * @throws CloneNotSupportedException */ @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
- StudentTest测试类
package com.learn.test; import com.learn.beans.Student; public class StudentTest { public static void main(String[] args) throws CloneNotSupportedException { // 创建Student对象stu并初始化 name-->张三 Student stu = new Student("张三"); // 调用clone()方法创建Student对象stuCopy Student stuCopy = (Student) stu.clone(); // 输出打印stu对象的引用地址 System.out.println(stu); // 输出打印stuCopy对象的引用地址 System.out.println(stuCopy); // 输出打印stu对象的name System.out.println("未做更改前stu-->name为:"+stu.getName()); // 输出打印stuCopy对象的name System.out.println("未做更改前stuCopy-->name为:"+stuCopy.getName()); // 更改stuCopy的姓名 stuCopy.setName("李四"); // 输出打印stu对象的name System.out.println("更改后stu-->name为:"+stu.getName()); // 输出打印stuCopy对象的name System.out.println("更改后stuCopy-->name为:"+stuCopy.getName()); } }
- 结果:
com.learn.beans.Student@1540e19d com.learn.beans.Student@677327b6 未做更改前stu-->name为:张三 未做更改前stuCopy-->name为:张三 更改后stu-->name为:张三 更改后stuCopy-->name为:李四
- 两个对象的地址是不同的,说明创建了新对象, 而不是把原对象的地址赋给了新的引用变量,内存中的情景如下图所示:
4. 浅拷贝与深拷贝
- 浅克隆shallow clone
- 上面的复制对象就是浅克隆
- 浅拷贝仔细分析图如下(上面的的图是为了直观)主要看堆里的Eden区,其实他们的name属性指向的是同一个String类型的对象 “张三”
- 问题先说: 为什么更改stuCopy的name属性而stu的还是张三并且他们的地址也不同?
因为String对象是不可更改的,更改就是重新分配内存重新创建,为什么“张三”还在,我认为是stu里的name还在引用着,而stuCopy更改它他要重新创建故地址和stu里的张三不一样,但他不是深克隆,因为String类没有实现Cloneable接口
- 深克隆deep clone
- clone对象时将对象里的属性也要clone一份,这要求属性是复合(引用)类型。例如在克隆学生对象时,连同学生对象的书包也要克隆一份
- 要求:克隆对象类和对象属性类都要实现Cloneable接口,类中都要重写clone方法
- 准备:student学生实体类,Bag书包类,DeepCloneTest测试类
- Bag.java
package com.learn.beans; public class Bag implements Cloneable { private String brand; // 品牌 public Bag(String brand) { this.brand = brand; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
- Student.java
package com.learn.beans; public class Student implements Cloneable{ private String name; // 学生姓名 private Bag bag; // 包 public Student(String name, Bag bag) { this.name = name; this.bag = bag; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Bag getBag() { return bag; } public void setBag(Bag bag) { this.bag = bag; } /** * 重写clone方法 * @return * @throws CloneNotSupportedException */ @Override public Object clone() throws CloneNotSupportedException { Student stu = (Student) super.clone(); // 实现自身的克隆 stu.bag = (Bag) stu.bag.clone(); // 实现属性的克隆 return stu; // 返回自身 } }
- DeepCloneTest.java
package com.learn.test; import com.learn.beans.Bag; import com.learn.beans.Student; public class DeepCloneTest { public static void main(String[] args) throws CloneNotSupportedException { // 创建Student对象stu并初始化 name-->张三 Student stu = new Student("张三",new Bag("耐克")); // 调用clone()方法创建Student对象stuCopy Student stuCopy = (Student) stu.clone(); // 输出打印stu对象的引用地址 System.out.println("stu------地址:" + stu); // 输出打印stuCopy对象的引用地址 System.out.println("stuCopy--地址:" + stuCopy); // stu bag的地址 System.out.println("stu-----bag--地址:" + stu.getBag()); // stuCopy bag的地址 System.out.println("stuCopy-bag--地址:" + stuCopy.getBag()); System.out.println("更改前:"); System.out.println("stu--bag:" + stu.getBag().getBrand() + "\tstuCopy--bag:" + stuCopy.getBag().getBrand()); stuCopy.getBag().setBrand("森马"); System.out.println("更改后:"); System.out.println("stu--bag:" + stu.getBag().getBrand() + "\tstuCopy--bag:" + stuCopy.getBag().getBrand()); } }
- 运行结果
stu------地址:com.learn.beans.Student@1540e19d stuCopy--地址:com.learn.beans.Student@677327b6 stu-----bag--地址:com.learn.beans.Bag@14ae5a5 stuCopy-bag--地址:com.learn.beans.Bag@7f31245a 更改前: stu--bag:耐克 stuCopy--bag:耐克 更改后: stu--bag:耐克 stuCopy--bag:森马
- 有结果可以看出stu–>stuCopy实现了对象拷贝,且bag属性也实现了属性对象的拷贝,内存如图:
5. 总结
- clone的实现实现需要类实现Cloneable接口,类中重写clone方法
- 复制引用和复制对象的区别在于复制后两个对象是否指向同一地址,同则为复制引用,反之复制对象
- 浅拷贝与深拷贝的区别在于复合(引用)属性对象是否拷贝,不拷贝则为浅拷贝,反之为深拷贝
- 注意:String类型的对象是不可更改的,更改即为重新创建,且其并未实现Cloneable接口,切莫与深拷贝混淆