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 深克隆,换甁又换药