JDK 之 如何实现clone方法

JDK clone方法相关知识总结。


JDK 之 如何实现clone方法

clone方法实现

测试类:Human、Student、Teacher、Matser

  • clone实现方式:

    1. Human类通过实现Clonable接口中clone方法
    2. 在clone方法中通过super.clone()调用父类Object的clone方法
    3. 对于需要修正的域进行修正,进行深度clone.
/*
 * @ProjectName: 编程学习
 * @Copyright:   2018 HangZhou Yiyuery Dev., Ltd. All Right Reserved.
 * @address:     https://yiyuery.club
 * @date:        2018/7/28 18:15
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.capsule.jdk.clone;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0
 * @date 2018/7/28 18:15
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人} 2018/7/28
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Data
@ToString
@Accessors(chain = true)
public class Human implements Cloneable{

    /**姓名*/
    private String name;
    /**性别 男:true 女:false*/
    private boolean sex;

    public Human() {
    }

    public Human(String name, boolean sex) {
        this.name = name;
        this.sex = sex;
    }

    /**
     * 重载克隆方法
     * @return
     */
    @Override
    public Object clone() {
        Human s;
        try {
            s =  (Human) super.clone();
        } catch (CloneNotSupportedException e) {
            throw  new AssertionError(); // Can't happen
        }
        return s;
    }
}

直接调用父类的clone方法会报CloneNotSupportedException异常

  1. 所有类默认缺省继承自Object,在Object 中 clone方法定义: protected native Object clone() throws CloneNotSupportedException;

  2. protected 修饰符限定了所有类中默认对基类 Object.clone可见。
  3. 直接调用super.clone,由方法定义可知,存在情况导致方法抛该异常,那么是什么情况会发生呢? Object.clone()在执行前,会判断当前类是否继承Cloneable接口,如果没有,就会抛 CloneNotSupportedException 这个异常。

Cloneable

  1. 应用Cloneable接口的类通常还必须重写一个public的clone()方法,如果不重写,由于Object.clone()是proteced属性,所以这个clone()方法将无法在外部被调用,更精确地说,无法在目标类之外的任何地方调用。这样就使得克隆失去了用武之地。或是通过公开方法来开放内部的私有方法的使用,这种看似鸡肋的代码设计,也可以达到相同的目的。
  2. 接口中只有一个clone方法,主要为公开和重载clone方法而设计。
  3. 运行时是否抛出CloneNotSupportedException跟是否重写了clone()方法没有关系,只跟是否继承了Cloneable接口有关系

浅复制

  1. Object的clone方法默认是对基本域进行复制,并返回一个新的对象,但是对于复杂对象,只传递了对象的引用,这在很多情况下是很危险的,因为你在修改当前clone得来的类时,会改变原来的对象中引用指向的对象
  2. 如果当前类有应用Clonable接口,则为当前类创建一个新对象,并将原对象中的所有字段进行一次浅层拷贝(通过赋值进行)。所以如果一个目标类应用了Clonable接口但并未重写clone()方法,Object.clone()毕竟只是提供了浅层拷贝,对于基本类型的字段,可以说它成功克隆了。但对于对象型字段,它并没有实现克隆的功能,仅仅做了一个赋值,即对象引用的传。

    浅复制测试代码:

  • Teacher:
/*
 * @ProjectName: 编程学习
 * @Copyright:   2018 HangZhou Yiyuery Dev., Ltd. All Right Reserved.
 * @address:     https://yiyuery.club
 * @date:        2018/7/28 15:57
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.capsule.jdk.clone;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * <p>
 *  老师对象
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0
 * @date 2018/7/28 15:57
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人} 2018/7/28
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Data
@ToString
@Accessors(chain = true)
public class Teacher implements Cloneable{

    /**姓名*/
    private String name;
    /**学科*/
    private String subject;

}
  • Matser:
/*
 * @ProjectName: 编程学习
 * @Copyright:   2018 HangZhou Yiyuery Dev., Ltd. All Right Reserved.
 * @address:     https://yiyuery.club
 * @date:        2018/7/29 10:09
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.capsule.jdk.clone;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * <p>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0
 * @date 2018/7/29 10:09
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人} 2018/7/29
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Data
@ToString
@Accessors(chain = true)
public class Master implements Cloneable{

    private String name;

    private StringBuffer sb = new StringBuffer("");

    public Master() {
        sb.append("first create.");
    }

    public Master(String name) {
        this.name = name;
        sb.append("first create.");
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Master m = new Master("zhaizhai");
        Master m2 = (Master) m.clone();
        System.out.println(m.toString());
        System.out.println(m2.toString());

        System.out.println("------------------------");
        //变更属性
        m.getSb().append("first append");
        System.out.println(m.toString());
        System.out.println(m2.toString());

        //Master(name=zhaizhai, sb=first create.)
        //Master(name=zhaizhai, sb=first create.)
        //------------------------
        //Master(name=zhaizhai, sb=first create.first append)
        //Master(name=zhaizhai, sb=first create.first append)

        //不重写Object.clone()方法的话在目标类内部仍然可以调用目标类.clone(),
        //但浅层拷贝导致m和m2共享同一个StringBuffer对象,这是很危险的

    }
}

  • CloneTest:

/*
 * @ProjectName: 编程学习
 * @Copyright:   2018 HangZhou Yiyuery Dev., Ltd. All Right Reserved.
 * @address:     https://yiyuery.club
 * @date:        2018/7/28 10:59
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.capsule.jdk.clone;

import org.junit.Test;
import org.springframework.util.Assert;

/**
 * <p>
 *  jdk clone 对象
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0
 * @date 2018/7/28 10:59
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人} 2018/7/28
 * @modify reason: {方法名}:{原因}
 * ...
 */
public class CloneTest {

    /**
     * clone复制 [继承、对象嵌套]
     */
    @Test
    public void cloneStudent(){
        Student s = new Student("zhangsan",Boolean.FALSE);
        s.setTeacher(new Teacher().setName("zhailaoshi").setSubject("yuwen"));
        Student s2 = (Student)s.clone();
        s2.getTeacher().setName("zhaizhai");
        Assert.isTrue(s.equals(s2)); //h.equals(h2) true
        System.out.println(s.toString());
        System.out.println(s2.toString());
        //Student(super=Human(name=zhangsan, sex=false), teacher=Teacher(name=zhaizhai, subject=yuwen))
        //Student(super=Human(name=zhangsan, sex=false), teacher=Teacher(name=zhaizhai, subject=yuwen))
    }

    /**
     * clone复制 [简单参数、clone实质是新建一个对象引用,但是属性和被克隆对象一致]
     */
    @Test
    public void cloneHuman(){
        Human h = new Human().setName("zhangsan").setSex(Boolean.FALSE);
        Human h2 = (Human)h.clone();
        h2.setName("new zhangsan");
        Assert.isTrue(!h.equals(h2)); // h.equals(h2) false
        System.out.println(h.toString());
        System.out.println(h2.toString());
        //Human(name=zhangsan, sex=false)
        //Human(name=new zhangsan, sex=false)
    }
}

clone的重写

  1. 重写clone()方法不一定需要应用Cloneable接口,因为只有执行Object.clone()方法才会做这个检查。但如果重写的clone()方法中调用了super.clone(),那就必须应用Cloneable接口。自定义clone属性的话,返回一个相同属性的对象也是可以达到克隆的目的。
  2. 应用Cloneable接口的好处在于,它可以允许你安全地调用super.clone(),从而快速地产生一个浅拷贝,之后只需要在重写的公共clone()方法中修改必须修改的字段,如那些不允许共享实例的对象。
  3. 对于需要克隆的类,如果其中含有其他对象,在重写clone的时候,需要认识到:clone就是一个构造器,你必须确保它不会伤害到原始对象,并确保正确的创建被克隆对象中得约束条件。

深度复制 调整Student、Teacher的clone达到深度clone的目标:

继承已重写clone的父类Human
  • Teacher
/*
* @ProjectName: 编程学习
* @Copyright:   2018 HangZhou Yiyuery Dev., Ltd. All Right Reserved.
* @address:     https://yiyuery.club
* @date:        2018/7/28 15:57
* @email:       xiazhaoyang@live.com
* @description: 本内容仅限于编程技术学习使用,转发请注明出处.
*/
package com.capsule.jdk.clone;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
* <p>
*  老师对象
* </p>
*
* @author xiazhaoyang
* @version V1.0
* @date 2018/7/28 15:57
* @modificationHistory=========================逻辑或功能性重大变更记录
* @modify By: {修改人} 2018/7/28
* @modify reason: {方法名}:{原因}
* ...
*/
@Data
@ToString
@Accessors(chain = true)
public class Teacher extends Human{

  /**姓名*/
  private String name;
  /**学科*/
  private String subject;

  @Override
  public Object clone(){
      return super.clone();
  }

}

自己继承Cloneable接口实现clone方法:
  • Teacher:

/*
* @ProjectName: 编程学习
* @Copyright:   2018 HangZhou Yiyuery Dev., Ltd. All Right Reserved.
* @address:     https://yiyuery.club
* @date:        2018/7/28 15:57
* @email:       xiazhaoyang@live.com
* @description: 本内容仅限于编程技术学习使用,转发请注明出处.
*/
package com.capsule.jdk.clone;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
* <p>
*  老师对象
* </p>
*
* @author xiazhaoyang
* @version V1.0
* @date 2018/7/28 15:57
* @modificationHistory=========================逻辑或功能性重大变更记录
* @modify By: {修改人} 2018/7/28
* @modify reason: {方法名}:{原因}
* ...
*/
@Data
@ToString
@Accessors(chain = true)
public class Teacher implements Cloneable{

  /**姓名*/
  private String name;
  /**学科*/
  private String subject;

  @Override
  public Object clone(){
      try {
          return super.clone();
      } catch (CloneNotSupportedException e) {
          throw new RuntimeException();
      }
  }

}

重写Student的clone方法,对于内部的对象型引用,新建对象或使用clone方法的嵌套来实现深度clone的目的。
  • Student:

/*
 * @ProjectName: 编程学习
 * @Copyright:   2018 HangZhou Yiyuery Dev., Ltd. All Right Reserved.
 * @address:     https://yiyuery.club
 * @date:        2018/7/28 11:00
 * @email:       xiazhaoyang@live.com
 * @description: 本内容仅限于编程技术学习使用,转发请注明出处.
 */
package com.capsule.jdk.clone;

import lombok.Data;
import lombok.ToString;
import lombok.experimental.Accessors;

/**
 * <p>
 *
 *  <li>拷贝对象返回的是一个新对象,而不是一个引用。<li/>
 *  <li>拷贝对象与用 new 操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息</li>
 *
 * </p>
 *
 * @author xiazhaoyang
 * @version V1.0
 * @date 2018/7/28 11:00
 * @modificationHistory=========================逻辑或功能性重大变更记录
 * @modify By: {修改人} 2018/7/28
 * @modify reason: {方法名}:{原因}
 * ...
 */
@Data
@ToString(callSuper = true)
@Accessors(chain = true)
public class Student extends Human{

    /**班主任*/
    private Teacher teacher;

    public Student(String name, boolean sex) {
        super(name,sex);
    }

    public Student(String name, boolean sex,Teacher teacher) {
        super(name, sex);
        this.teacher = teacher;
    }


    /**
     * 重载克隆方法
     * @return
     */
    @Override
    public Object clone() {
        Student s = (Student) super.clone();
        s.setTeacher((Teacher) teacher.clone());
        //s.setTeacher(new Teacher(teacher.getName()))
        return s;
    }


}

测试深复制后的效果


/**
 * 修改Student的clone方法,并让Teacher继承Human以获得clone方法
 */
@Test
public void deepClone(){
    Student s = new Student("zhangsan",Boolean.FALSE);
    s.setTeacher(new Teacher().setName("zhailaoshi").setSubject("yuwen"));
    Student s2 = (Student)s.clone();
    s2.getTeacher().setName("zhaizhai");
    Assert.isTrue(!s.equals(s2)); //h.equals(h2) false
    System.out.println(s.toString());
    System.out.println(s2.toString());

    //Student(super=Human(name=zhangsan, sex=false), teacher=Teacher(name=zhailaoshi, subject=yuwen))
    //Student(super=Human(name=zhangsan, sex=false), teacher=Teacher(name=zhaizhai, subject=yuwen))
}


总结

  1. clone实现方式需要继承Cloneable接口
  2. 重写clone方法主要为了内部对象型成员域的深度clone和调整clone方法的可见域,确保在其他类中不会因为clone方法被protect修饰而无法使用。
  3. clone的使用主要目的其实类似于对象的一个构造器,返回一个相同属性的对象。
  4. 所有的clone的重写方法的实现需要优先调用super.clone(),并修正任何需要修正的域。
  5. 如果需要对线程安全的类实现Cloneable接口,clone方法需要得到很好的同步。
  6. clone方法深度赋值的缺陷往往可以被用来进行特殊场景下的程序设计,因为他们内部的对象型的引用都是共享的,这对于关键域的传递貌似是个可以利用的优点。

更多

扫码关注“架构探险之道”,回复文章名称获取更多源码和文章资源

在这里插入图片描述

知识星球(扫码加入获取源码和文章资源链接)

在这里插入图片描述

坚持原创技术分享,您的支持将鼓励我继续创作!