在实际编程过程中,我们常常要遇到这种情况:有一个对象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功能的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异常。
下面的例子包含三个类UnCloneA,CloneB,CloneMain。CloneB类包含了一个UnCloneA的实例和一个int 类型变量,并且重载clone()方法。CloneMain类初始化UnCloneA类的一个实例b1,然后调用clone()方法生成了一个b1的拷贝 b2。最后考察一下b1和b2的输出:
|
输出的结果说明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很简单,需要两个改变:一是让UnCloneA类也实现和CloneB类一样的clone功能(实现 Cloneable接口,重载clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone();
程序如下:
|
可以看出,现在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();
}
相关推荐
在本文中,我们将深入探讨关于Java对象序列化你可能不知道的五件事情,这些知识点对于理解和优化你的Java应用程序至关重要。 1. **序列化的意义与用途** Java对象序列化不仅用于持久化对象状态,还能在网络传输中...
3. **序列化操作**:使用`ObjectOutputStream`的`writeObject()`方法进行序列化,`ObjectInputStream`的`readObject()`方法进行反序列化。 4. **优点**:序列化可以保存对象的状态,方便持久化存储;在网络传输中,...
"clone"方法就是用于复制对象的一种方式,尤其在Java等支持此功能的语言中。本文将深入探讨"深度克隆"这一概念,以及它与普通克隆的区别,并讨论其在实际应用中的优缺点。 深度克隆,也称为完全克隆,是一种创建新...
为了实现 Java 序列化,需要将需要被序列化的类实现 Serializable 接口,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 ...
**浅拷贝**:在浅拷贝中,新创建的对象与原对象共享同一块内存空间,只复制对象的引用,不复制对象的内容。因此,如果对象包含对其他对象的引用,修改复制对象的这些引用会影响到原始对象。 **深拷贝**:与浅拷贝...
总结来说,Java中实现List的深度复制,可以采用序列化和反序列化的方法,或者手动复制每个元素。选择哪种方法取决于你的具体需求,例如对象是否支持序列化,以及性能和代码复杂性的考量。在处理可变对象时,确保深...
可以使用序列化(Serialization)和反序列化(Deserialization)来实现深复制,或者手动编写代码递归复制所有属性。例如: ```java public class MyClass implements Serializable { private String str; private...
- **序列化与反序列化**:将对象序列化为字节数组,然后再反序列化为新的对象,实现深拷贝。 3. **注解实现对象复制** - **Apache Commons BeanUtils库**:提供了`BeanUtils.copyProperties()`方法,可以快速实现...
10万次序列化,1万次反序列化,毫秒。 阿里序列化时间 1229 1133 1179 阿里反序列化时间 478 523 466 HZS序列化时间 1089 998 1010 HZS反序列化时间 606 623 635 测试代码如下: { org.hzs.json.JSONObject bjson...
总的来说,Java序列化提供了一种实现对象深度克隆的方法,适用于需要完整复制对象及其关联对象的情况。然而,这也带来了一些潜在问题,如性能影响、安全风险(因为序列化的对象可能被恶意用户反序列化以执行代码)...
Java编程语言中,对象的创建和管理涉及到一系列...总结来说,Java对象的生命周期涉及类加载、初始化,以及对象的创建、克隆、序列化和反序列化等步骤,每一步都可能需要特定的处理和优化,以满足不同的需求和安全考虑。
然而,由于其浅拷贝的特性,开发者需要根据具体需求来决定是否使用它,或者选择其他复制策略,比如序列化和反序列化,或者使用构造函数来创建新对象。在实际编程中,理解`clone()`的工作原理和限制是非常重要的,这...
在实际应用中,我们还可以考虑使用序列化和反序列化的方式实现深度拷贝,或者使用第三方库如Apache Commons Lang的`DeepClone`方法,它们提供了更简便的解决方案。但这里我们重点讨论了如何仅通过Java反射机制来实现...
10万次序列化,1万次反序列化,毫秒。 阿里序列化时间 1122 1054 1115 阿里反序列化时间 409 423 412 HZS序列化时间 884 864 880 HZS反序列化时间 392 375 394 JAVA7版已经逼近阿里的速度,JAVA8版利用了闭包技术...
1. **实现Serializable接口**:利用序列化和反序列化来实现深拷贝。首先将对象序列化为字节数组,然后再将这个字节数组反序列化为新的对象。这种方法适用于所有实现了Serializable接口的对象,但效率较低,并且如果...
本文将深入探讨这两种拷贝方式,并着重讲解如何通过重写`clone()`方法以及使用序列化来实现深拷贝。 1. 浅拷贝: 浅拷贝是指创建一个新对象,该对象拥有原始对象的引用字段的副本。这意味着如果原始对象的字段包含...
Java中的`clone`方法是Java语言提供的一种复制对象的方式,它允许创建一个对象的副本,这个副本与原对象具有相同的属性值,但它们是两个独立的对象,修改副本不会影响原对象。`clone`方法存在于Java的`java.lang....
### Java 高级特性详解 #### 一、`hashCode` ...正确地重写 `equals` 和 `hashCode` 方法、使用 `Comparator` 进行排序、利用反射机制和序列化技术,以及实现 `clone` 方法都是开发高质量 Java 应用程序的重要技能。
如果需要复制这些内部对象,就需要实现深复制,这通常涉及到递归地调用`clone()`方法或使用序列化和反序列化技术。 例如,假设有一个类`CloneB`,它包含一个`UnCloneA`类型的实例和其他属性。在进行深复制时,除了...