浅拷贝、深拷贝

2021/7/14 23:13:00

本文主要是介绍浅拷贝、深拷贝,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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



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);
                        }
                    }
                }
            }
        }
    }
}






这篇关于浅拷贝、深拷贝的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程