一劳永逸解决问题之深复制

说明

关于这个问题jdk始终没有给出官方的解决办法,第三方的包以及网上的手写代码很多时候也会出现各种各样的问题,如spring 的BeanUtils根本就无法做到完全深复制,如网上使用的Property的方式,要么是性能极差,要么是bug特别多,稍微复杂一点的对象就会报错。
其实目前来说实现深复制主流的方式大概有如下3种:
1.使用流的方式实现,比如mybatisPlus的SerializationUtils.clone
2.使用反射的方式
3.使用json序列化的方式,如fastjson和gson等,如JSON.parseObject(JSON.toJSONString(object), Xxx.class)
以上三种经过反复测试得出如下结论:
单线程执行下,数据量1万以内,第2种反射的方式性能明显优于其它两种,数据量越小性能越好,对比第1种流的方式,执行10次深复制,流的方式大概50毫秒,反射大概8毫秒,json序列化方式大概260毫秒。执行1万次,流大概400毫秒,反射大概250毫秒,序列化方式大概350毫秒。数据量1万以上,如在for循环种大量深复制对象的话,执行10万次,流的方式大概1800毫秒,反射的方式大概1300毫秒,序列化的方式大概370毫秒。由上面的数据不难看出使用第3种json序列化的方式在大数据的情况下波动性非常小,性能明显优于其它两种方式,而在数据量较少的情况下第2种反射的方式明显优于其它方式。

个人理解

虽然json序列化的方式在大数据量下有着十分明显的性能优势,但却有如下三个问题
1.使用场景较小,同一个对象的效率较高,这种情况基本上是在for循环中,且深复制在1万次以上,这样的场景基本上是不多见的
2.json的方式部分在解析时难免会出现bug,如gson进行深复制时对象如果继承另一个对象,那么会出现问题
3.json的方式在序列化时有时会出现对于is方法命名的boolean类型会解析不正确,如jackson

通过上面的分析不难看出,其实开发的时候更需要一种单次并且功能以及性能均佳的深复制方法,于是第二种通过反射的方式来实现深复制便作为了首选。毕竟反射执行10次的时间仅仅只需要8毫秒,而无论是流还是json目前都做不到这一点,且后续还可以通过判断是否大于1万实现动态切换深复制的算法来达到一个性能上的平衡。因此基于这些原因,我将json-script-rule框架中的代码摘出来并用反射的方式来实现深复制,看到的可以直接拿过去用。

/**
     * <p>深拷贝,支持不同类型的对象间属性拷贝,支持各种容器类型包括数组,集合,Map的拷贝,支持对象中各种复杂嵌套类型拷贝,支持对象继承关系拷贝
     * <p>支持复杂嵌套类型,如LinkedList<Object>;Set<Map<String,Integer>>;LinkedHashMap<String,Map<String,Integer>>;
     * <p>注意:字段名称相同才会进行拷贝,当目标对象属性无法在来源对象中找到,或者两种类型无法强转,又或者是其它类型出错时将不进行复制,结果为默认值
     * <p>注意:任何待copy的对象或其子类对象都应包含一个无参的构造函数,否则将返回null值,来源对象的子类和父亲属性名重复时将基于多态方式以子类属性的值进行拷贝
     * <p>注意:由于采用对象内同名属性拷贝策略,因此一定要避免两边对象的类型差距过大,如target为普通对象而source为Map类型,这样拷贝出来的结果容易引发未知的问题
     * @param target 目标对象类型,当来源对象的类型为目标对象的子类型时将会以多态的子类型进行复制
     * @param source 属性来源对象
     * @return 返回目标类型的复制后的对象
     * */
    @SuppressWarnings("unchecked")
    public static <T> T copy(Class<T> target, Object source) {
        if (source == null || target == null){
            return null;
        }
        try{
            if (isPrimitiveType(target) || target.isEnum()){
                return (T) source;
            }else if (Collection.class.isAssignableFrom(target)){
                return (T) copyCollection((Collection<?>)source);
            }else if (Map.class.isAssignableFrom(target)){
                return (T) copyMap((Map<?,?>)source);
            }else if (target.isArray()){
                return (T) copyArray(source);
            }else{
                Class<?> sourceType = source.getClass();
                T t;
                if (target.isAssignableFrom(sourceType)){
                    t = (T) sourceType.getConstructor().newInstance();
                }else{
                    t = target.getConstructor().newInstance();
                }
                for (Field f:target.getFields()){
                    try{
                        f.set(t, copy(f.getType(),sourceType.getField(f.getName()).get(source)));
                    }catch (Exception ignored) {}
                }
                return t;
            }
        }catch (Exception e) {
            return null;
        }
    }

其它抽离出来的方法

public static boolean isPrimitiveType(Class<?> clazz){
    return clazz==String.class || clazz.isPrimitive() || isPrimitiveWrapper(clazz);
}

public static boolean isPrimitiveWrapper(Class<?> clazz){
    try {
      return ((Class<?>) clazz.getField("TYPE").get(null)).isPrimitive();
    } catch (Exception e) {
        return false;
    }
}

public static <E> Collection<E> copyCollection(Collection<?> source){
        try {
            Collection<E> collection = source.getClass().getConstructor().newInstance();
            for (E e:(Collection<E>)source) {
                collection.add((E) copy(e.getClass(),e));
            }
            return collection;
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
}

public static <K,V> Map<K,V> copyMap(Map<?,?> source){
        try {
            Map<K,V> map = source.getClass().getConstructor().newInstance();
            source.forEach((k,v)->map.put((K) k, (V) copy(v.getClass(),v)));
            return map;
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
}

public static Object copyArray(Object source){
        int length = Array.getLength(source);
        Class<?> type = source.getClass().getComponentType();
        Object array = Array.newInstance(type,length);
        for (int i=0;i<length;i++){
            Array.set(array,i, copy(type,Array.get(source,i)));
        }
        return array;
    }

以上就是关于深复制的全部代码了,其实代码十分的简洁易懂,简约而不简单,基本上能够满足你全部需求了


此后会陆续从json-script-rule中摘取相对较好的工具方法分享给大家

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九天流云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值