Java中clone方法的使用

时间:2022-12-13 11:14:35

什么是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类的时候有了一个更多的 选择。同时我们也可以把自己的类编成不可更改的类。

 

Java中clone方法的使用的更多相关文章

  1. 分析java中clone()方法 (转载+修改)

    Java中的clone() 方法 java所有的类都是从java.lang.Object类继承而来的,而Object类提供下面的方法对对象进行复制. protected native Object c ...

  2. Java基础——clone()方法浅析

    一.clone的概念 clone顾名思义就是复制, 在Java语言中, clone方法被对象调用,所以会复制对象.所谓的复制对象,首先要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象.那 ...

  3. java中clone的深入理解

    Java中Clone的概念大家应该都很熟悉了,它可以让我们很方便的“制造”出一个对象的副本来,下面来具体看看java中的Clone机制是如何工作的?      1. Clone和Copy      假 ...

  4. Java中clone的写法

    Cloneable这个接口设计得十分奇葩,不符合正常人的使用习惯,然而用这个接口的人很多也很有必要,所以还是有必要了解一下这套扭曲的机制.以下内容来自于对Effective Java ed 2. it ...

  5. Java的clone方法效率问题

    在Java中,经常会需要新建一个对象,很多情况下,需要这个新建的对象和现有的某个对象保持属性一致. 那么,就有两种方式来实现这个对象的构造: ①通过新建一个对象,为这个对象的属性根据原有对象的属性来进 ...

  6. Java中的方法应用

    一.如何定义java中的方法 所谓方法,就是用来解决一类问题的代码的有序组合,是一个功能模块. 语法: 1. 访问修饰符:方法允许被访问的权限范围, 可以是 public.protected.priv ...

  7. c#和java中的方法覆盖——virtual、override、new

    多态和覆盖 多态是面向对象编程中最为重要的概念之一,而覆盖又是体现多态最重要的方面.对于像c#和java这样的面向对象编程的语言来说,实现了在编译时只检查接口是否具备,而不需关心最终的实现,即最终的实 ...

  8. Java中的方法(形参及实参)return返回类型

    如何定义 Java 中的方法 所谓方法,就是用来解决一类问题的代码的有序组合,是一个功能模块. 一般情况下,定义一个方法的语法是: 其中: 1. 访问修饰符:方法允许被访问的权限范围, 可以是 pub ...

  9. java中的方法method

    java中的方法必须存在于类class里,不能独立存在.类是描述具有某种特征的事物,方法则是这类 事物具有的某种功能,通过调用方法可以实现某种特定的功能.方法名一般以小写的动词开头. 例: publi ...

随机推荐

  1. Windows下安装Redis

    1.首先,Redis官方是支持Linux系统的,我这里不多说,需要的可以参考:http://www.oschina.net/question/12_18065/ 2.Windows 64位下载地址:h ...

  2. Coursera Machine Learning 作业答案脚本 分享在github上

    Github地址:https://github.com/edward0130/Coursera-ML

  3. Yii2 中日志的记录

    Yii2自带日志记录,但用起来感觉比较不是很顺手,故自己封装了个方法,如下: /** * 记录日志 * * @param type $msg * @time 2015年8月31日17:46:20 * ...

  4. 使用Jquery+EasyUI 进行框架项目开发案例讲解之二---用户管理源码分享

    使用Jquery+EasyUI 进行框架项目开发案例讲解之二 用户管理源码分享   在上一篇文章<使用Jquery+EasyUI进行框架项目开发案例讲解之一---员工管理源码分享>我们分享 ...

  5. &period;NET设计模式(12):外观模式(Fa&&num;231&semi;ade Pattern)(转)

    概述 在软件开发系统中,客户程序经常会与复杂系统的内部子系统之间产生耦合,而导致客户程序随着子系统的变化而变化.那么如何简化客户程序与子系统之间的交互接口?如何将复杂系统的内部子系统与客户程序之间的依 ...

  6. 《linux程序设计》笔记 第一章 入门

    linux程序存放位置linux主要有一下几个存放程序的目录: /bin    系统启动程序目录 /usr/bin 用户使用的标准程序 /usr/local/bin   用于存放软件安装目录 /usr ...

  7. uva 1343 非原创

    uva1343 原作者 题目题意是:给你的棋盘,在A-H方向上可以拨动,问你最少拨动几次可以是中心图案的数字一致 解题思路:回溯法,剪枝 其中要把每次拨动的字母所代表的位置提前用数组表示: 然后在如果 ...

  8. UIView的layoutSubviews,initWithFrame&comma;initWithCoder方法

    ****************************layoutSubviews************************************ layoutSubviews是UIView ...

  9. delete和delete&lbrack;&rsqb;的区别&lpar;转载&rpar;

    一直对C++中的delete和delete[]的区别不甚了解,今天遇到了,上网查了一下,得出了结论.做个备份,以免丢失. C++告诉我们在回收用 new 分配的单个对象的内存空间的时候用 delete ...

  10. 【Python使用】使用pip安装卸载Python包(含离线安装Python包)未完成???

    pip 是 Python 包管理工具,该工具提供了对Python包的查找.下载.安装.卸载的功能.Python 2.7.9 + 或 Python 3.4+ 以上版本都自带 pip 工具. pip使用( ...