深递归拷贝

该博客主要讨论了Java中实现对象深拷贝的一种方法,通过反射调用属性的getter方法获取源对象的值,并根据值的类型创建新对象进行赋值。在处理过程中,针对不同类型的值(如基本类型、日期、集合、Map等)进行了特殊处理,确保深拷贝的准确性。同时,文章提到了可能出现的`IllegalArgumentException`异常,并提供了解决方案,即确保调用的方法与目标对象实例匹配。
    public static <T> void copyPropertiesDoDeepCopy(T source, T target) {
        if (source == null) {
            throw DasException.internalError(null, "Internal_error");
        }
        if (target == null) {
            throw DasException.internalError(null, "Internal_error");
        }
        try {

            if (source instanceof Collection) {
                if (source instanceof List) {
                    List invokeSouceClassInstance = (List) source;
                    List invokeTargeClassInstance = (List) target;
                    for (Object invokeSouceTypeListItem : invokeSouceClassInstance) {
                        Object invokeTargeTypeListItem = invokeSouceTypeListItem.getClass().newInstance();
                        copyPropertiesDoDeepCopy(invokeSouceTypeListItem, invokeTargeTypeListItem);
                        invokeTargeClassInstance.add(invokeTargeTypeListItem);
                    }
                }
            } else if (source instanceof Array) {

            } else {
                BeanInfo beanInfoSource = Introspector.getBeanInfo(source.getClass());

                PropertyDescriptor[] propertyDescriptorsSource = beanInfoSource.getPropertyDescriptors();

                for (PropertyDescriptor propertyDescriptorSource : propertyDescriptorsSource) {
                    Method readMethod = propertyDescriptorSource.getReadMethod();
                    Object invokeSouce = readMethod.invoke(source);
                    if (invokeSouce instanceof Class) {
                        continue;
                    }
                    if (null == invokeSouce) {
                        continue;
                    }
                    if (invokeSouce instanceof Number) {
                        if (invokeSouce instanceof Double) {
                            Double invokeTarget = new Double((Double) invokeSouce);

                            PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                            Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                            writeMethodTarget.invoke(target, invokeTarget);
                        } else if (invokeSouce instanceof Float) {
                            Float invokeTarget = new Float((Float) invokeSouce);

                            PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                            Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                            writeMethodTarget.invoke(target, invokeTarget);
                        } else if (invokeSouce instanceof Long) {
                            Long invokeTarget = new Long((Long) invokeSouce);

                            PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                            Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                            writeMethodTarget.invoke(target, invokeTarget);
                        } else if (invokeSouce instanceof Integer) {
                            Integer invokeTarget = new Integer((Integer) invokeSouce);

                            PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                            Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                            writeMethodTarget.invoke(target, invokeTarget);
                        } else if (invokeSouce instanceof Short) {
                            Short invokeTarget = new Short((Short) invokeSouce);

                            PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                            Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                            writeMethodTarget.invoke(target, invokeTarget);
                        } else if (invokeSouce instanceof Byte) {
                            Byte invokeTarget = new Byte((Byte) invokeSouce);

                            PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                            Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                            writeMethodTarget.invoke(target, invokeTarget);
                        }
                    } else if (invokeSouce instanceof String) {
                        String invokeTarget = (String) invokeSouce;

                        PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                        Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                        writeMethodTarget.invoke(target, invokeTarget);
                    } else if (invokeSouce instanceof Boolean) {
                        // 布尔类型
                        Boolean invokeTarget = (Boolean) invokeSouce;

                        PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                        Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                        writeMethodTarget.invoke(target, invokeTarget);
                    } else if (invokeSouce instanceof Date) {
                        // 日期类型
                        Date invokeTarget = new Date(((Date) invokeSouce).getTime());

                        PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                        Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                        writeMethodTarget.invoke(target, invokeTarget);
                    } else if (invokeSouce instanceof Collection) {
                        if (invokeSouce instanceof List) {
                            List invokeSouceType = (List) invokeSouce;
                            List invokeTargeType = new LinkedList();
                            for (Object invokeSouceTypeListItem : invokeSouceType) {
                                Object invokeTargeTypeListItem = invokeSouceTypeListItem.getClass().newInstance();
                                copyPropertiesDoDeepCopy(invokeSouceTypeListItem, invokeTargeTypeListItem);
                                invokeTargeType.add(invokeTargeTypeListItem);
                            }

                            PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                            Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                            writeMethodTarget.invoke(target, invokeTargeType);
                        }
                    } else if (invokeSouce instanceof Map) {
                        Map invokeSouceType = (Map) invokeSouce;
                        Map invokeTargeType = (Map) invokeSouce.getClass().newInstance();
                        for (Object entry : invokeSouceType.entrySet()) {
                            Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) entry;
                            Object invokeTargeTypeMapValue = mapEntry.getValue().getClass().newInstance();
                            copyPropertiesDoDeepCopy(mapEntry.getValue(), invokeTargeTypeMapValue);
                            invokeTargeType.put(mapEntry.getKey(), invokeTargeTypeMapValue);
                        }

                        PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                        Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                        writeMethodTarget.invoke(target, invokeTargeType);
                    } else if (invokeSouce instanceof Enum) {
                        // 枚举类型
                        Enum invokeTarget = (Enum) invokeSouce;

                        PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                        Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                        writeMethodTarget.invoke(target, invokeTarget);
                    } else {
                        Object invokeTarget = invokeSouce.getClass().newInstance();
                        copyPropertiesDoDeepCopy(invokeSouce, invokeTarget);

                        PropertyDescriptor propertyDescriptorTarget = new PropertyDescriptor(propertyDescriptorSource.getName(), target.getClass());
                        Method writeMethodTarget = propertyDescriptorTarget.getWriteMethod();
                        writeMethodTarget.invoke(target, invokeTarget);
                    }
                }
            }
        } catch (Exception e) {
            throw DasException.internalError(e, "Internal_error");
        }
    }

BUG:

writeMethodTarget.invoke(target, invokeTarget);

Caused by: java.lang.IllegalArgumentException: object is not an instance of declaring class

解决方案:writeMethodTarget 和 target必须是一一对应的对象实例和方法。通过propertyDescriptor获得的WriteMethod需要与invoke方法的第一个参数保持一致。

<think> 我们正在回答用户关于JavaScript递归深拷贝的问题。根据用户提供的引用,我们可以看到几种不同的递归深拷贝实现方法。我们将结合这些引用,给出一个完整的、健壮的递归深拷贝实现,并解释其原理和注意事项。 引用[1]提供了两种方法:递归和JSON方法。但JSON方法有局限性(不能处理函数、undefined等),所以我们重点看递归方法。 引用[2]给出了一个递归深拷贝方法,并提到可以拷贝多维数组、对象和函数(但注意,函数在深拷贝中通常被认为是无需拷贝的,因为函数是共享的,且一般不会改变)。 引用[3]也给出了递归深拷贝方法,并演示了深拷贝成功。 引用[4]提到了使用lodash库,但用户要求自己实现,所以不采用。 综合以上,我们将实现一个递归深拷贝函数,并考虑以下情况: 1. 基本类型(直接返回) 2. 数组和对象(递归拷贝) 3. 循环引用(避免无限递归) 4. 其他特殊对象(如Date、RegExp等)的处理?(但引用中未涉及,用户问题也未要求,我们暂时不处理,但可以提一下) 然而,在提供的引用中,都没有处理循环引用的问题。循环引用指的是对象的属性直接或间接地引用了自身,这样在递归时会进入死循环导致栈溢出。 因此,我们将在实现中加入一个缓存(使用WeakMap)来解决循环引用问题。 步骤: 1. 判断是否是基本数据类型,是则直接返回。 2. 如果是对象(包括数组),则检查缓存中是否已经拷贝过该对象,如果有则直接返回缓存中的拷贝对象(避免循环引用导致的无限递归)。 3. 根据对象类型初始化一个新的对象(数组或对象)。 4. 将当前对象存入缓存(在递归之前存入,这样在递归中如果再次遇到该对象就可以从缓存中取)。 5. 递归拷贝对象的每个属性(包括Symbol属性,引用中未考虑,但我们可以考虑)。 6. 注意:只拷贝对象自身的属性(使用hasOwnProperty判断)。 但是,引用中提供的例子都没有考虑Symbol属性,也没有考虑其他内置对象(如Date、RegExp、Map、Set等)。由于用户问题中只要求递归深拷贝,我们可以先实现一个基本版本,然后说明其不足之处。 根据用户要求,我们给出一个基础版本(处理循环引用)和一个增强版本(处理更多类型)的讨论。 我们先实现基础版本(处理基本类型、数组、对象、循环引用): ``` function deepClone(obj, map = new WeakMap()) { // 如果是基本数据类型,直接返回 if (obj === null || typeof obj !== 'object') { return obj; } // 检查缓存,避免循环引用 if (map.has(obj)) { return map.get(obj); } // 根据obj的类型初始化一个新的对象 let clone = Array.isArray(obj) ? [] : {}; // 将当前对象存入缓存 map.set(obj, clone); // 递归复制所有自有属性(包括可枚举的Symbol属性) // 注意:这里我们使用Reflect.ownKeys可以获取包括Symbol在内的所有自有属性(包括不可枚举?但深拷贝通常只考虑可枚举属性?) // 但是,引用中的例子都是使用for...in,而for...in会遍历原型链,所以他们都用了hasOwnProperty。而Reflect.ownKeys只获取自有属性(包括不可枚举和Symbol),但我们通常深拷贝拷贝可枚举属性。 // 因此,我们可以使用Object.keys(obj).concat(Object.getOwnPropertySymbols(obj))来获取所有可枚举的自有属性(包括Symbol)。但这样会忽略不可枚举属性?通常深拷贝忽略不可枚举属性。 // 获取所有可枚举的自有属性(包括Symbol) const keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj).filter(sym => obj.propertyIsEnumerable(sym))]; for (let key of keys) { clone[key] = deepClone(obj[key], map); } return clone; } ``` 但是,上面的方法不能处理Date、RegExp等特殊对象,也不能处理Map、Set等。用户如果要求深拷贝这些,我们需要扩展。 由于用户问题中并没有明确要求,我们给出一个基础版本,并说明其局限性,然后给出一个更完整的版本作为扩展。 基础版本(不考虑特殊对象)已经可以处理普通对象、数组、循环引用。 测试循环引用: ``` let obj = { a: 1 }; obj.self = obj; let cloneObj = deepClone(obj); console.log(cloneObj.self === cloneObj); // 应该为true,表示循环引用被正确处理 ``` 但是,上面的基础版本在处理函数时,函数会被直接返回(因为函数是对象,但是我们在第一行判断了`typeof obj !== 'object'`,注意函数的typeof是'function',而'function'不是'object',所以会直接返回。因此函数是浅拷贝,但通常函数不需要深拷贝。 接下来,我们考虑增强版本,处理一些内置对象: 我们可以这样扩展: 1. 判断obj的具体类型,如果是Date、RegExp、Map、Set等,则调用相应的构造函数复制。 2. 注意:Map和Set中的值也需要递归深拷贝。 由于用户问题要求递归深拷贝,我们提供基础版本,并讨论增强版本。 根据引用[3]中的例子,我们只需要普通对象和数组的深拷贝。因此,我们按基础版本来回答。 但是,我们注意到引用[2]中声称可以拷贝函数,但实际上函数没有被递归,而是直接赋值(因为函数不是object类型,所以直接返回)。所以函数是浅拷贝,这是可以接受的。 因此,我们最终给出基础版本(处理循环引用、普通对象、数组)的深拷贝函数。 另外,我们也可以提供一个简单版本(不处理循环引用)作为对比,但用户要求递归深拷贝,并且实际应用中循环引用很常见,所以我们推荐带缓存的基础版本。 现在,我们按照用户要求,用中文回答,结构清晰。 回答结构: 1. 深拷贝的概念和递归方法的基本原理。 2. 递归深拷贝的基础实现(处理基本类型、数组、对象、循环引用)。 3. 代码示例。 4. 说明该实现的局限性(如不处理Date等特殊对象)。 5. 如何扩展以处理更多类型(简要说明)。 同时,生成相关问题。 </think>### JavaScript 递归深拷贝实现方法 在 JavaScript 中,递归深拷贝是一种创建对象完全独立副本的技术,新对象与原对象无任何引用关系。以下是几种实现方法: #### 1. 基础递归实现 ```javascript function deepClone(obj) { // 处理基本类型或 null if (obj === null || typeof obj !== 'object') { return obj; } // 初始化拷贝对象(区分数组/普通对象) const copy = Array.isArray(obj) ? [] : {}; // 递归拷贝每个属性 for (let key in obj) { if (obj.hasOwnProperty(key)) { copy[key] = deepClone(obj[key]); } } return copy; } // 使用示例 const original = { a: 1, b: { c: 2 } }; const cloned = deepClone(original); cloned.b.c = 99; // 修改副本不影响原对象 console.log(original.b.c); // 输出: 2 (深拷贝成功)[^3] ``` #### 2. 增强版(处理循环引用) ```javascript function deepClone(obj, map = new WeakMap()) { if (obj === null || typeof obj !== 'object') { return obj; } // 检查循环引用 if (map.has(obj)) return map.get(obj); const copy = Array.isArray(obj) ? [] : {}; map.set(obj, copy); // 存储已拷贝对象 // 处理 Symbol 属性 const keys = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)]; for (const key of keys) { copy[key] = deepClone(obj[key], map); } return copy; } // 测试循环引用 const obj = { a: 1 }; obj.self = obj; // 循环引用 const clone = deepClone(obj); console.log(clone.self === clone); // 输出: true (循环引用正确处理) ``` #### 3. 使用 JSON 方法(简易版,有局限) ```javascript function simpleDeepClone(obj) { return JSON.parse(JSON.stringify(obj)); } // 局限性:无法复制函数/undefined/循环引用 const obj = { fn: () => console.log('test') }; console.log(simpleDeepClone(obj).fn); // 输出: undefined[^1] ``` ### 关键特性说明 1. **递归处理**:遇到对象属性时递归调用自身 2. **循环引用处理**:通过 WeakMap 存储已拷贝对象 3. **类型区分**: - 基本类型直接返回 - 数组 → 初始化空数组 - 对象 → 初始化空对象 4. **特殊属性**: - 支持 Symbol 作为属性名 - 使用 `hasOwnProperty` 过滤原型链属性 ### 注意事项 1. **函数处理**:函数按引用复制(通常无需深拷贝) 2. **特殊对象**:未处理 Date/RegExp/Map/Set 等(需额外判断) 3. **性能考量**:深层次嵌套对象可能导致栈溢出 对于更复杂的场景,推荐使用成熟库如 Lodash 的 `_.cloneDeep()` 方法[^4]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值