问:Java 中集合默认的 clone 是深度克隆还是浅度克隆,有什么开发经验可以分享?
答:集合的默认 clone() 方法都是浅克隆,而且集合类提供的拷贝构造方法或 addAll、add 等方法都是浅克隆,也就是说存储在原集合和克隆集合中的对象会保持一致并指向堆中同一内存地址。关于集合克隆(拷贝)的开发经验其实就是一些常见场景的归类分析,具体如下。
常见的集合浅克隆(拷贝复制)操作经验如下:
//使用集合默认的 clone 方法复制(浅)
List<InfoBean> destList = (List<InfoBean>) srcList.clone();
//使用 add 方法循环遍历复制(浅)
List<InfoBean> destList = new ArrayList<InfoBean>(srcList.size());
for(InfoBean bean : srcList){
destList.add(bean);
}
//使用 addAll 方法复制(浅)
List<InfoBean> destList = new ArrayList<InfoBean>();
destList.addAll(srcList);
//使用构造方法复制(浅)
List<InfoBean> destList = new ArrayList<InfoBean>(srcList);
//使用System.arraycopy()方法复制(浅)
InfoBean[] srcBeans = srcList.toArray(new InfoBean[0]);
InfoBean[] destBeans= new InfoBean[srcBeans.length];
System.arraycopy(srcBeans, 0, destBeans, 0, srcBeans.length);
常见的集合浅克隆(拷贝复制)操作经验如下:
//通过add和clone实现集合深度拷贝
class InfoBean implements Cloneable {
public String name;
public int age;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
for (int index=0; index<src.size(); index++) {
destList.add((InfoBean)srcList.get(index).clone());
}
//使用序列化方法对集合进行深度拷贝
public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
objOut.writeObject(src);
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream objIn = new ObjectInputStream(byteIn);
return (List<T>) objIn.readObject();
}
List<InfoBean> destList = deepCopy(srcList);
问:下面程序的运行结果是什么?为什么?
class InfoBean {
public String name;
public int age;
}
InfoBean bean1 = new InfoBean();
InfoBean bean2 = new InfoBean();
List<InfoBean> srcList = new ArrayList();
srcList.add(bean1);
srcList.add(bean2);
ArrayList<InfoBean> destList = (ArrayList<InfoBean>) srcList.clone();
destList.remove(0);
System.out.println("srcList="+srcList.size());
System.out.println("destList="+destList.size());
答:本题考查 Java 集合的浅 clone 特性,答案如下。
srcList=2
destList=1
因为 destList 是 srcList 的浅 clone,所以当使用 remove 方法移除掉集合中的对象且不修改集合中对象的值时只是在 destList 的 List 内部实现数组中移除了指向元素的地址,故对 srcList 无影响。
问:实现对象克隆常见的方式有哪些,具体怎么做?
答:常见的实现方式主要有三种。
-
通过自己写一个克隆方法里面 new 一个同样的对象来进行 get、set 依次赋值实现深度克隆(很繁琐且易出错);
-
通过实现 Cloneable 接口并重写 Object 类的 clone() 方法(分为深浅两种方式);
-
通过实现 Serializable 接口并用对象的序列化和反序列化来实现真正的深度克隆;
自己实现方法 new 对象 get、set 的方式因为非常不优雅和没有实际作用,所以不再给出具体示例。
通过实现 Cloneable 接口并重写 Object 类的 clone() 方法实现浅克隆做法:Object 类中 clone 方法的默认实现最终是一个 native 方法(如果 clone 类没有实现 Cloneable 接口并调用了 Object 的 clone 方法就会抛出 CloneNotSupportedException 异常,因为 clone 方法默认实现中有使用 instanceof 进行接口判断),相对来说效率高些,默认实现是先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容,对基本数据类型就是值复制,而对非基本类型变量保存的仅仅是对象的引用,所以会导致 clone 后的非基本类型变量和原始对象中相应的变量指向的是同一个对象。
class InfoBean implements Cloneable {
public String name;
public int age;
public InfoBean clone() {
try {
return (InfoBean) super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
通过实现 Cloneable 接口并重写 Object 类的 clone() 方法实现深克隆做法:
在浅度克隆的基础上对于要克隆对象中的非基本数据类型的属性对应的类也实现克隆,这样对于非基本数据类型的属性复制的不是一份引用。
class InfoBean implements Cloneable {
public String name;
public int age;
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
class PeopleBean implements Cloneable {
public String vipId;
public InfoBean infoBean;
public Object clone() {
try {
PeopleBean bean = (PeopleBean) super.clone();
bean.infoBean = (InfoBean) infoBean.clone();
return bean;
} catch (CloneNotSupportedException e) {
return null;
}
}
}
通过 Serializable 接口并用对象的序列化和反序列化来实现真正的深度克隆做法:
对象序列化操作可以将对象的状态转换成字节流传输或者存储再生,我们可以借用这一特点实现对象的深度克隆,特别是当我们的对象嵌套非常复杂且想实现深度克隆时如果使用序列化方式会大大减少代码量。
class CloneUtil {
public static <T extends Serializable> T clone(T obj) {
T cloneObj = null;
try {
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
objOut.writeObject(obj);
objOut.close();
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream objIn = new ObjectInputStream(byteIn);
cloneObj = (T) objIn.readObject();
objIn.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return cloneObj;
}
}
class InfoBean implements Serializable {
public String name;
public int age;
}
class PeopleBean implements Serializable {
public String vipId;
public InfoBean infoBean;
public Object clone() {
return CloneUtil.clone(this);
}
}
老铁,别嫌短,长了你肯定不会看完的,所以这就是码农每日一题的宗旨~
看完分享一波朋友圈嘛,小编也是下班后花很多心血来运营的,和你的小伙伴一起讨论才更加有意思~
看个笑话放松一下
程序猿问科比:“你为什么这么成功? ”科比:“你知道洛杉矶凌晨四点是什么样子吗? ”程序猿:“知道,一般那个时候我还在写代码,怎么了?”科比:“额…….”
We can't help everyone, but everyone can help someone.
我们不能帮助每一个人,但每个人都可以帮助别人。