黑马程序员技术交流社区

标题: 【暂解决】关于clone深拷贝的问题 [打印本页]

作者: 黄玉昆    时间: 2013-2-6 08:58
标题: 【暂解决】关于clone深拷贝的问题
本帖最后由 黄玉昆 于 2013-2-22 08:04 编辑

我在书上看到了关于clone的一些知识点,书中提到:
默认的克隆操作是浅拷贝,也就是说,clone只是对当前对象进行了拷贝,而没有拷贝对象中引用了的其他对象。
浅拷贝浅克隆)具体含义是:被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝(深克隆)具体含义是:被复制对象的所有变量都含有与原来对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制的新对象,而不再是原有的那些被引用的对象。换言之,深复制把复制的对象所引用的对象都复制了一遍。
那么对于所有的数组类型均包含了一个clone方法,这个方法被设为public,而不是protected(数组外的基本上是被设为protected),可以利用这个方法创建一个包含所有数据元素的一个新数组,如:
  1. int[] num = {2,3,5,7,11,13}
  2. int [] cloned = (int[])num.clone();
  3. cloned[5] = 12;//并没有改变num[5]的值
复制代码
那么我想说的是:
1、如果对其他类型的如新定义的Employee类型,将诸如姓名,薪水,雇佣日期等数据包装起来放入Employee[]的数组,那么是否也能像int[]数组一样实现深拷贝呢?
2、如果定义的新数组里面的数据都是诸如int,double这些基本类型组成的,那么这种数组是不是一定能实现深拷贝呢?
希望知道的朋友可以对这两个问题一一解答,谢谢。

作者: 杨杨    时间: 2013-2-6 09:38
"将诸如姓名,薪水,雇佣日期等数据包装起来放入Employee[]的数组,那么是否也能像int[]数组一样实现深拷贝呢?"
你的意思是不是把姓名 薪水 ,雇佣日期 封装成类  在数组中新建对象吧
  1. package cn.itcast;

  2. import java.util.*;

  3. public class CloneTest{
  4.         public static void main(String[] args) {
  5.                 try {
  6.                         Employee []staff = new Employee [3];
  7.                         staff[0] =new Employee("张三");
  8.                         staff[1] =new Employee("李四");
  9.                         staff[2] =new Employee("王五");
  10.                        
  11.                         staff[0].raiseSalary(10);
  12.                 } catch (Exception e) {
  13.                         // TODO: handle exception
  14.                 }
  15.         }
  16. }
  17. class Employee implements Cloneable {
  18.         private String name="";
  19.         private int salary;
  20.         public String getName() {
  21.                 return name;
  22.         }
  23.         public void raiseSalary(int byPercent) {
  24.                 // TODO Auto-generated method stub
  25.                 double raise = salary *byPercent/100;
  26.                
  27.         }
  28.         public void setName(String name) {
  29.                 this.name = name;
  30.         }
  31.         public Employee(String name) {
  32.                 this.name=name;
  33.         }

  34.        
  35. }
复制代码
数组只是存对象 用对象的克隆的时候取出来就可以
深拷贝 必须克隆说有可变的实例域
实际上我 对克隆也不是很理解,老害怕用错,所有我一般在实际开发中不用克隆
而用implements Serializable 接口
这个是比较好用的
这是我在api找到的希望对你用帮助
类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

要允许不可序列化类的子类型序列化,可以假定该子类型负责保存和恢复超类型的公用 (public)、受保护的 (protected) 和(如果可访问)包 (package) 字段的状态。仅在子类型扩展的类有一个可访问的无参数构造方法来初始化该类的状态时,才可以假定子类型有此职责。如果不是这种情况,则声明一个类为可序列化类是错误的。该错误将在运行时检测到。

在反序列化过程中,将使用该类的公用或受保护的无参数构造方法初始化不可序列化类的字段。可序列化的子类必须能够访问无参数构造方法。可序列化子类的字段将从该流中恢复。

当遍历一个图形时,可能会遇到不支持 Serializable 接口的对象。在此情况下,将抛出 NotSerializableException,并将标识不可序列化对象的类。

在序列化和反序列化过程中需要特殊处理的类必须使用下列准确签名来实现特殊方法:


private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;
private void readObjectNoData()
     throws ObjectStreamException;
writeObject 方法负责写入特定类的对象的状态,以便相应的 readObject 方法可以恢复它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream,状态可以被保存。

readObject 方法负责从流中读取并恢复类字段。它可以调用 in.defaultReadObject 来调用默认机制,以恢复对象的非静态和非瞬态字段。defaultReadObject 方法使用流中的信息来分配流中通过当前对象中相应指定字段保存的对象的字段。这用于处理类演化后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream,状态可以被保存。

在序列化流不列出给定类作为将被反序列化对象的超类的情况下,readObjectNoData 方法负责初始化特定类的对象状态。这在接收方使用的反序列化实例类的版本不同于发送方,并且接收者版本扩展的类不是发送者版本扩展的类时发生。在序列化流已经被篡改时也将发生;因此,不管源流是“敌意的”还是不完整的,readObjectNoData 方法都可以用来正确地初始化反序列化的对象。

将对象写入流时需要指定要使用的替代对象的可序列化类,应使用准确的签名来实现此特殊方法:


ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
此 writeReplace 方法将由序列化调用,前提是如果此方法存在,而且它可以通过被序列化对象的类中定义的一个方法访问。因此,该方法可以拥有私有 (private)、受保护的 (protected) 和包私有 (package-private) 访问。子类对此方法的访问遵循 java 访问规则。

在从流中读取类的一个实例时需要指定替代的类应使用的准确签名来实现此特殊方法。


ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此 readResolve 方法遵循与 writeReplace 相同的调用规则和访问规则。

序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致 InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:


ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。




作者: 黄玉昆    时间: 2013-2-6 09:51
杨杨 发表于 2013-2-6 09:38
"将诸如姓名,薪水,雇佣日期等数据包装起来放入Employee[]的数组,那么是否也能像int[]数组一样实现深拷贝 ...

这里说的可序列化和反序列之类的东西,我看得有点晕了,不过还是挺感谢你的
作者: 杨杨    时间: 2013-2-6 10:04
本帖最后由 杨杨 于 2013-2-6 10:06 编辑

他确实说的有点官方 ,简单点就是必须实现一个Serializable接口 然后能他就能把对象给序列化了 所为的序列化就是把对象从内存 硬盘 ,内存一断电就没了而硬盘 断电没事,还有就是把对象 序列化之后通过网络 发送到 别的远程服务器中 ,别其他人调用,就是 一般做持久化的时候能用到。
反序列话就是把硬盘的类在转到内存中,硬盘可以变 成其他的存储设备的。
作者: 黄玉昆    时间: 2013-2-6 11:05
杨杨 发表于 2013-2-6 10:04
他确实说的有点官方 ,简单点就是必须实现一个Serializable接口 然后能他就能把对象给序列化了 所为的序列 ...

好吧,我还是以后在深入了解序列化的问题吧。谢谢




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2