记得以前写过一篇,比这个详细,然后不见了
浅拷贝是将对象的栈上的属性直接拷贝一份给新对象,基本类型是没有问题的,但引用类型会拷贝一个地址引用,本质使用的还是堆上的同一个对象,修改时会同时发生变化。浅拷贝需要实现 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}
对具有引用类型属性的对象进行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}
设置各种getter/setter手动复制(没人用吧)
Apache BeanUtils(阿里巴巴规范不建议使用)
Spring BeanUtils(性能比Apache高)
// 是浅拷贝,是浅拷贝 // 注意Boolean类型生成的方法是isBoolean,要手动改写 // 基于内省+反射,借助getter/setter拷贝 // 只检查可访问性 和 属性名是否对应 User prototype = new User("Howl", "xxx@qq.com", new Date()); User shallowUser = new User(); // 使用简单 BeanUtils.copyProperties(prototype, shallowUser);
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); } } } } } } }