对象克隆+深浅拷贝

转载 2015年11月17日 22:49:25

【0】README

0.1) 本文描述+源代码均 转自 core java volume 1, 旨在理解 对象拷贝 的概念 , 特别是对 深拷贝和浅拷贝 的理解;
0.2) 最后,我们还要看一个 clone 的荔枝;


【1】对象克隆相关

1.1)出现的问题: 当copy一个变量时, 原始变量和 copy 变量引用同一个对象, 也就是说, 改变一个变量所引用的对象将会 对另一个变量产生影响;

Employee original = new Employee("tang", 50000);
Employee copy = original;
copy.raiseSalary(10);//oops -- also changed original

这里写图片描述
1.2)解决方法:

  • 1.2.1)如果创建一个对象的新的copy, 它的最初状态与 original一样, 但以后将可以各自改变各自的状态, 那就需要使用clone 方法:
Employee copy = original.clone();
copy.raiseSalary(10); // OK--original unchanged
  • 1.2.2)不过,事情没有那么简单。clone 方法是 Object类的 protected方法, 也就是说, 在用户编写的代码中不能直接调用它, 只有Employee 类才能克隆 Employee 对象;
  • 1.2.3)浅拷贝:如果对象中的所有数据域都属于数值或者基本类型, 只有copy 域是没有问题的。 但是,如果 在对象中包含了子对象的引用, copy 的结果 会使得域 引用同一个子对象, 因此原始对象与 克隆对象共享这部分信息, 这就叫做浅拷贝; 要知道, Object类的clone方法克隆Employee 对象的结果, 可以看到, 默认的克隆操作是 浅拷贝, 它并没有克隆包含在对象中的内部对象;
    这里写图片描述

【2】浅拷贝和深拷贝

2.1)深拷贝:如果原始对象 与 浅拷贝对象共享的子对象是不可变的, 将不会产生任何问题;然而,更常见的 是 子对象是可变的, 因此必须重新定义 clone 方法, 以便实现 克隆子对象的 深拷贝;
2.2)在列举的实例中, hireDay域 属于 Date类, 这就是一个可变的子对象;
2.3)对于每一个类, 都需要做如下判断:

  • 2.3.1)默认的clone方法是否满足要求;
  • 2.3.2)默认的 clone 方法是否能够通过 调用可变子对象的 clone 得到修补;
  • 2.3.3)是否不应该使用 clone;

2.4)实际上, 选项3是默认的, 如果要选择1或2, 类必须:

  • 2.4.1)实现 Cloneable 接口;
  • 2.4.2)使用 public 访问修饰符重新定义 clone方法;

Attention)

  • A1) 在Object类中,clone方法被说明为 protected, 因此无法直接调用 anObject.clone()。但是, 不是所有子类都可以访问 受保护的方法吗?不是每个类都是Object的子类吗?值得庆幸的是, 受保护访问的规则极为微妙。子类只能调用受保护的clone方法克隆它自己。为此, 必须重新定义clone方法, 并将它声明为 public, 这样才能够让 所有方法克隆对象;
  • A2) Cloneable接口的出现与接口的正常使用没有任何关系。尤其是, 它并没有指定clone方法, 这个是从 Object类继承来的。接口在这里只是作为一个标记, 表明类设计者知道要进行克隆处理。如果一个对象需要克隆,而没有实现Cloneable 接口,就会产生一个已检验异常;
  • A3) Cloneable接口是 java提供的几个标记接口之一, 我们知道, 通常使用接口的目的是为了确保类实现某个特定的方法或一组特定的方法, Comparable接口就是这样一个实例。而标记接口没有方法, 使用它的唯一目的是可以用 instanceof 进行类型检查: if(obj instanceof Cloneable) , 但是我们建议不这样使用;

2.5)即使clone 的默认实现(浅拷贝)能够满足需求, 也应该实现 Cloneable接口, 将 clone 重定义为 public, 并调用super.clone();

class Employee implements Cloneable
{
    public Employee() throws CloneNotSupportedException
    {
        return (Employee)super.clone();
    }
}

2.6)为了实现深拷贝, 必须克隆所有可变的实例域(Key):

class Employee implements Cloneable
{
    public Employee() throws CloneNotSupportedException
    {
        Employee cloned = (Employee)super.clone();
        cloned.hireDay = (Date)hireDay.clone();
    }
}

2.7)只要在 clone 中含有没有实现 Cloneable接口的对象,Object类的 clone方法就会抛出一个CLoneNotSupportedException异常。所以,我们需要声明异常:

public Employee clone() throws CloneNotSupportedException
  • 2.7.1)如果将 上面的形式替换为 try-catch呢?
public Employee() 
{
    try
    {
        return (Employee)super.clone();
    }
    catch(CloneNotSupportedException e) {return null;}
}

Attention)以上写法比较适用于 final类,否则最好还是保留 throws的形式;
2.8)必须谨慎地实现子类的克隆

  • 2.8.1)如, 一旦 Employee定义了clone方法,那就可以用它来克隆 Manager对象。Employee的克隆方法能够完成这项任务吗? 这将取决于 Manager类中包含哪些域;在 Manager类中有可能存在需要深拷贝的域, 或者包含一些没有实现 Cloneable 接口的域;
  • 2.8.2)没有人能够保证子类实现的clone 一定正确。鉴于这个原因, 应该将 Object类中的clone方法声明为 protected;

2.9)在自定义的类中应该实现 clone方法吗?
如果客户需要深拷贝,那就应该实现它。而且, 克隆的应用并不像人们想象的那样普遍, 在标准类库中, 只有不到 5%的类实现了 clone;
Annotation)所有的数组类型均包含clone方法, 这个方法被设为 public, 而不是 protected。 可以利用 这个 方法创建一个包含所有数据元素拷贝的一个新数组,如:

int[] luckyNumbers = {2, 3, 5, 7, 11, 13];
int[] cloned = luckyNumbers.clone();
cloned[5] = 12; // doesn't change luckyNumbers[5]

【3】看个荔枝:

package com.corejava.chapter6_2;

import java.util.Date;
import java.util.GregorianCalendar;

public class Employee implements Cloneable
{
   private String name;
   private double salary;
   private Date hireDay;

   public Employee(String n, double s)
   {
      name = n;
      salary = s;
      hireDay = new Date();
   }

   public Employee clone() throws CloneNotSupportedException
   {
      // call Object.clone()
      Employee cloned = (Employee) super.clone();

      // clone mutable fields
      cloned.hireDay = (Date) hireDay.clone();

      return cloned;
   }

   /**
    * Set the hire day to a given date. 
    * @param year the year of the hire day
    * @param month the month of the hire day
    * @param day the day of the hire day
    */
   public void setHireDay(int year, int month, int day)
   {
      Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();

      // Example of instance field mutation
      hireDay.setTime(newHireDay.getTime());
   }

   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }

   public String toString()
   {
      return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
   }
}

package com.corejava.chapter6_2;

/**
 * This program demonstrates cloning.
 * @version 1.10 2002-07-01
 * @author Cay Horstmann
 */
public class CloneTest
{
   public static void main(String[] args)
   {
      try
      {
         Employee original = new Employee("John Q. Public", 50000);
         original.setHireDay(2000, 1, 1);

         Employee copy = original.clone();
         copy.raiseSalary(10);
         copy.setHireDay(2002, 12, 31);

         System.out.println("original=" + original);
         System.out.println("copy=" + copy);
      }
      catch (CloneNotSupportedException e)
      {
         e.printStackTrace();
      }
   }
}

java克隆(深浅拷贝,复制)详解

1.浅复制与深复制概念 (1)浅复制(浅克隆)被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用仍然只指向原来的对象,换言之,浅复制仅仅复制锁考虑的对象,而不复制它所引用的对象。...

java克隆(深浅拷贝,复制)详解

1.浅复制与深复制概念(1)浅复制(浅克隆)被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用仍然只指向原来的对象,换言之,浅复制仅仅复制锁考虑的对象,而不复制它所引用的对象。(2...

【C/C++学院】0820-Nullptr/const对象/类指针引用以及mallocfree与newde/类重载运算符/QT加法重载/类的重载赋值运算/自增在前在后差别/赋值重载深浅拷贝/重载下标

Nullptr #include void go(int num) { std::cout
  • waldmer
  • waldmer
  • 2015年10月24日 09:50
  • 947

python对象的深浅拷贝

python对象的赋值实际上是简单的对象引用。也就是说当你创建一个对象然后把它赋值给另一个变量的时候,python并没有拷贝这个对象,而是这个对象的引用。参考下面的例子: 新建列表li,并把li赋值...

C++/C#中堆栈、对象内存模型、深浅拷贝、Array.Clone方法

目录1.      C++/C#中对象内存模型................................................................................

JavaScript深度克隆(深度拷贝)一个对象

我有一个前端笔试题:使用JavaScript深度克隆一个对象。可是我发现大多数人都是空白,问他为什么不做,大部分说不懂这题目的意思。 科普一下: js一般有两种不同数据类型的值: 基...

java中对象的拷贝(克隆)

有时候我们需要拿到一个对象的一份拷贝,不能简单的通过传值的方式来解决。我们知道,在java中,只有基本类型可以通过赋值的方式来拷贝。比如有一个Person类,然后申明了p1,p2两个Person类型的...

ActionScript 3.0 对象克隆 & 深拷贝

对象克隆可以复制出一个和已存在对象相同的对象,并且两个对象没有关联。ActionScript 3.0中克隆对象使用的是ByteArray类。以前一直以为这个方法只对数组有用,其实这个方法适用于所有的对...
  • cceevv
  • cceevv
  • 2012年07月19日 23:21
  • 2145

Java序列化和克隆--对象深度拷贝

Java序列化和克隆--对象深度拷贝http://hi.baidu.com/en_wan/item/32bf32140b1baafcdceeca92(觉着不错所以就转来了) Java序列化和...

ios内存管理之深浅拷贝

  • 2014年03月21日 20:56
  • 38.65MB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:对象克隆+深浅拷贝
举报原因:
原因补充:

(最多只允许输入30个字)