接前文,有朋友提到说这个工具只实现了浅拷贝,在很多条件下不适合使用,这篇文章我们就来解决这个浅拷贝问题。
一、概念
首先我们得先要弄清楚什么是浅拷贝,什么是深拷贝。这里我们就不铺开说了,大致了解一下概念。
所谓浅拷贝就是指复制了原对象的引用,拷贝后的对象和原对象依然是指向同一地址和同一实例,这会导致一个问题就是,我在修改拷贝后的对象时原对象会同时发生变化。而深拷贝就不会存在上述问题,深拷贝是拷贝了原对象的值,拷贝后的对象和原对象完全独立开互不影响,所以我们修改拷贝后的对象时原对象不会发生任何变化。
二、思路
实现深拷贝有好几种方法,最常用的有:
- 序列化与反序列化,
- 重写克隆方法,
- 使用三方类库提供的方法。
我们一项一项的来看,
第一种方法又有几种方式,典型两种,一种是通过IO流来实现,另一种只通过Gson,FastJson,Jackson等将对象序列化成json后在反序列化成对象来实现。这里我想减少三方类库等使用所以排除第二种方式。使用IO流实现的方式待定。
第二种方法需要对每个对象重写克隆方法,在易用性上不如第一种方法,所以排除。
第三种方法使用第三方类库,Apache Commons Lang库提供了SerializationUtils中提供了对象的深拷贝方法,用起来比较方便,但是还是上面的原因,我想尽量少的使用三方类库,而且它也是通过序列化和反序列化来实现的深拷贝,所以也排除掉。
最后我决定用IO流来完善这个工具。
三、实现
有了方向,我们就可以开始着手修改了。
首先我想的是编写一个独立的深拷贝的工具出来,然后用对象转换工具去调用它。
最初的代码如下:
public class DeepCloneUtil {
@SuppressWarnings("unchecked")
public static <T> T clone(T t){
T result = null;
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
//将对象写入流中
objectOutputStream.writeObject(t);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
//生成的新对象
result = (T) objectInputStream.readObject();
objectInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
通过测试发现如果传入对象没有实现Serializable接口会抛出NotSerializableException异常,然而在将泛型T限定为继承Serializable又会导致调用时错误。所以作出了如下修改:
public class DeepCloneUtil {
@SuppressWarnings("unchecked")
public static <T> T clone(T t) {
if (t instanceof Serializable) {
T result = null;
ByteArrayOutputStream byteArrayOutputStream = null;
ObjectOutputStream objectOutputStream = null;
ObjectInputStream objectInputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
//将对象写入流中
objectOutputStream.writeObject(t);
objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
//生成的新对象
result = (T) objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (byteArrayOutputStream != null) {
byteArrayOutputStream.close();
}
if (objectOutputStream != null) {
objectOutputStream.close();
}
if (objectInputStream != null) {
objectInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
} else {
return t;
}
}
}
这样就实现了如果待拷贝的对象实现了Serializable接口我们就做深拷贝处理,否则我们就不处理。
工具完成,接下来就是修改对象转换工具了,实现的方式就很简单了,只需在ObjectTransUtil中的transBean方法的倒数第二行将
targetMethod.invoke(v, invoke);
改为
targetMethod.invoke(v, DeepCloneUtil.clone(invoke));
就大功告成了。
下面我们用前面文章的例子来测试一下,我们只需要新增一个TempSerializable对象来和Temp对象来做对比
@Data
class TempSerializable implements Serializable {
String s;
}
修改一下测试程序
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
Temp temp = new Temp();
temp.setS("testTemp");
TempSerializable tempSerializable = new TempSerializable();
tempSerializable.setS("testTempSerializable");
Source source = new Source();
source.setSourceTemp(temp);
source.setSourceTempSerializable(tempSerializable);
Target target = ObjectTransUtil.transBean(source, Target.class);
Temp targetTemp = target.getTargetTemp();
targetTemp.setS("12345");
TempSerializable targetTempSerializable = target.getTargetTempSerializable();
targetTempSerializable.setS("12345");
System.out.println(source);
System.out.println(target);
}
运行得到下面结果
可以明显看出实现的序列化的TempSerializable类实现了深拷贝,而没有实现序列化的Temp类只实现了浅拷贝。
这个是我挤了一点点时间来完成的这个小工具,没有做太多的验证,或许中间有不少不合理的地方,希望看到这篇文章的朋友可以提出宝贵的意见,大家一起交流提升。
------------------------------------------------------------------------
欢迎关注我的个人公众号,推送最新文章
来源:oschina
链接:https://my.oschina.net/jack90john/blog/3187759