关于java对象复制
我们在编码过程经常会碰到将一个对象传递给另一个对象,java中对于基本型变量
采用的是值传递,而对于对象比如bean传递时采用的是应用传递也就是地址传递,
而很多时候对于对象传递我们也希望能够象值传递一样,使得传递之前和之后有
不同的内存地址,在这种情况下我们一般采用以下两种情况。
1 对象克隆
什么是"clone"?
在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。在Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。
Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。
怎样应用clone()方法?
一个很典型的调用clone()代码如下:
class CloneClass implements Cloneable{
public int aInt;
public Object clone(){
CloneClass o = null;
try{
o = (CloneClass)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
}
有三个值得注意的地方,一是希望能实现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异常。
什么是影子clone?
下面的例子包含三个类UnCloneA,CloneB,CloneMain。CloneB类包含了一个UnCloneA的实例和一个int类型变量,并且重载clone()方法。CloneMain类初始化UnCloneA类的一个实例b1,然后调用clone()方法生成了一个b1的拷贝b2。最后考察一下b1和b2的输出:
package clone;
class UnCloneA {
private int i;
public UnCloneA(int ii) { i = ii; }
public void doubleValue() { i *= 2; }
public String toString() {
return Integer.toString(i);
}
}
class CloneB implements Cloneable{
public int aInt;
public UnCloneA unCA = new UnCloneA(111);
public Object clone(){
CloneB o = null;
try{
o = (CloneB)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
}
public class CloneMain {
public static void main(String[] a){
CloneB b1 = new CloneB();
b1.aInt = 11;
System.out.println("before clone,b1.aInt = "+ b1.aInt);
System.out.println("before clone,b1.unCA = "+ b1.unCA);
CloneB b2 = (CloneB)b1.clone();
b2.aInt = 22;
b2.unCA.doubleValue();
System.out.println("=================================");
System.out.println("after clone,b1.aInt = "+ b1.aInt);
System.out.println("after clone,b1.unCA = "+ b1.unCA);
System.out.println("=================================");
System.out.println("after clone,b2.aInt = "+ b2.aInt);
System.out.println("after clone,b2.unCA = "+ b2.unCA);
}
}
/** RUN RESULT:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 222
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222
*/
输出的结果说明int类型的变量aInt和UnCloneA的实例对象unCA的clone结果不一致,int类型是真正的被clone了,因为改变了b2中的aInt变量,对b1的aInt没有产生影响,也就是说,b2.aInt与b1.aInt已经占据了不同的内存空间,b2.aInt是b1.aInt的一个真正拷贝。相反,对b2.unCA的改变同时改变了b1.unCA,很明显,b2.unCA和b1.unCA是仅仅指向同一个对象的不同引用!从中可以看出,调用Object类中clone()方法产生的效果是:先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容。对基本数据类型,这样的操作是没有问题的,但对非基本类型变量,我们知道它们保存的仅仅是对象的引用,这也导致clone后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
大多时候,这种clone的结果往往不是我们所希望的结果,这种clone也被称为"影子clone"。要想让b2.unCA指向与b2.unCA不同的对象,而且b2.unCA中还要包含b1.unCA中的信息作为初始信息,就要实现深度clone。
怎么进行深度clone?
把上面的例子改成深度clone很简单,需要两个改变:一是让UnCloneA类也实现和CloneB类一样的clone功能(实现Cloneable接口,重载clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone();
程序如下:
package clone.ext;
class UnCloneA implements Cloneable{
private int i;
public UnCloneA(int ii) { i = ii; }
public void doubleValue() { i *= 2; }
public String toString() {
return Integer.toString(i);
}
public Object clone(){
UnCloneA o = null;
try{
o = (UnCloneA)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
}
class CloneB implements Cloneable{
public int aInt;
public UnCloneA unCA = new UnCloneA(111);
public Object clone(){
CloneB o = null;
try{
o = (CloneB)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
o.unCA = (UnCloneA)unCA.clone();
return o;
}
}
public class CloneMain {
public static void main(String[] a){
CloneB b1 = new CloneB();
b1.aInt = 11;
System.out.println("before clone,b1.aInt = "+ b1.aInt);
System.out.println("before clone,b1.unCA = "+ b1.unCA);
CloneB b2 = (CloneB)b1.clone();
b2.aInt = 22;
b2.unCA.doubleValue();
System.out.println("=================================");
System.out.println("after clone,b1.aInt = "+ b1.aInt);
System.out.println("after clone,b1.unCA = "+ b1.unCA);
System.out.println("=================================");
System.out.println("after clone,b2.aInt = "+ b2.aInt);
System.out.println("after clone,b2.unCA = "+ b2.unCA);
}
}
/** RUN RESULT:
before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 111
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222
*/
可以看出,现在b2.unCA的改变对b1.unCA没有产生影响。此时b1.unCA与b2.unCA指向了两个不同的UnCloneA实例,而且在CloneB b2 = (CloneB)b1.clone();调用的那一刻b1和b2拥有相同的值,在这里,b1.i = b2.i = 11。
要知道不是所有的类都能实现深度clone的。例如,如果把上面的CloneB类中的UnCloneA类型变量改成StringBuffer类型,看一下JDK API中关于StringBuffer的说明,StringBuffer没有重载clone()方法,更为严重的是StringBuffer还是一个final类,这也是说我们也不能用继承的办法间接实现StringBuffer的clone。如果一个类中包含有StringBuffer类型对象或和StringBuffer相似类的对象,我们有两种选择:要么只能实现影子clone,要么就在类的clone()方法中加一句(假设是SringBuffer对象,而且变量名仍是unCA): o.unCA = new StringBuffer(unCA.toString()); //原来的是:o.unCA = (UnCloneA)unCA.clone();
还要知道的是除了基本数据类型能自动实现深度clone以外,String对象是一个例外,它clone后的表现好象也实现了深度clone,虽然这只是一个假象,但却大大方便了我们的编程。
通过以上我们可以看出在某些情况下,我们可以利用clone方法来实现对象只见的复制,但对于比较复杂的对象(比如对象中包含其他对象,其他对象又包含别的对象.....)这样我们必须进行层层深度clone,每个对象需要实现cloneable接口,比较麻烦,那就继续学习下一个序列化方法。
2 对象序列化
所谓对象序列化就是将对象的状态转换成字节流,以后可以通过这些值再生成相同状态的对象。这个过程也可以通过网络实现,可以先在Windows机器上创建一个对象,对其序列化,然后通过网络发给一台Unix机器,然后在那里准确无误地重新“装配”。是不是很神奇。
也许你会说,只了解一点点,但从来没有接触过,其实未必如此。RMI、Socket、JMS、EJB你总该用过一种吧,彼此为什么能够传递Java对象,当然都是对象序列化机制的功劳。
第一次使用Java的对象序列化是做某项目,当时要求把几棵非常复杂的树(JTree)及相应的数据保存下来(就是我们常用的保存功能),以便下次运行程序时可以继续上次的操作。
那时XML技术在网上非常的热,而且功能也强大,再加上树的结构本来就和XML存储数据的格式很像。作为一项对新技术比较有兴趣的我当然很想尝试一下。不过经过仔细分析,发现如果采用XML保存数据,后果真是难以想象:哪棵树的哪个节点被展开、展开到第几级、节点当前的属性是什么。真是不知该用A、B、C还是用1、2、3来表示。
还好,发现了Java的对象序列化机制,问题迎刃而解,只需简单的将每棵树的根节点序列化保存到硬盘上,下次再通过反序列化后的根节点就可以轻松的构造出和原来一模一样的树来。
其实保存数据,尤其是复杂数据的保存正是对象序列化的典型应用。最近另一个项目就遇到了需要对非常复杂的数据进行存取,通过使用对象的序列化,问题同样化难为简。
对象的序列化还有另一个容易被大家忽略的功能就是对象复制(Clone),Java中通过Clone机制可以复制大部分的对象,但是众所周知,Clone有深层Clone和浅层Clone,如果你的对象非常非常复杂,假设有个100层的Collection(夸张了点),如果你想实现深层Clone,真是不敢想象,如果使用序列化,不会超过10行代码就可以解决。
还有就是Swing组件,如果你有两个很象很象(或是一模一样)的比较难以构造的Swing组件,你该怎么办,也许你想到了Clone,但是偏偏Java的Swing组件没有提供Clone方法。别急,使用序列化,6行代码搞定:
ByteArrayOutputStream
byteOut = new ByteArrayOutputStream();
ObjectOutputStream out
= new ObjectOutputStream(byteOut);
out.writeObject(combo);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in
=new ObjectInputStream(byteIn);
JComboBox comb2 = (JComboBox)in.readObject();
虽然Java的序列化非常简单、强大,但是要用好,还有很多地方需要注意。比如曾经序列化了一个对象,可由于某种原因,该类做了一点点改动,然后重新被编译,那么这时反序列化刚才的对象,将会出现异常。
你可以通过添加serialVersionUID属性来解决这个问题。如果你的类是个单态(Singleton)类,是否允许用户通过序列化机制复制该类,如果不允许你需要谨慎对待该类的实现。
/**
* Clone Object
* @param obj
* @return
* @throws Exception
*/
private Object cloneObject(Object obj) throws Exception{
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(obj);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in =new ObjectInputStream(byteIn);
return in.readObject();
}
参考资料:
1.http://www-128.ibm.com/developerworks/cn/java/l-jpointer/index.html
2.http://www.softexam.cn/eschool/details.asp?id=10930
分享到:
相关推荐
总结,Java中的对象克隆是一项重要的功能,用于复制和独立化对象。通过实现`Cloneable`接口和覆盖`clone()`方法,我们可以创建浅克隆对象。对于更复杂的场景,可以自定义克隆逻辑或利用序列化来实现深克隆。理解并...
在Java编程中,对象之间的复制是一项常见的操作,特别是在数据传输、模型转换或者持久化存储等场景下。这里的“java不同对象之间及集合的复制”主要关注如何高效且便捷地完成对象及其集合的复制工作,避免繁琐的手动...
- **浅拷贝**:创建一个新的对象,只复制对象的引用,不复制内部引用的对象。这意味着改变其中一个对象的内部引用对象,会影响另一个对象。 - **深拷贝**:创建一个全新的对象,不仅复制对象本身,还递归复制其...
**浅拷贝**:在浅拷贝中,新创建的对象与原对象共享同一块内存空间,只复制对象的引用,不复制对象的内容。因此,如果对象包含对其他对象的引用,修改复制对象的这些引用会影响到原始对象。 **深拷贝**:与浅拷贝...
深拷贝不仅复制对象,还复制对象中引用的对象。在描述中提到的方法,很可能是关于深拷贝的实现。 在Java中,我们可以通过以下几种方法实现对象的深拷贝: 1. **实现Serializable接口**:利用序列化和反序列化来...
这个过程在需要复制对象的所有属性而不创建新类型实例时非常有用。本文将深入探讨Java中的对象克隆,包括其原理、实现方式以及注意事项。 一、对象克隆的原理 在Java中,对象克隆可以通过实现`Cloneable`接口并覆盖...
Java中的对象复制与克隆是程序开发中常见的需求,主要用于创建一个对象的副本,而不会影响原始对象的状态。这里主要讨论两种类型的对象拷贝:浅拷贝和深拷贝。 浅拷贝,也称为表面拷贝,是创建一个新的对象,然后将...
在Java编程中,将JSON对象转换为Java Bean对象是一个常见的任务,特别是在处理Web服务或API交互时。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,而Java Bean是符合特定规范的Java类,通常用于...
在Java编程语言中,对象复制是一项常见的操作,尤其是在处理持久化对象时,为了保持数据的一致性和完整性,正确地复制对象变得尤为重要。本文将深入探讨如何利用反射机制在Java中复制一个持久化对象,理解其背后的...
### MongoDB、Java与对象关系映射 #### MongoDB简介与特性 MongoDB作为一种强大的NoSQL数据库,在处理非结构化数据方面有着显著的优势。它通过使用JSON(JavaScript Object Notation)格式来存储和检索数据,简化...
Java CopyUtil工具类,可以进行对象的深copy,比如:对象里面包含对象,对象里面包含Map,List,Set...等复杂类型的属性都可以copy,copy后的对象与原有对象没有联系,即改变原有对象内容,不会改变copy后的对象里面的...
这一点在Java中非常重要,因为它影响到了如何处理对象的复制以及对象的生命周期等问题。 - **空引用** 如果我们声明了一个引用变量但并未给它分配任何对象,那么这个引用变量就称为“空引用”或“未初始化的引用...
Java提供了多种复制对象的方式,其中最常见的就是通过`clone()`方法。 `clone()`方法是Java `Object`类中的一个`native`方法,它能够创建当前对象的一个副本。但是,直接调用`clone()`可能会抛出`...
对象克隆可以分为浅克隆和深克隆,浅克隆仅复制对象的引用,而深克隆则复制对象的所有属性。 对象克隆的原因是为了在不同的场景下使用同一个对象,而不影响原始对象的状态。例如,在游戏开发中,需要创建多个相同的...
浅复制仅仅复制对象中的基本类型数据和引用其他对象的引用,而不会复制引用的对象本身;而深复制则会复制对象本身以及所引用的所有对象,从而创建一个完全独立的对象副本。 知识点二:Cloneable接口的作用 在Java中...
浅复制只复制对象本身,而不复制它引用的对象,而深复制则会递归复制所有引用的对象。这篇博客将探讨如何在Java中对List进行深度复制。 首先,我们来理解一下什么是浅复制和深复制。在Java中,当我们使用`clone()`...
在Java编程中,复制图片是一项常见的任务,尤其在处理图形用户界面(GUI)或文件操作时。本篇文章将深入探讨如何使用Java实现图片的复制功能,以满足标题和描述中的需求。 首先,我们需要理解Java中处理文件的基本...
这意味着修改复制对象的这些引用变量会影响到原始对象。简而言之,浅复制只复制了对象本身,而没有复制其引用的对象。 2. **深复制(深克隆)** 深复制则更进一步,不仅创建了原始对象的副本,而且对所有引用其他...
然而,`clone()`方法仅执行浅复制,即复制对象的引用属性,而不是引用指向的实体。如果对象的属性包含其他对象,这些内部对象并不会被复制,而是共享相同的引用。如果需要复制这些内部对象,就需要实现深复制,这...
首先,它打开源文件并创建一个新的工作簿对象,然后复制指定Sheet的数据和样式。`copyCellStyle`方法负责复制单元格的样式,而`copyRowHeightAndColumnWidth`方法则处理行高和列宽的复制。 使用这个工具类时,只需...