原始模型就是给出一个原始对象来指明要创建对象的类型,然后用复制这个原始对象的方法创建出更多同类型的对象。
要理解原始模型需要先理解浅层次的拷贝与深层次的拷贝之间的不同。
浅层次拷贝就是获取的新对象是一份拷贝,然而所引用的对象仅仅拷贝的是内存地址。拷贝对象和原对象共享内存地址,这就意味着修改拷贝对象也就修改了原始对象,这是很危险的。
实际工作中使用一些全局性的属性时,最好是深拷贝一份,否则存在影响全局的风险。
深层次拷贝则是连对象的引用也拷贝一份,这时操作拷贝中的引用的就不会影响到原对象中的引用,是安全的。深层次拷贝需要实现clonable接口。
public class ConfigClass { private int poolSize = 5; public int getPoolSize() { return poolSize; } public void setPoolSize(int poolSize) { this.poolSize = poolSize; } }
public class ObjectToClone implements Cloneable{ private int height, weight; private String name; private ConfigClass config; public void setHeight(int height){ this.height = height; } public int getHeight(){ return height; } public void setWeight(int weight){ this.weight = weight; } public int getWeight() { return weight; } public Object clone(){ ObjectToClone temp = new ObjectToClone(); temp.setHeight(height); //temp.setWeight(weight); temp.setName(name); temp.setConfig(config); return (Object) temp; } public String getName() { return name; } public void setName(String name) { this.name = name; } public ConfigClass getConfig() { return config; } public void setConfig(ConfigClass config) { this.config = config; } }
public class CloneTest{ private static ObjectToClone orignalObject , clonedObject; public static void main(String[] args){ orignalObject = new ObjectToClone(); orignalObject.setHeight(3); orignalObject.setWeight(5); orignalObject.setName("orignal"); ConfigClass config = new ConfigClass(); config.setPoolSize(10); orignalObject.setConfig(config); clonedObject = (ObjectToClone) orignalObject.clone(); System.out.println("orignalObject height : " + orignalObject.getHeight()); System.out.println("orignalObject weight : " + orignalObject.getWeight()); System.out.println("orignalObject name : " + orignalObject.getName()); System.out.println("orignalObject config pool size : " + orignalObject.getConfig().getPoolSize()); System.out.println("clonedObject height : " + clonedObject.getHeight()); System.out.println("clonedObject weight : " + clonedObject.getWeight()); System.out.println("clonedObject name : " + clonedObject.getName()); System.out.println("clonedObject config pool size : " + clonedObject.getConfig().getPoolSize()); System.out.println("orignalObject == clonedObject :" + (orignalObject == clonedObject)); System.out.println("orignalObject config == clonedObject config: " + (orignalObject.getConfig()==clonedObject.getConfig())); clonedObject.getConfig().setPoolSize(20); System.out.println("orignalObject config pool size : " + orignalObject.getConfig().getPoolSize()); } }
输出结果为:
orignalObject height : 3 orignalObject weight : 5 orignalObject name : orignal orignalObject config pool size : 10 clonedObject height : 3 clonedObject weight : 0 clonedObject name : orignal clonedObject config pool size : 10 orignalObject == clonedObject :false orignalObject config == clonedObject config: true orignalObject config pool size : 20
clonedObject对poolSize的操作影响到了orignalObject ,需要做深层次的拷贝。
只需要new一个ConfigClass即可。
public Object clone(){ ObjectToClone temp = new ObjectToClone(); temp.setHeight(height); //temp.setWeight(weight); temp.setName(name); ConfigClass config = new ConfigClass(); temp.setConfig(config); return (Object) temp; }
Java里面实现深层次的拷贝,常常是需要拷贝的对象实现Serializable,表示是一个可以序列化的对象。先将对象的拷贝通过ObjectOutputStream写入到一个输出流,再用ObjectInputStream从流中将对象读回来。
import java.io.Serializable; public class ConfigClass implements Serializable{ private static final long serialVersionUID = 228445010384753204L; private int poolSize = 5; public int getPoolSize() { return poolSize; } public void setPoolSize(int poolSize) { this.poolSize = poolSize; } }
import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import pattern.builder.ConfigClass; public class ObjectStreamTest { public static void main(String [ ] args) { ConfigClass cc = new ConfigClass(); cc.setPoolSize(23); ObjectOutputStream os = null; ObjectInputStream is = null; try { os = new ObjectOutputStream(new FileOutputStream(new File("objectStore.txt"),false)); os.writeObject(cc); ConfigClass cd = new ConfigClass(); cd.setPoolSize(53); os.writeObject(cd); is = new ObjectInputStream(new BufferedInputStream(new FileInputStream(new File("objectStore.txt")))); Object o = null; while(null!=(o =is.readObject())){ if(o instanceof ConfigClass) { System.out.println(((ConfigClass)o).getPoolSize()); } } } catch (Exception e) { System.out.println("not found!"); }finally{ if(os !=null){ try { os.close(); } catch (IOException e) { e.printStackTrace(); } } if(is !=null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
输出结果为23,53.
需要注意的是文件流最后一定要关闭。也可以将多个对象存储到List中,发序列化List就可以得到多个对象。
满足下面四个条件中的一个的类就不应该被序列化:
1.一个类与本地代码(native code)有紧密的关系,如java.util.zip.Deflater,这个类中很多都是native的。
2.对象的内部状态依赖于java虚拟机或者运行状态,从而每次运行时状态都可能是不同的。例如Thread,InputStream等。
3.串行化肯能带来潜在的安全隐患,如java.lang.SecurityManager,java.security.MessageDigest等。
4.一个类全是静态方法,没有内部状态,如java.lang.Math等。
对象某些状态是瞬时的,无法保存。例如一个Thread对象或一个InputStream对象 ,对于这些字段,我们必须用transient关键字标明,否则编译器将报措。
另外 ,串行化可能涉及将对象存放到 磁盘上或在网络上发达数据,这时候就会产生安全问题。因为数据位于Java运行环境之外,不在Java安全机制的控制之中。对于这些需要保密的字段,不应保存在永久介质中 ,或者不应简单地不加处理地保存下来 ,为了保证安全性。应该在这些字段前加上transient关键字。
或者还有一种办法,就是覆盖默认的Serializable接口的readObject(ObjectOutPutStream ous)方法和writeObject(ObjectInputStream ins)方法,在该方法中自定义序列化哪些字段,反序列化哪些字段。
序列化还需要考虑的是版本问题,这个问题很经典了,如果一个类实现了序列化,那它必须承担起升版后如何保证接收端用老版本的类能成功反序列化的问题。jdk的解决方案是加入serialVersionUID字段,只要该字段值相同就认为是同一个对象。serialVersionUID可自己手动填 ,也可让IDE给你计算出一个。当该字段存在时,如果新版本的类传递到收端,收端根据该字段判断与老版本是同一对象时,它将”尽最大可能转换“,这意味着新版本的类里的新添加的方法,变量将被舍去。
当然你也可以自己定义版本问题的策略,比如你想要保证服务端和客户端部署的类版本始终保持一致,那你可以在新版本的类发布的时候,更改serialVersionUID字段值,这样在收端肯定会抛出serialVersionUID不一致异常,你可以捕获这个异常,然后提示客户端需要更新该类版本。