zoukankan      html  css  js  c++  java
  • 浅拷贝、深拷贝

    记得以前写过一篇,比这个详细,然后不见了



    1.浅拷贝

    浅拷贝是将对象的栈上的属性直接拷贝一份给新对象,基本类型是没有问题的,但引用类型会拷贝一个地址引用,本质使用的还是堆上的同一个对象,修改时会同时发生变化。浅拷贝需要实现 Cloneable接口,不然无法调用clone方法,返回的是Object对象,可在重写中修改返回类型


    public class User implements Cloneable{
    
        // 基本类型
        private String name;
        private String email;
        
        // 引用类型
        private Date birthday;
    
      	// 可重写,也可不写
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    
        public static void main(String[] args) throws CloneNotSupportedException {
            User prototype = new User("oldHowl","old@qq.com",new Date());
            User shallowUser  = (User) prototype.clone();
    
            prototype.setName("newHowl");
            prototype.setEmail("new@qq.com");
            prototype.getBirthday().setMonth(10);
            
            System.out.println("prototype: " + prototype);
            System.out.println("shallowUser" + shallowUser);
        }
    }
    
    // 修改原对象的基本类型的属性是不会改变克隆之后的对象属性
    // 修改引用类型,公用一个堆上的引用对象,那么克隆对象也会被修改,解决方法是使用深拷贝,月份变成了10月
    prototype: User{name='newHowl', email='new@qq.com', birthday=Tue Nov 02 14:29:35}
    shallowUser: User{name='oldHowl', email='old@qq.com', birthday=Tue Nov 02 14:29:35}
    




    2. 深拷贝

    对具有引用类型属性的对象进行copy,引用对象需要不是直接复制一个引用地址了,而是新建一个引用对象,这个需要手动重写clone方法


    public class User implements Cloneable {
    
        // 必须重写
        @Override
        protected Object clone() throws CloneNotSupportedException {
            // 对基本属性进行拷贝
            User deepClone = (User) super.clone();
            // 引用类型进行深拷贝
            deepClone.setBirthday((Date) deepClone.getBirthday().clone());
            return deepClone;
        }
    
        public static void main(String[] args) throws CloneNotSupportedException {
            
            User prototype = new User("oldHowl","old@qq.com",new Date());
            User shallowUser  = (User) prototype.clone();
    
            prototype.setName("newHowl");
            prototype.setEmail("new@qq.com");
            prototype.getBirthday().setMonth(10);
            
            System.out.println("prototype: " + prototype);
            System.out.println("shallowUser" + shallowUser);
        }
    }
    
    // 引用类型的月份没有改变了,证明引用对象也是一个新的对象
    prototype: User{name='newHowl', email='new@qq.com', birthday=Tue Nov 02 14:51:14}
    shallowUserUser{name='oldHowl', email='old@qq.com', birthday=Wed Jun 02 14:51:14}
    




    3. 拷贝工具类

    • 设置各种getter/setter手动复制(没人用吧)

    • Apache BeanUtils(阿里巴巴规范不建议使用)

    • Spring BeanUtils(性能比Apache高)


    3.1 Spring BeanUtils

    // 是浅拷贝,是浅拷贝
    // 注意Boolean类型生成的方法是isBoolean,要手动改写
    // 基于内省+反射,借助getter/setter拷贝
    // 只检查可访问性 和 属性名是否对应
    
    User prototype = new User("Howl", "xxx@qq.com", new Date());
    User shallowUser = new User();
    
    // 使用简单
    BeanUtils.copyProperties(prototype, shallowUser);
    

    3.2 BeanUtils.copyProperties的源码

    public static void copyProperties(Object source, Object target) throws BeansException {
        
        // 源对象,目标对象,需转化类型,需要忽视的对象
        copyProperties(source, target, (Class)null, (String[])null);
    }
    
    private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException {
        
        // 断言,前两个不为空
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");
        
        // 变量提升
        // 看看目标对象能否被转化,继承关系,接口关系可转化
        Class<?> actualEditable = target.getClass();
        if (editable != null) {
            if (!editable.isInstance(target)) {
                throw new IllegalArgumentException("Target not assignable to Editable class");
            }
            actualEditable = editable;
        }
    
        // 内省,属性描述器
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        
        // 看看是否有需要忽略的对象,传参过来的
        List<String> ignore = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
        
        // 辅助来遍历属性描述器
        PropertyDescriptor[] var7 = targetPds;
        int var8 = targetPds.length;
    
        // 遍历,var是jdk10的功能,下面主要判断源和目标对象的类型是否对应
        for(int var9 = 0; var9 < var8; ++var9) {
            PropertyDescriptor targetPd = var7[var9];
            Method writeMethod = targetPd.getWriteMethod();
            
            // 判断方法
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                
                // 源对象的属性描述器
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                
                // 源对象可读方法
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null) {
                        
                        // 源对象方法的返回值类型
                        ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod);
                        
                        // 目标方法的返回值类型
                        ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0);
                        
                        boolean isAssignable = !sourceResolvableType.hasUnresolvableGenerics() && !targetResolvableType.hasUnresolvableGenerics() ? targetResolvableType.isAssignableFrom(sourceResolvableType) : ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType());
                        
                        // 获取源对象的get方法然后使用获得 value
                        // 将获取的 value使用目标对象的set方式写入
                        if (isAssignable) {
                            try {
                                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                    // 读私有属性,设置可访问
                                    readMethod.setAccessible(true);
                                }
    
                                Object value = readMethod.invoke(source);
                                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                    // 写私有属性,设置可访问
                                    writeMethod.setAccessible(true);
                                }
    
                                writeMethod.invoke(target, value);
                            } catch (Throwable var18) {
                                throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var18);
                            }
                        }
                    }
                }
            }
        }
    }
    




  • 相关阅读:
    代码风格
    C语言带返回值的宏
    设计模式之PIMPL模式
    打印控制之VT100
    C语言实现反射
    C语言实现函数override
    [BZOJ3669] [NOI2004] 魔法森林 LCT维护最小生成树
    [BZOJ4826] [HNOI2017] 影魔 单调栈 主席树
    [BZOJ2054]疯狂的馒头 并查集
    [BZOJ5305] [HAOI2018] 苹果树 数学 组合计数
  • 原文地址:https://www.cnblogs.com/Howlet/p/15013283.html
Copyright © 2011-2022 走看看