什么是 clone
在实际编程过程中, 我们常常要遇到这种情况: 有一个对象 object1, 在某一时刻 object1 中已经包含了一些有效值, 此时可能会需要一个和 object1 完全相同新对象 object2, 并且此后对 object2 任何改动都不会影响到 object1 中的值, 也就是说, object1 与 object2 是两个独立的对象, 但 object2 的初始值是由 object1 对象确定的. 在 Java 语言中, 用简单的赋值语句是不能满足这种需 求的. 要满足这种需求虽然有很多途径, 但实现 clone()方法是其中最简单, 也是最高效的手段.
Java 的所有类都默认继承 java.lang.Object 类, 在 java.lang.Object 类中有一个方法 clone(), 该方法在 Object 中的定义如下:
- /**
- * Class Object is the root of the class hierarchy. Every class has Object as a superclass.
- * All objects, including arrays, implement the methods of this class.
- */
- public class Object {
- /**
- * Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
- * The general intent is that, for any object {@code x}, the expression: x.clone() != x will be true,
- * and that the expression: x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
- * While it is typically the case that: x.clone().equals(x) will be true, this is not an absolute requirement.
- */
- protected native Object clone() throws CloneNotSupportedException;
- }
从上面对 clone 方法的注解可知 clone 方法的通用约定: 对于任意一个对象 x, 表达式x.clone != x 将会是 true; 表达式x.clone().getClass()==x.getClass()将会是 true, 但不是绝对的; 表达式x.clone().equals(x)将会是 true, 但是这也不是绝对的.
从源代码可知, 根类 Object 的 clone 方法是用 protected 关键字修饰, 这样做是为避免我们创建每一个类都默认具有克隆能力.
如何使用 clone 方法
要使类具有克隆能力能力时, 需要实现 Cloneable 接口, 实现它的目的是作为一个对象的一个 mixin(混入)接口, 表明这个对象是允许克隆的. 它的源码如下:
- /**
- * A class implements the Cloneable interface to indicate to the {@link java.lang.Object#clone()} method that
- * it is legal for that method to make a field-for-field copy of instances of that class.
- */
- public interface Cloneable {
- }
可以看出 Cloneable 是一个空接口(标记接口), 它决定了 Object 中受保护的 clone 方法的实现行为: 如果一个类实现了 Cloneable 接口, Object 的 clone 方法就返回这个对象的逐域拷贝, 否则就抛出 CloneNotSupportedException 异常. 如果实现了这个接口, 类和它所有的超类都无需调用构造器就可以创建对象. 下面通过一个简单的实例来演示 clone 方法的使用.
编写一个被克隆对象 Student 类:
- package com.kevin.clone;
- /**
- * 创建一个简单实例演示 clone 方法
- * @author Kevin
- *
- */
- public class Student implements Cloneable {
- private String name;
- private String gender;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getGender() {
- return gender;
- }
- public void setGender(String gender) {
- this.gender = gender;
- }
- @Override
- protected Student clone() throws CloneNotSupportedException {
- return (Student)super.clone();
- }
- @Override
- public String toString() {
- return "[name=" + name + ", gender=" + gender + "]";
- }
- }
编写测试代码:
- package com.kevin.clone;
- /**
- * 测试 clone 方法
- * @author Kevin
- *
- */
- public class test_clone {
- public static void main(String[] args){
- Student student1 = new Student();
- student1.setName("Kevin");
- student1.setGender("Male");
- System.out.println("student1"+student1);
- try{
- Student student2 = student1.clone();
- System.out.println("Clone student2 from student1...");
- System.out.println("student2"+student2);
- System.out.println(student1.equals(student2));
- System.out.println("Alter student2...");
- student2.setName("Alice");
- student2.setGender("Female");
- System.out.println("student1"+student1);
- System.out.println("student2"+student2);
- }catch(CloneNotSupportedException e){
- e.printStackTrace();
- }
- }
- }
输出结果:
- student1 [name=Kevin, gender=Male]
- Clone student2 from student1...
- student2 [name=Kevin, gender=Male]
- false
- Alter student2...
- student1 [name=Kevin, gender=Male]
- student2 [name=Alice, gender=Female]
分析:
一是希望能实现 clone 功能的 CloneClass 类实现了 Cloneable 接口, 这个接口属于 java.lang 包, java.lang 包已经被缺省的导入类中, 所以不需要写成 java.lang.Cloneable.
二是重载了 clone()方法. 最 后在 clone()方法中调用了 super.clone(), 这也意味着无论 clone 类的继承结构是什么样的, super.clone()直接或间接调 用了 java.lang.Object 类的 clone()方法. 下面再详细的解释一下这几点.
最后仔细观察一下 Object 类的 clone()一个 native 方法, native 方法的效率一般来说都是远高于 java 中的非 native 方法. 这也解释了为什么要用 Object 中 clone()方法而不是先 new 一个类, 然后把原始对象中的信息赋到新对象中, 虽然这也实现了 clone 功能. 对于第二点, 也要观察 Object 类中的 clone()还是一个 protected 属性的方法. 这也意味着如果要应用 clone()方 法, 必须继承 Object 类, 在 Java 中所有的类是缺省继承 Object 类的, 也就不用关心这点了. 然后重载 clone()方法. 还有一点要考虑的是为 了让其它类能调用这个 clone 类的 clone()方法, 重载之后要把 clone()方法的属性设置为 public.
那么 clone 类为什么还要实现 Cloneable 接口呢? 需要注意的是, Cloneable 接口是不包含任何方法的, 其实这个接口仅仅是一个标志, 而且 这个标志也仅仅是针对 Object 类中 clone()方法的, 如果 clone 类没有实现 Cloneable 接口, 并调用了 Object 的 clone()方 法 (也就是调用了 super.Clone() 方法), 那么 Object 的 clone()方法就会抛出 CloneNotSupportedException 异常.
影子克隆和深度克隆
下面通过一个实例来演示什么是影子克隆.
编写一个 Teacher 类(其中包含一个 Course 属性):
- package com.kevin.clone;
- /**
- * 测试影子克隆方法
- * @author Kevin
- *
- */
- public class Teacher implements Cloneable{
- private String name;
- private Integer age;
- private Course course;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Integer getAge() {
- return age;
- }
- public void setAge(Integer age) {
- this.age = age;
- }
- public Course getCourse() {
- return course;
- }
- public void setCourse(Course course) {
- this.course = course;
- }
- @Override
- protected Teacher clone() throws CloneNotSupportedException {
- return (Teacher) super.clone();
- }
- @Override
- public String toString() {
- return "Teacher [name=" + name + ", age=" + age + ", course=" + course + "]";
- }
- }
- package com.kevin.clone;
- public class Course {
- private String name;
- private Integer id;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- @Override
- public String toString() {
- return "Course [name=" + name + ", id=" + id + "]";
- }
- }
编写一个测试类:
- package com.kevin.clone;
- public class Course {
- private String name;
- private Integer id;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- @Override
- public String toString() {
- return "Course [name=" + name + ", id=" + id + "]";
- }
- }
输出结果如下:
- teacher1 [name=Kevin, age=22, course=Course [name=Math, id=66]]
- Clone teacher2 from teacher1...
- teacher2 [name=Kevin, age=22, course=Course [name=Math, id=66]]
- Alter teacher2...
- teacher1 [name=Kevin, age=22, course=Course [name=English, id=88]]
- teacher2 [name=Ryan, age=18, course=Course [name=English, id=88]]
通过分析结果可知, 当我们修改克隆对象 teacher2 的时候, teacher1 的 course 属性也被修改了, 如果通过查看内存地址的形式我们可以发现, 他们两个 course 其实是同一个对象. 由此我们可以推断, 调用 clone 方法产生的效果是: 现在内存中开辟一块和原始对象一样的空间, 然后拷贝原始对象中的内容. 但是对于基本数据类型, 这样的操作是没有问题的, 但对于非基本类型, 它们保存的仅仅是对象的引用, 这就是为什么 clone 后的非基本类型变量和原始对象中相应的变量会指向的是同一个对象. 这就是所谓的影子克隆.
为了解决影子克隆所产生的问题, 我们就需要使用深度克隆方案. 通过对以上实例改进后的方案如下:
- package com.kevin.clone;
- /**
- * 测试影子克隆方法
- * @author Kevin
- *
- */
- public class Teacher implements Cloneable{
- private String name;
- private Integer age;
- private Course course;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Integer getAge() {
- return age;
- }
- public void setAge(Integer age) {
- this.age = age;
- }
- public Course getCourse() {
- return course;
- }
- public void setCourse(Course course) {
- this.course = course;
- }
- @Override
- protected Teacher clone() throws CloneNotSupportedException {
- Teacher teacher = (Teacher)super.clone();
- teacher.course = course.clone();
- return teacher;
- }
- @Override
- public String toString() {
- return "[name=" + name + ", age=" + age + ", course=" + course + "]";
- }
- }
- package com.kevin.clone;
- public class Course implements Cloneable{
- private String name;
- private Integer id;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Integer getId() {
- return id;
- }
- public void setId(Integer id) {
- this.id = id;
- }
- protected Course clone() throws CloneNotSupportedException{
- return (Course)super.clone();
- }
- @Override
- public String toString() {
- return "Course [name=" + name + ", id=" + id + "]";
- }
- }
同样使用原来的测试类:
- package com.kevin.clone;
- /**
- * 测试 clone 方法
- * @author Kevin
- *
- */
- public class test_clone2 {
- public static void main(String[] args){
- Teacher t1 = new Teacher();
- t1.setName("Kevin");
- t1.setAge(22);
- Course c1 = new Course();
- c1.setName("Math");
- c1.setId(66);
- t1.setCourse(c1);
- System.out.println("teacher1"+t1);
- try{
- Teacher t2 = t1.clone();
- System.out.println("Clone teacher2 from teacher1...");
- System.out.println("teacher2"+t2);
- System.out.println("Alter teacher2...");
- t2.setName("Ryan");
- t2.setAge(18);
- // 修改 courese 属性
- t2.getCourse().setName("English");
- t2.getCourse().setId(88);
- System.out.println("teacher1"+t1);
- System.out.println("teacher2"+t2);
- }catch(CloneNotSupportedException e){
- e.printStackTrace();
- }
- }
- }
输出结果:
- teacher1 [name=Kevin, age=22, course=Course [name=Math, id=66]]
- Clone teacher2 from teacher1...
- teacher2 [name=Kevin, age=22, course=Course [name=Math, id=66]]
- Alter teacher2...
- teacher1 [name=Kevin, age=22, course=Course [name=Math, id=66]]
- teacher2 [name=Ryan, age=18, course=Course [name=English, id=88]]
分析: 由以上运行结果可知, 进行过深度克隆之后, 对 clone 产生的 teacher2 对象的 course 属性进行修改时, 并未影响到原对象 teacher1 的 course 属性.
任何类都可以实现深度 clone 吗
答案是否定的, 例如, StringBuffer, 看一下 JDK API 中关于 StringBuffer 的说明, StringBuffer 没有重载 clone()方法, 更为严重的是 StringBuffer 还是一个 final 类, 这也是说我们也不能用继承的办法间接实现 StringBuffer 的 clone. 如果一个类中包含有 StringBuffer 类型对象或和 StringBuffer 相似类的对象, 我们有两种选择: 要么只能实现影子 clone, 要么自己重新生成对象: new StringBuffer(oldValue.toString()); 进行赋值.
要知道除了基本数据类型 (byte,short,int,long,double 等) 可自动实现深度克隆以外, 其它例如 Integer,String,Double 等是一特殊情况.
下面通过一个实例来演示上述结论.
- package com.kevin.clone;
- public class Book implements Cloneable{
- public String name;
- public StringBuffer author;
- protected Book clone() throws CloneNotSupportedException{
- return (Book)super.clone();
- }
- }
测试代码:
- package com.kevin.clone;
- /**
- * 测试 clone 方法
- * @author Kevin
- *
- */
- public class test_clone3 {
- public static void main(String[] args){
- Book book = new Book();
- book.name = new String("Think in Java");
- book.author = new StringBuffer("Kevin");
- System.out.println("Before clone book.name :"+book.name);
- System.out.println("Before clone book.author :"+book.author);
- Book book_clone = null;
- try{
- book_clone = (Book)book.clone();
- }catch(CloneNotSupportedException e){
- e.printStackTrace();
- }
- book_clone.name = book_clone.name.substring(0,5);
- book_clone.author = book_clone.author.append("Zhang");
- System.out.println("\nAfter clone book.name :"+book.name);
- System.out.println("After clone book.author :"+book.author);
- System.out.println("\nAfter clone book_clone.name :"+book_clone.name);
- System.out.println("After clone book_clone.author :"+book_clone.author);
- }
- }
输出结果:
- Before clone book.name :Think in Java
- Before clone book.author :Kevin
- After clone book.name :Think in Java
- After clone book.author :Kevin Zhang
- After clone book_clone.name :Think
- After clone book_clone.author :Kevin Zhang
分析: 有上述结果可知, String 类型的变量看起来好像实现了深度 clone, 因为对 book_clone.name 的改动并没有影响到 book.name. 实质上, 在 clone 的时候 book_clone.name 与 book.name 仍然是引用, 而且都指向了同一个 String 对象. 但在执行 book_clone.name = book_clone.name.substring(0,5)的时候, 生成了一个新的 String 类型, 然后又赋回给 book_clone.name. 这是因为 String 被 Sun 公司的工程师写成了一个不可更改的类(immutable class), 在所有 String 类中的函数都不能更改自身的值. 类似的, String 类中的其它方法也是如此, 都是生成一个新的对象返回. 当然 StringBuffer 还是原来的对象.
需要知道的是在 Java 中所有的基本数据类型都有一个相对应的类, 例如 Integer 类对应 int 类型, Double 类对应 double 类型等等, 这些类也 与 String 类相同, 都是不可以改变的类. 也就是说, 这些的类中的所有方法都是不能改变其自身的值的. 这也让我们在编 clone 类的时候有了一个更多的 选择. 同时我们也可以把自己的类编成不可更改的类.
来源: https://www.cnblogs.com/Kevin-ZhangCG/p/9088619.html