Clonable接口
在抽象类和接口的博客中,我们讲到了两个我们会常用到的接口,一个是Compareable接口,一个是Comperator接口,现在我们再来讲一个我们会经常用到的接口,也就是我们的Clonable接口.
首先我们来思考一个问题:如何进行对象的拷贝?
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 "拷贝". 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.
我们先来看Clonable接口的源码:
我们可以看到这类接口什么都没有,那么我们称这类接口为空接口,也就是标记接口.
空接口的意义:标记当前这个类是可以被克隆的.克隆会做一件事情,拷贝一份副本.
同时关于对象的拷贝会牵扯出之前我们所提到的深拷贝和浅拷贝的问题,下面我们来进行对比:
Clonable接口结合深拷贝问题与浅拷贝问题
首先一个对象的引用如果想要进行克隆,那么其对应的类必须实现Clonable 接口,并且重写Object类中的clone方法,并且重写的clone方法后必须抛出CloneNotSupportedException 异常.
Clonable接口结合深拷贝
我们直接通过代码来进行解析:先来看一段代码:
1.class Person implements Cloneable { 2. public int age; 3. @Override 4. //此处的clone方法必须抛出CloneNotSupportedException异常 5. protected Object clone() throws CloneNotSupportedException { 6. return super.clone(); 7. } 8.} 9.//此处的主函数也需要抛出这个异常 10. public static void main(String[] args) throws CloneNotSupportedException { 11. Person person = new Person(); 12. /*注意对于下面的调用clone方法有两点需要注意: 13. 1:首先引用在调用clone方法时main方法需要抛出CloneNotSupportedException异常 14. 2:因为clone方法的返回值为引用类型Object,所以如果是其他类定义的引用就需要强制类型转换 15. */ 16. Person person1 = (Person) person.clone(); 17. System.out.println(person.age); 18. System.out.println(person1.age); 19. System.out.println("===========对比==========="); 20. person1.age = 10; 21. System.out.println(person.age); 22. System.out.println(person1.age); 23. }
此段代码的输出结果为:
此时我们会发现首先我们对person所指向的对象进行了拷贝,并把它赋给了一个新的引用person1,那么此时在内存是什么样的呢?我们来看下图所示:
此时我们可以看到通过clone方法我们在堆上克隆了一个副本,并将这个副本对象赋给了一个新的引用,并且当我们修改person1这个引用所指向对象中所包含的简单类型age的值时,person这个引用所指向对象中所包含i的简单类型age的值并没有发生改变,那么这种情况我们便称之为深拷贝,即当拷贝结束后,通过一个新的引用修改所拷贝的新的对象的其中的某个类型的值时,并不影响原来引用所对应的相同对象中的相同类型的值,那么此时便为深拷贝
Clonable接口结合浅拷贝
下面我们来介绍浅拷贝:
因为之前堆上的对象内部存储都是简单类型,所以都是深拷贝,那么如果存储引用类型后,就会发生浅拷贝了,下面来看代码:
1.class Money { 2. double money = 12.6; 3.} 4. 5.class Person implements Cloneable { 6. public int age; 7. Money m = new Money(); 8. 9. @Override 10. //此处的clone方法必须抛出CloneNotSupportedException异常 11. protected Object clone() throws CloneNotSupportedException { 12. return super.clone(); 13. } 14.} 15. 16.public class TestDemo { 17. public static void main(String[] args) throws CloneNotSupportedException { 18. Person person = new Person(); 19. Person person1 = (Person) person.clone(); 20. System.out.println(person.m.money); 21. System.out.println(person1.m.money); 22. System.out.println("===========对比==========="); 23. person1.m.money = 99.9; 24. System.out.println(“修改后为”+person.m.money); 25. System.out.println(“修改后为”+person1.m.money); 26. } 27.}
此时我们新建了一个Money类,并在Person类中加入了Money类的实例,那么此时Person类的实例在堆上的存储中便会多出一个引用类型,那么来看下其在内存上的存储示意图吧:
此时我们会发现我们通过clone方法仅仅将简单类型age和引用类型m克隆了过来,但是m所指向的对象我们并没有克隆过来,那么两者的m引用都将会指向同一个对象
此时如果我们通过person1.m.money去修改money的值为99.9时,我们会发现原引用person所对应的money值也随之变成了99.9,那么这种现象我们称之为浅拷贝,即其当一个新的引用去修改其克隆过来的对象中的某个类型(此段代码为引用类型)的值后,原引用调用这个类型时便会发现这个值为新修改后的值了,并不是原来的值。
所以上述代码中的最终结果我们会发现当其中一个引用修改了值后,另一个引用去调用时也会得到相同的值,我们来看运行结果以证实我们的猜想吧
果然就算修改过后两者的值依然相等
将深拷贝改为浅拷贝的方法
此时就会有同学有疑问了,上述代码如何可以实现深拷贝呢?
答案非常简单:此时我们让Money类实现Cloneable接口就好。目的就是为了将Money类的实例也能通过clone方法拷贝过来,下面来看代码:
1.class Money implements Cloneable { 2. double money = 12.6; 3. 4. @Override 5. protected Object clone() throws CloneNotSupportedException { 6. return super.clone(); 7. } 8.} 9. 10.class Person implements Cloneable { 11. public int age; 12. Money m = new Money(); 13. 14. //重写Object类中的clone方法 ,并且必须抛出CloneNotSupportedException异常 15. @Override 16. protected Object clone() throws CloneNotSupportedException { 17. //克隆person所指向的对象 18. Person p = (Person) super.clone(); 19. //克隆Person类中的另一个引用类型m所指向的对象 20. p.m = (Money) this.m.clone(); 21. return p; 22. } 23.} 24. 25.public class TestDemo { 26. public static void main(String[] args) throws CloneNotSupportedException { 27. Person person = new Person(); 28. Person person1 = (Person) person.clone(); 29. System.out.println(person.m.money); 30. System.out.println(person1.m.money); 31. System.out.println("===========对比==========="); 32. person1.m.money = 99.9; 33. System.out.println("修改后的结果为"+person.m.money); 34. System.out.println("修改后的结果为"+person1.m.money); 35. } 36.}
内存示意图:
最终的输出结果为:
此时我们发现当通过语句person1.m.money修改money值后,并不影响person引用所指向对象中的m引用所指向对象中的money值,那么此时便发生了深拷贝,我们的目的达到了。