简单实用的对象转换复制工具(续)

时光怂恿深爱的人放手 提交于 2020-03-04 12:03:10
接前文,有朋友提到说这个工具只实现了浅拷贝,在很多条件下不适合使用,这篇文章我们就来解决这个浅拷贝问题。

一、概念

    首先我们得先要弄清楚什么是浅拷贝,什么是深拷贝。这里我们就不铺开说了,大致了解一下概念。

    所谓浅拷贝就是指复制了原对象的引用,拷贝后的对象和原对象依然是指向同一地址和同一实例,这会导致一个问题就是,我在修改拷贝后的对象时原对象会同时发生变化。而深拷贝就不会存在上述问题,深拷贝是拷贝了原对象的值,拷贝后的对象和原对象完全独立开互不影响,所以我们修改拷贝后的对象时原对象不会发生任何变化。

二、思路

    实现深拷贝有好几种方法,最常用的有:

  1. 序列化与反序列化,
  2. 重写克隆方法,
  3. 使用三方类库提供的方法。

    我们一项一项的来看,

第一种方法又有几种方式,典型两种,一种是通过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类只实现了浅拷贝。

这个是我挤了一点点时间来完成的这个小工具,没有做太多的验证,或许中间有不少不合理的地方,希望看到这篇文章的朋友可以提出宝贵的意见,大家一起交流提升。

------------------------------------------------------------------------

欢迎关注我的个人公众号,推送最新文章

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!