`

Java克隆对象的特性

    博客分类:
  • Java
阅读更多

在java面向对象的编程当中,要复制引用类型的对象,就必须克隆对象。通过调用对所有引用类型和对象都是可用的clone方法,来实现克隆。

在Java中传值及引伸深度克隆的思考中,我们讲过引申到克隆技术Java中的所有对象都是Object类的子类。我们知道,Java是纯面向对象的程序设计语言。Java里,所有的类的顶级父类都是java.lang.Object类,也就是说,如果一个类没有显示 申明继承关系,它的父类默认就是java.lang.Object。

有一个很简单的方法可以证明这一点,我们写一个Test类,如下:

 

public class Test {   
public void someMethod() {   
super.clone();   
}   
} 

 里面调用了super.clone(),编译时并不报错。其实clone()方法为java.lang.Object类提供的一个 protected型方法。

 

 

对象克隆

 

本文通过介绍java.lang.Object#clone()方法来说明Java语言的对象克隆特性。

java.lang.Object#clone()方法由java.lang.Object加以实现,主要对对象本身加以克隆。

首先我们看看下面的例子:

 

public class TestClone {   
public static void main(String[] args) {   
MyClone myClone1 = new MyClone("clone1");   
MyClone myClone2 = (MyClone)myClone1.clone();   
if (myClone2 != null) {   
System.out.println(myClone2.getName());  
System.out.println("myClone2 equals myClone1: " + myClone2.equals(myClone1));   
} else {   
System.out.println("Clone Not Supported");   
}   
} }   
class MyClone {   
private String name;   
public MyClone(String name) {   
this.name = name;   
}  
public String getName() {   
return name;   
}   
public void setName(String name) {   
this.name = name; }  
public Object clone() {   
try {  
return super.clone();   
} catch (CloneNotSupportedException e) {   
return null;   
}} 

 编译执行TestClone,打印出:

 

 

C:\clone>javac *.java   
C:\clone>java TestClone   
Clone Not Supported   
C:\clone>  

 说明MyClone#clone()方法调用super.clone()时抛出了CloneNotSupportedException异常,不支持克隆。

 

为什么父类java.lang.Object里提供了clone()方法,却不能调用呢?

原来,Java语言虽然提供了这个方法,但考虑到安全问题, 一方面将clone()访问级别设置为protected型,以限制外部类访问;

 

另一方面,强制需要提供clone功能的子类实现java.lang.Cloneable接口,在运行期,JVM会检查调用clone()方法的 类,如果该类未实现java.lang.Cloneable接口,则抛出CloneNotSupportedException异常。

 

java.lang.Cloneable接口是一个空的接口,没有申明任何属性与方法。该接口只是告诉JVM,该接口的实现类需要开放“克隆”功能。

 

我们再将MyClone类稍作改变,让其实现Cloneable接口:

 

 

class MyClone implements Cloneable {   
...//其余不做改变   
}   
编译执行TestClone,打印出:   
C:\clone>javac *.java   
C:\clone>java TestClone   
clone1   
myClone2 equals myClone1: false   
C:\clone>  

 根据结果,我们可以发现:

 

1,myClone1.clone()克隆了跟myClone1具有相同属性值的对象

2,但克隆出的对象myClone2跟myClone1不是同一个对象(具有不同的内存空间)

 

小结

如果要让一个类A提供克隆功能,该类必须实现java.lang.Cloneable接口,并重载 java.lang.Object#clone()方法。

 

public class A extends Cloneable {   
public Object clone() {   
try {   
return super.clone();   
} catch (CloneNotSupportedException e) {   
//throw (new InternalError(e.getMessage()));   
return null;   
}   
}   
} 

 对象的深层次克隆

 

上例说明了怎么样克隆一个具有简单属性(String,int,boolean等)的对象。

但如果一个对象的属性类型是List,Map,或者用户自定义的其他类时,克隆行为是通过怎样的方式进行的?

很多时候,我们希望即使修改了克隆后的对象的属性值,也不会影响到原对象,这种克隆我们称之为对象的深层次克隆。怎么样实现对象的深层次克隆呢?

 

验证对象的克隆方式

为了验证对象的克隆方式,我们对上面的例子加以改进,如下(为了节省篇幅,我们省略了setter与getter方法):

 

public class TestClone {   
public static void main(String[] args) {   
//为克隆对象设置值   
MyClone myClone1 = new MyClone("clone1");   
myClone1.setBoolValue(true);   
myClone1.setIntValue(100);   
//设置List值   
List <Element>listValue = new ArrayList<Element>();   
listValue.add(new Element("ListElement1"));   
listValue.add(new Element("ListElement2"));   
listValue.add(new Element("ListElement3"));   
myClone1.setListValue(listValue);   
//设置Element值   
Element element1 = new Element("element1");   
myClone1.setElement(element1);   
//克隆   
MyClone myClone2 = (MyClone)myClone1.clone();   
if (myClone2 != null) {   
//简单属性   
System.out.println("myClone2.name=" + myClone2.getName()   
+ " myClone2.boolValue=" + myClone2.isBoolValue()   
+ " myClone2.intValue=" + myClone2.getIntValue() );   
//复合属性(List<Element>与Element)   
List clonedList = myClone2.getListValue();   
Element element2 = myClone2.getElement();   
System.out.println("myClone2.listValue.size():" + clonedList.size());   
System.out.println("myClone2.element.equals(myClone1.element):" + element2.equals(element1));   
System.out.println("myClone2.element.name:" + element2.getName());  
//下面我们测试一下myClone2.element是否等于myClone1.element   
//以及myClone2.listValue是否等于myClone1.listValue   
//为此,我们修改myClone2.element与myClone2.listValue,如果myClone1的相应值也跟着被修改了,
则它们引用 的是同一个内存空间的变量,我们认为它们相等   
clonedList.add("ListElement4");   
System.out.println("myClone1.listValue.size():" + listValue.size());   
element2.setName("Element2");   
System.out.println("myClone1.element.name:" + element1.getName());   
} else {   
System.out.println("Clone Not Supported");   
}   
}   
}   
class MyClone implements Cloneable {   
private int intValue;   
private boolean boolValue;   
private String name;   
private List <Element>listValue;   
private Element element;   
public MyClone(String name) {   
this.name = name;   
}  
...//setter与getter方法(略)   
}   
class Element implements Cloneable {   
private String name;   
public Element (String name) {   
this.name = name;   
}   
...//setter与getter方法(略)   
}  

 编译执行TestClone,打印出:

C:\clone>javac *.java   
C:\clone>java TestClone   
myClone2.name=clone1 myClone2.boolValue=true myClone2.intValue=100   
myClone2.listValue.size():3   
myClone2.element.equals(myClone1.element):true   
myClone2.element.name:element1   
myClone1.listValue.size():4   
myClone1.element.name:Element2 09.myClone2 equals myClone1: false 10.C:\clone> 11.  

 

我们发现,对于对象里的List,Element等复合属性,super.clone()只是简单地赋值,没有采取克隆手段。也就是说,修改被克 隆后的对象值,会影响到原对象。

 

怎么进行深层次的克隆呢?

答案是,我们只能手动在重载的clone()方法里,对属性也分别采用克隆操作。当然条件是,属性类也得支持克隆操作

class MyClone implements Cloneable {   
...   
public Object clone() {   
try {   
MyClone myClone = (MyClone)super.clone();   
//分别对属性加以克隆操作   
myClone.element = this.element.clone();   
myClone.listValue = new ArrayList();   
for (Element ele:this.listValue) {   
myClone.listValue.add(ele.clone());   
}   
return myClone;   
} catch (CloneNotSupportedException e) {   
return null;   
}   
}   
...  
}   
//让Element类也支持克隆操作   
class Element implements Cloneable {   
...   
public Element clone() {   
try {   
return (Element)super.clone();   
} catch (CloneNotSupportedException e) {   
return null;   
}   
}   
}  

 深层次的克隆操作往往存在效率问题,尤其是需要让List,Map等集合类也支持深层次的克隆操作时。

 

总结

 

本文结合范例,比较深入地介绍了Java语言的克隆属性,以及克隆的实现方法等。同时分析了深层次克隆的概念,实现,以及存在的问题等。 但是有没有更好的方法呢?当然,是有的,串行化来实现。

 

 

转自:http://developer.51cto.com/art/201106/271883.htm

分享到:
评论

相关推荐

    Java八股文的面试题

    面向对象编程(OOP): Java是一种面向对象的编程语言,支持封装、继承、多态三大特性。面向对象编程使得代码更加模块化,易于理解和维护。 Java虚拟机(JVM): JVM是运行所有Java程序的虚拟机环境,实现了Java的跨...

    Java开发详解.zip

    031111_【第11章:Java常用类库】_对象克隆技术笔记.pdf 031112_【第11章:Java常用类库】_Arrays笔记.pdf 031113_【第11章:Java常用类库】_比较器(Comparable、Comparator)笔记.pdf 031114_【第11章:Java常用...

    面向对象之继承-JAVA面试资料

    在《Think in java》中有这样一句话:复用代码是Java众多...在这句话中最引人注目的是“复用代码”,尽可能的复用代码使我们程序员一直在追求的,现在我来介绍一种复用代码的方式,也是java三大特性之一---继承。

    JAVA上百实例源码以及开源项目

     Java绘制图片火焰效果,源代码相关注释:前景和背景Image对象、Applet和绘制火焰的效果的Image对象、Applet和绘制火焰的效果的Graphics对象、火焰效果的线程、Applet的高度,图片到图片装载器、绘制火焰效果的X坐标...

    JAVA上百实例源码以及开源项目源代码

     Java绘制图片火焰效果,源代码相关注释:前景和背景Image对象、Applet和绘制火焰的效果的Image对象、Applet和绘制火焰的效果的Graphics对象、火焰效果的线程、Applet的高度,图片到图片装载器、绘制火焰效果的X坐标...

    Java后端面试问题整理.docx

    • 熟练的使用Java语言进行面向对象程序设计,熟悉Java语言特性 • 熟悉常用排序,堆栈,树等数据结构和算法 • 熟悉常用集合数据结构(数组、Hashmap、ConcurrentHashMap、HashTable、ArrayList、Vetor、LinkedList...

    java 编程入门思考

    A.1.3 传递和使用Java对象 A.1.4 JNI和Java违例 A.1.5 JNI和线程处理 A.1.6 使用现成代码 A.2 微软的解决方案 A.3 J/Direct A.3.1 @dll.import引导命令 A.3.2 com.ms.win32包 A.3.3 汇集 A.3.4 编写回调函数 A.3.5 ...

    Java初学者入门教学

    A.1.3 传递和使用Java对象 A.1.4 JNI和Java违例 A.1.5 JNI和线程处理 A.1.6 使用现成代码 A.2 微软的解决方案 A.3 J/Direct A.3.1 @dll.import引导命令 A.3.2 com.ms.win32包 A.3.3 汇集 A.3.4 编写回调函数 A.3.5 ...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    / 29 2.2.7 直接内存 / 29 2.3 对象访问 / 30 2.4 实战:OutOfMemoryError异常 / 32 2.4.1 Java堆溢出 / 32 2.4.2 虚拟机栈和本地方法栈溢出 / 35 2.4.3 运行时常量池溢出 / 38 2.4.4 方法区溢出 / 39 2.4.5...

    java联想(中文)

    A.1.3 传递和使用Java对象 A.1.4 JNI和Java违例 A.1.5 JNI和线程处理 A.1.6 使用现成代码 A.2 微软的解决方案 A.3 J/Direct A.3.1 @dll.import引导命令 A.3.2 com.ms.win32包 A.3.3 汇集 A.3.4 编写回调函数 A.3.5 ...

    JAVA_Thinking in Java

    A.1.3 传递和使用Java对象 A.1.4 JNI和Java违例 A.1.5 JNI和线程处理 A.1.6 使用现成代码 A.2 微软的解决方案 A.3 J/Direct A.3.1 @dll.import引导命令 A.3.2 com.ms.win32包 A.3.3 汇集 A.3.4 编写回调函数 A.3.5 ...

    Thinking in Java简体中文(全)

    A.1.3 传递和使用Java对象 A.1.4 JNI和Java违例 A.1.5 JNI和线程处理 A.1.6 使用现成代码 A.2 微软的解决方案 A.3 J/Direct A.3.1 @dll.import引导命令 A.3.2 com.ms.win32包 A.3.3 汇集 A.3.4 编写回调函数 A.3.5 ...

    java jdk实列宝典 光盘源代码

    复制文件和目录;一个简单的文件搜索器; 多种方式读文件内容, 按字节读取文件内容、按字符读取文件内容、按行读取文件内容、随机读取文件内容; 多种方式写文件内容, 按字节写文件内容、按字符写文件内容、按行写...

    Java面试题-基础和集合.docx

    通过面试题的逐一解答,读者可以了解到Java语言的一些核心概念,如面向对象的特性、线程安全、性能优化、泛型、自动装箱与拆箱等。此外,文件还详细解释了类型擦除的概念及其优势,以及深浅克隆的区别和实现方式。...

    Thinking in Java 中文第四版+习题答案

    A.1.3 传递和使用Java对象 A.1.4 JNI和Java违例 A.1.5 JNI和线程处理 A.1.6 使用现成代码 A.2 微软的解决方案 A.3.1 @dll.import引导命令 A.3.2 com.ms.win32包 A.3.3 汇集 A.3.4 编写回调函数 A.3.5 其他J/Direct...

    Java JDK 7学习笔记(国内第一本Java 7,前期版本累计销量5万册)

    4.4.2 字符串特性 111 4.4.3 字符串编码 115 4.5 查询java api文件 117 4.6 重点复习 119 4.7 课后练习 120 chapter5 对象封装 125 5.1 何谓封装 126 5.1.1 封装对象初始流程 126 5.1.2 封装对象...

    java基础案例与开发详解案例源码全

    1.2.1 Java语言特性3 1.2.2 JavaApplet4 1.2.3 丰富的类库4 1.2.4 Java的竞争对手5 1.2.5 Java在应用领域的优势7 1.3 Java平台的体系结构7 1.3.1 JavaSE标准版8 1.3.2 JavaEE企业版10 1.3.3 JavaME微型版11 1.4 ...

    java 面试题 总结

     GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收...

    Java2核心技术.part5

    6.2对象克隆 6.3接口与回调 6.4内部类 6.4.1使用内部类访问对象状态 6.4.2内部类的特殊语法规则 6.4.3内部类是否实用、必要和安全 6.4.4局部内部类 6.4.5匿名内部类 6.4.6静态内部类 6.5...

Global site tag (gtag.js) - Google Analytics