对象克隆+深浅拷贝

【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();
      }
   }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值