以下介绍两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。
在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。
一、浅克隆
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。
1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
3. API里其中一个实现了clone方法的类:java.util.Date
4. 在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
二、深克隆
在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现(这里引用类型的成员变量也要实现clone()方法),也可以通过序列化(Serialization)等方式来实现。(如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。)
1. 序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
2. 在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
三、例子
开发中遇到这样的情况,需要将上传上来的Excel文件,先保存源文件,再进行提取和处理,此时,需要将上传来的流文件对象进行克隆,保存。
首先,在后台用于接收前台提交的Excel文件的对象是CommonsMultipartFile,CommonsMultipartFile实现了Serializable接口。
克隆文件对象:
private CommonsMultipartFile[] cloneMultiFiles(CommonsMultipartFile[] multiFiles){
CommonsMultipartFile[] copyMutiFiles = null;
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(multiFiles);
bais = new ByteArrayInputStream(baos.toByteArray());
ois = new ObjectInputStream(bais);
copyMutiFiles = (CommonsMultipartFile[]) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(baos!=null){
try {
baos.flush();
baos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(oos!=null){
try {
oos.flush();
oos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(bais!=null){
try {
bais.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(ois!=null){
try {
ois.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return copyMutiFiles;
}
保存源文件:
private void saveOriginFiles(CommonsMultipartFile[] multiFiles, String resultFolder) throws IOException {
String originFileFolder = resultFolder+"_origin";
for(int i=0;i<multiFiles.length;i++){
File originFile = new File(originFileFolder+File.separator+multiFiles[i].getOriginalFilename());
if(!originFile.getParentFile().exists()){
originFile.getParentFile().mkdirs();
}
if(originFile.exists()){
//先删除已存在的文件
originFile.delete();
}
originFile.createNewFile();
multiFiles[i].transferTo(originFile);
}
//进行文件夹压缩并删除源文件
String zipFileName=originFileFolder.concat(".zip");
ZipCompressorUtil zc = new ZipCompressorUtil(zipFileName);
zc.compress(originFileFolder);
boolean flag=FileUtil.forceDeleteFile(originFileFolder);
log.info("原文件保存成功: ["+originFileFolder+"], "+flag);
}