本文涉及到的概念
1.浅拷贝和深拷贝
2..clone方法的作用和使用方式
3.拷贝构造器和拷贝工厂
 
1.浅拷贝和深拷贝
浅拷贝
一个类实现Cloneable接口,然后,该类的实例调用clone方法,返回一个新的实例。
新的实例与原来的实例是不同的对象。
新的实例的各个非引用类型的成员变量值与原来的实例的成员变量值相同。
对于引用类型的成员变量,新实例的成员变量和旧实例的成员变量,都是指向相同的对象,引用值相同,这种称作浅拷贝(shallow copying)

  1. public class Car implements Cloneable {
  2.  
  3. private Object containedObj1 = new Object();
  4. private Object containedObj2 = new Object();
  5.  
  6. public Object getObj1() {
  7. return containedObj1;
  8. }
  9.  
  10. public Object getObj2() {
  11. return containedObj2;
  12. }
  13.  
  14. @Override
  15. protected Object clone() throws CloneNotSupportedException {
  16.  
  17. return (Car) super.clone();
  18.  
  19. }
  20.  
  21. public Car() {
  22.  
  23. }
  24.  
  25. public static void main(String[] args) {
  26.  
  27. try {
  28. Car obj1 = new Car();
  29. Car obj2 = (Car) obj1.clone();
  30.  
  31. System.out.println("obj1 and obj2 are same:" + (obj1 == obj2));
  32. System.out.println("obj1.containedObj1 and obj2.containedObj1 are same:" + (obj1.getObj1() == obj2.getObj1()));
  33. System.out.println("obj1.containedObj2 and obj2.containedObj2 are same:" + (obj1.getObj2() == obj2.getObj2()));
  34. System.out.println("obj1.str and obj2.str are same:" +(obj2.getString() == obj1.getString()));
  35. System.out.println("obj1.data:" + obj1.getData()+"; obj2.data:" + obj2.getData());
  36. System.out.println("obj1.dataf:" + obj1.getDataf()+"; obj2.dataf:" + obj2.getDataf());
  37. } catch (CloneNotSupportedException e) {
  38. e.printStackTrace();
  39. }
  40.  
  41. }
  42. }
输出结果:
obj1 and obj2 are same:false
obj1.containedObj1 and obj2.containedObj1 are same:true
obj1.containedObj2 and obj2.containedObj2 are same:true
obj1.str and obj2.str are same:true
obj1.data:1024; obj2.data:1024
obj1.dataf:1024.1024; obj2.dataf:1024.1024
 
深拷贝(deep copying)
使用拷贝构造方法来实现深拷贝
深拷贝与浅拷贝的区别是,对于引用类型的成员变量,经过深拷贝之后,两者的值不相等,它们不是指向相同的对象,所以引用值不同。
深拷贝就是,对于非引用类型的成员变量复制值,对于引用类型的成员变量,创建新的对象,将新对象的地址复制给引用类型的成员变量
 
下面并没有使用clone方法来实现深拷贝,阅读相关资料后,发现使用clone方法来实现深拷贝,限制和问题太多了。

  1. public class Car2 {
  2. private Object containedObj1 = new Object();
  3. private Object containedObj2 = new Object();
  4.  
  5. private String str = "oneStr";
  6.  
  7. private int data = 1024;
  8. private float dataf = 1024.1024f;
  9.  
  10. public Car2() {
  11.  
  12. }
  13.  
  14. public Car2(Car2 car) {
  15.  
  16. this.str = new String(car.getString().toString());
  17. this.data = car.getData();
  18. this.dataf = car.getDataf();
  19.  
  20. }
  21.  
  22. private String getString() {
  23. return str;
  24. }
  25.  
  26. public int getData() {
  27. return data;
  28. }
  29.  
  30. public float getDataf() {
  31. return dataf;
  32. }
  33.  
  34. public Object getObj1() {
  35. return containedObj1;
  36. }
  37.  
  38. public Object getObj2() {
  39. return containedObj2;
  40. }
  41.  
  42. public static void main(String[] args) {
  43.  
  44. Car2 obj1 = new Car2();
  45. Car2 obj2 = new Car2(obj1);
  46.  
  47. System.out.println("obj1 and obj2 are same:" + (obj1 == obj2));
  48. System.out.println("obj1.containedObj1 and obj2.containedObj1 are same:" + (obj1.getObj1() == obj2.getObj1()));
  49. System.out.println("obj1.containedObj2 and obj2.containedObj2 are same:" + (obj1.getObj2() == obj2.getObj2()));
  50. System.out.println("obj1.str and obj2.str are same:" +(obj2.getString() == obj1.getString()));
  51. System.out.println("obj1.data:" + obj1.getData()+"; obj2.data:" + obj2.getData());
  52. System.out.println("obj1.dataf:" + obj1.getDataf()+"; obj2.dataf:" + obj2.getDataf());
  53.  
  54. }
  55. }
obj1 and obj2 are same:false
obj1.containedObj1 and obj2.containedObj1 are same:false
obj1.containedObj2 and obj2.containedObj2 are same:false
obj1.str and obj2.str are same:false
obj1.data:1024; obj2.data:1024
obj1.dataf:1024.1024; obj2.dataf:1024.1024
 
2.clone方法的作用和使用方式
使用clone方法来实现深拷贝,就是一个坑。使用clone来实现浅拷贝,是不错的选择,但是不适合用来实现深拷贝。
 
使用clone实现深拷贝
Body类含有引用类型成员变量head,Head类含有引用类型成员变量
现在要对Body类实现深度拷贝,使用clone方法来实现,方式是,对于要被深度拷贝的类实现Cloneable接口,重载clone方法,返回类型是当前类。
Body类要实现深拷贝,实现Cloneable接口,重载clone方法
Body newBody = (Body)super.clone(),调用Body类的父类,实现Body各个非引用类型的变量的浅拷贝。
接着,要对Body的引用类型成员变量head进行深拷贝,newBody.head = (Head) head.clone(); 执行之后,可以实现Head类的浅拷贝,而对Body来说,head变量就是深拷贝了,新的Body.head的引用值指向新的Head对象。
 
也就是,要对某个类实现深拷贝,那么,就必须保证类中的引用类型成员变量,这些类它们实现了Cloneable接口,然后,我们再在当前类中实现Cloneable接口,对每一个引用类型的成员变量调用clone方法。
 
这还只是能确保当前成员变量被深度拷贝了,不能保证当前成员变量中的引用类型成员变量被深度拷贝。
  1. @Override
  2. protected Object clone() throws CloneNotSupportedException {
  3. Body newBody = (Body) super.clone();
  4. newBody.head = (Head) head.clone();
  5. return newBody;
  6. }
  1. public class Body implements Cloneable {
  2. public Head head;
  3.  
  4. public Body() {
  5. }
  6.  
  7. public Body(Head head) {
  8. this.head = head;
  9. }
  10.  
  11. @Override
  12. protected Object clone() throws CloneNotSupportedException {
  13. Body newBody = (Body) super.clone();
  14. newBody.head = (Head) head.clone();
  15. return newBody;
  16. }
  17.  
  18. public static void main(String[] args) throws CloneNotSupportedException {
  19.  
  20. Body body = new Body(new Head());
  21.  
  22. Body body1 = (Body) body.clone();
  23.  
  24. System.out.println("body == body1 : " + (body == body1));
  25.  
  26. System.out.println("body.head == body1.head : " + (body.head == body1.head));
  27.  
  28. }
  29. }
  30.  
  31. class Head implements Cloneable/* implements Cloneable */ {
  32. public Face face;
  33.  
  34. public Head() {
  35. }
  36.  
  37. public Head(Face face) {
  38. this.face = face;
  39. }
  40.  
  41. @Override
  42. protected Object clone() throws CloneNotSupportedException {
  43. return super.clone();
  44. }
  45.  
  46. }
  47.  
  48. class Face {
  49.  
  50. }
输出结果:
body == body1 : false
body.head == body1.head : false
 
只是保证对当前的引用类型的变量进行深度拷贝
Body实现了Cloneable,Head实现了Cloneable;Head中的Face类型没有实现Cloneable;
 
对Body进行深拷贝,Body中的Head类型的成员变量被深度拷贝了,但是Head里面的Face类型的成员变量进行的是浅拷贝。
  1. public class Body2 implements Cloneable {
  2. public Head head;
  3.  
  4. public Body2() {
  5. }
  6.  
  7. public Body2(Head head) {
  8. this.head = head;
  9. }
  10.  
  11. @Override
  12. protected Object clone() throws CloneNotSupportedException {
  13. Body2 newBody = (Body2) super.clone();
  14. newBody.head = (Head) head.clone();
  15. return newBody;
  16. }
  17.  
  18. public static void main(String[] args) throws CloneNotSupportedException {
  19.  
  20. Body2 body = new Body2(new Head(new Face()));
  21.  
  22. Body2 body1 = (Body2) body.clone();
  23.  
  24. System.out.println("body == body1 : " + (body == body1));
  25.  
  26. System.out.println("body.head == body1.head : " + (body.head == body1.head));
  27.  
  28. System.out.println("body.head.face == body1.head.face : " + (body.head.face == body1.head.face));
  29.  
  30. }
  31. }
  32.  
  33. class Head implements Cloneable/* implements Cloneable */ {
  34. public Face face;
  35.  
  36. public Head() {
  37. }
  38.  
  39. public Head(Face face) {
  40. this.face = face;
  41. }
  42.  
  43. @Override
  44. protected Object clone() throws CloneNotSupportedException {
  45. return super.clone();
  46. }
  47.  
  48. }
  49.  
  50. class Face {
  51.  
  52. }
输出结果:
body == body1 : false
body.head == body1.head : false
body.head.face == body1.head.face : true
 
 
3.拷贝构造器和拷贝工厂
通过上面的例子,我们可以知道,如果只是实现浅拷贝,可以使用clone方法(对要被浅拷贝的类,该类要实现Cloneable接口)。如果使用Clone方法来实现深拷贝,那将是一个坑。
<<effective Java>>中推荐使用拷贝构造器和拷贝工厂的方式来实现对象的拷贝。

下面的代码,使用了拷贝构造器和拷贝工厂来实现对象的拷贝
创建一个对象m101,然后,使用m101作为参数,拷贝它,使用拷贝构造器来实现,得到一个新的对象m101CopyOne;
使用m101CopyOne对象作为参数,通过拷贝工厂方法,活动一个新的对象m101CopyTwo
  1. public final class Galaxy {
  2.  
  3. /**
  4. * Regular constructor.
  5. */
  6. public Galaxy(double aMass, String aName) {
  7. fMass = aMass;
  8. fName = aName;
  9. }
  10.  
  11. /**
  12. * Copy constructor.
  13. */
  14. public Galaxy(Galaxy aGalaxy) {
  15. this(aGalaxy.getMass(), aGalaxy.getName());
  16. //no defensive copies are created here, since
  17. //there are no mutable object fields (String is immutable)
  18. }
  19.  
  20. /**
  21. * Alternative style for a copy constructor, using a static newInstance
  22. * method.
  23. */
  24. public static Galaxy newInstance(Galaxy aGalaxy) {
  25. return new Galaxy(aGalaxy.getMass(), aGalaxy.getName());
  26. }
  27.  
  28. public double getMass() {
  29. return fMass;
  30. }
  31.  
  32. /**
  33. * This is the only method which changes the state of a Galaxy
  34. * object. If this method were removed, then a copy constructor
  35. * would not be provided either, since immutable objects do not
  36. * need a copy constructor.
  37. */
  38. public void setMass(double aMass){
  39. fMass = aMass;
  40. }
  41.  
  42. public String getName() {
  43. return fName;
  44. }
  45.  
  46. // PRIVATE
  47. private double fMass;
  48. private final String fName;
  49.  
  50. /** Test harness. */
  51. public static void main (String... aArguments){
  52. Galaxy m101 = new Galaxy(15.0, "M101");
  53.  
  54. Galaxy m101CopyOne = new Galaxy(m101);
  55. m101CopyOne.setMass(25.0);
  56. System.out.println("M101 mass: " + m101.getMass());
  57. System.out.println("M101Copy mass: " + m101CopyOne.getMass());
  58.  
  59. Galaxy m101CopyTwo = Galaxy.newInstance(m101);
  60. m101CopyTwo.setMass(35.0);
  61. System.out.println("M101 mass: " + m101.getMass());
  62. System.out.println("M101CopyTwo mass: " + m101CopyTwo.getMass());
  63. }
  64. }
  65. 输出结果:
  66. M101 mass: 15.0
  67. M101Copy mass: 25.0
  68. M101 mass: 15.0
  69. M101CopyTwo mass: 35.0
总结:
拷贝一个对象,避免使用Clone的方式,使用拷贝构造器和拷贝工厂的方法来获得一个新的对象。同时也避免使用序列化反序列化的方式来实现对象的拷贝。
Clone的方式,只适合用来实现浅拷贝。
 

谨慎重载clone方法的更多相关文章

  1. Java中clone方法的使用

    什么是clone 在实际编程过程中,我们常常要遇到这种情况:有一个对象object1,在某一时刻object1中已经包含了一些有效值,此时可能会需要一个和object1完全相同新对象object2,并 ...

  2. Effective Java —— 谨慎覆盖clone

    本文参考 本篇文章参考自<Effective Java>第三版第十三条"Always override toString",在<阿里巴巴Java开发手册>中 ...

  3. Effective Java 第三版——13. 谨慎地重写 clone 方法

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  4. Effective Java 之-----谨慎的覆盖clone方法

    1.概述 如果clone方法返回一个由构造器创建的对象,它就得到有错误的类.因此,如果覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone得到的对象.如果类的所有超类都 ...

  5. 第十一条:谨慎的覆盖clone()方法

    一个类要想实现克隆,需要实现Cloneable接口,表明这个类的对象具有克隆的功能. Cloneable接口是一个mixin接口,它里面并没有任何的抽象方法,类似的接口有Serializable接口, ...

  6. Java 深度克隆 clone()方法重写 equals()方法的重写

    1.为什么要重写clone()方法? 答案:Java中的浅度复制是不会把要复制的那个对象的引用对象重新开辟一个新的引用空间,当我们需要深度复制的时候,这个时候我们就要重写clone()方法. 2.为什 ...

  7. .NET 基础 一步步 一幕幕[面向对象之方法、方法的重载、方法的重写、方法的递归]

    方法.方法的重载.方法的重写.方法的递归 方法: 将一堆代码进行重用的一种机制. 语法: [访问修饰符] 返回类型 <方法名>(参数列表){ 方法主体: } 返回值类型:如果不需要写返回值 ...

  8. Object类clone方法的自我理解

    网上搜帖: clone()是java.lang.Object类的protected方法,实现clone方法: 1)类自身需要实现Cloneable接口 2)需重写clone()方法,最好设置修饰符mo ...

  9. PHP面向对象编程——深入理解方法重载与方法覆盖(多态)

    什么是多态? 多态(Polymorphism)按字面的意思就是“多种状态”.在面向对象语言中,接口的多种不同的实现方式即为多态.引用Charlie Calverts对多态的描述——多态性是允许你将父对 ...

随机推荐

  1. web.py 中文模版报错

    1. 作为模板的html文件,必须是utf-8编码; 2. html文件内容中的charset必须为utf-8,也就是必须包含 <meta http-equiv="Content-Ty ...

  2. PHP关于传众多参数还是传上下文对象的性能测试

    在开发微信公众平台平台的过程中,有这么几个参数总是需要传来传去,$userOpenId,$message,$time. 在整个程序的运行过程中,为了函数方便的处理,将这三个变量一直放在参数列表里.关于 ...

  3. bootstrap练习制作网页

    导航条 <nav class="navbar navbar-default"> <div class="container-fluid"> ...

  4. CentOS 6.5 下安装 Redis

    wget http://download.redis.io/redis-stable.tar.gz tar xvzf redis-stable.tar.gz cd redis-stable make ...

  5. 【uoj#225】[UR #15]奥林匹克五子棋 构造

    题目描述 两个人在 $n\times m$ 的棋盘上下 $k$ 子棋,问:是否存在一种平局的情况?如果存在则输出一种可能的最终情况. 输入 第一行三个正整数 $n,m,k$ ,意义如前所述. 输出 如 ...

  6. [BZOJ3507]通配符匹配

    3507: [Cqoi2014]通配符匹配 Time Limit: 10 Sec  Memory Limit: 128 MB Description 几乎所有操作系统的命令行界面(CLI)中都支持文件 ...

  7. CF1093F Vasya and Array DP

    题面 题面 \(\Delta\)题面有点问题,应该是数列中没有长度大于等于\(len\)的连续数字才是合法的. 题解 设\(f[i][j]\)表示DP到\(i\)位,以\(j\)为结尾的方案数, \( ...

  8. 【Cogs2187】帕秋莉的超级多项式(多项式运算)

    [Cogs2187]帕秋莉的超级多项式(多项式运算) 题面 Cogs 题解 多项式运算模板题 只提供代码了.. #include<iostream> #include<cstdio& ...

  9. Unity3D手游开发日记(6) - 适合移动平台的水深处理

    市面上大部分的手机游戏,水面都比较粗糙,也基本没发现谁做过水深的处理. 水深的处理在PC平台比较容易,因为很容易获得每个像素的深度,比如G-Buffer,有了像素的深度,就能计算出每个像素到水面的距离 ...

  10. 并发时-修改Linux系统下的最大文件描述符限制

    通常我们通过终端连接到linux系统后执行ulimit -n 命令可以看到本次登录的session其文件描述符的限制,如下: $ulimit -n1024 当然可以通过ulimit -SHn 1024 ...