hutool 工具包 深克隆踩坑

文章介绍了Hutool库中如何使用ObjectUtil进行对象的浅克隆(UserList)和深克隆(UserList2),以及深克隆背后的原理,涉及序列化和反射机制。
摘要由CSDN通过智能技术生成
package hutool;

import cn.hutool.core.util.ObjectUtil;


import java.util.ArrayList;
import java.util.List;

public class CloneTest {


    public static void main(String[] args) {

        User user = new User();
        user.setId(1);
        user.setAge(18);
        List<User> userList= new ArrayList<>();
        userList.add(user);
        for (User user1 : userList) {
            System.out.println(user1);
        }
        List<User> userList2 = ObjectUtil.clone(userList);
        for (User user1 : userList2) {
            System.out.println(user1);
        }
        System.out.println(userList==userList2);

    }
}
package hutool;


import java.io.Serializable;

public class User implements Serializable {

    int age;
    int id;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

 上面输出结果

hutool.User@5594a1b5
hutool.User@5594a1b5
false

userList2  == userList  输出 false,两个list 是不同的对象

两个list 里面的元素user 是地址相同的,即指向同一个对象

所以 list 里面的元素是同一个user. 想要需要深克隆list里面的元素,可以使用

ObjectUtil.cloneByStream(userList)
package hutool;

import cn.hutool.core.util.ObjectUtil;


import java.util.ArrayList;
import java.util.List;

public class CloneTest {


    public static void main(String[] args) {

        User user = new User();
        user.setId(1);
        user.setAge(18);
        List<User> userList= new ArrayList<>();
        userList.add(user);

        List<User> userList1 = ObjectUtil.cloneByStream(userList)
        System.out.println(userList);
        System.out.println(userList1);


    }
}

输出

[hutool.User@29ee9faa]
[hutool.User@3f49dace]

接下来让我们看看hutool底层的实现, 从 ObjectUtil.clone(userList) 进去

	/**
	 * 克隆对象<br>
	 * 如果对象实现Cloneable接口,调用其clone方法<br>
	 * 如果实现Serializable接口,执行深度克隆<br>
	 * 否则返回<code>null</code>
	 *
	 * @param <T> 对象类型
	 * @param obj 被克隆对象
	 * @return 克隆后的对象
	 */
	public static <T> T clone(T obj) {
		T result = ArrayUtil.clone(obj);
		if (null == result) {
			if (obj instanceof Cloneable) {
				result = ReflectUtil.invoke(obj, "clone");
			} else {
				result = cloneByStream(obj);
			}
		}
		return result;
	}

T result = ArrayUtil.clone(obj);  先判断是否为 null,是的话返回null ,是否数组,是数组那么拷贝

	/**
	 * 克隆数组,如果非数组返回<code>null</code>
	 * 
	 * @param <T> 数组元素类型
	 * @param obj 数组对象
	 * @return 克隆后的数组对象
	 */
	@SuppressWarnings("unchecked")
	public static <T> T clone(final T obj) {
		if (null == obj) {
			return null;
		}
		if (isArray(obj)) {
			final Object result;
			final Class<?> componentType = obj.getClass().getComponentType();
			if (componentType.isPrimitive()) {// 原始类型
				int length = Array.getLength(obj);
				result = Array.newInstance(componentType, length);
				while (length-- > 0) {
					Array.set(result, length, Array.get(obj, length));
				}
			} else {
				result = ((Object[]) obj).clone();
			}
			return (T) result;
		}
		return null;
	}

回到原来的位置

if (null == result) {
			if (obj instanceof Cloneable) {
				result = ReflectUtil.invoke(obj, "clone");
			} else {
				result = cloneByStream(obj);
			}
		}
  /**
     * Returns a shallow copy of this {@code ArrayList} instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this {@code ArrayList} instance
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
 

这个我们刚刚传的参数 是 userList ,它的类型是 ArrayList , 而 

ArrayList 是实现了  Cloneable 接口的,所有会调用 ArrayList 的 clone 方法

v.elementData = Arrays.copyOf(elementData, size);

这行代码就是没有深克隆的原因,我们可以断点进去看看,

此时的 user 地址是 User@715, 走到下一行之后  v.elementData 的地址也是 User@715,

但是 v 是新的地址,底层用的数组的拷贝 

Arrays.copyOf(elementData, size)
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    @IntrinsicCandidate
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

arraycopy 被native 修饰看不到源码,但是我们可以确定 这个方法只是将 src数组的内容地址也赋值到dest数组内容的地址。

接下来我们看看 

ObjectUtil.cloneByStream 源码
public static <T> T cloneByStream(T obj) {
		if (false == (obj instanceof Serializable)) {
			return null;
		}
		final FastByteArrayOutputStream byteOut = new FastByteArrayOutputStream();
		ObjectOutputStream out = null;
		try {
			out = new ObjectOutputStream(byteOut);
			out.writeObject(obj);
			out.flush();
			final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
			return (T) in.readObject();
		} catch (Exception e) {
			throw new UtilException(e);
		} finally {
			IoUtil.close(out);
		}
	}

 看到 

ObjectOutputStream 和 ObjectInputStream 就知道了使用java原生的io对象流来下创建新对象,这里就不深入了,不知道的小伙伴自己补充一下不足。

总结:如果只是读取List里面对象的属性,可以使用 

ObjectUtil.clone 浅克隆,换瓶不换药

如果想要修改List里面对象的属性,而不改变原来对象的属性,那么使用

ObjectUtil.cloneByStream 深克隆,换甁又换药
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值