问:为什么需要克隆?
答:因为在编程中会遇到一种情况,有一个 DemoBean 的对象实例 demo1 在某一时刻已经包含了一些有效值,此时可能会需要一个和 demo1 完全相同的新对象 demo2 且此后对 demo1 的任何改动都不影响到 demo1 中的值,在 Java 中用简单的赋值语句是不能满足这种需求的,所以我们需要使用其他的途径来保证 demo1 与 demo2 是两个独立的对象且 demo2 的初始值是由 demo1 对象确定的,而克隆就是其官方提供的一种接口定义(至少比主动 new 对象然后取值赋值方便)。
问:浅度克隆(浅拷贝)和深度克隆(深拷贝)的区别是什么?
浅度克隆:
被复制对象(一个新的对象实例)的所有变量都含有与原来的对象相同的值,对于基本数据类型的属性复制一份给新产生的对象,对于非基本数据类型的属性仅仅复制一份引用给新产生的对象(新实例中引用类型属性还是指向原来对象引用类型属性)。深度克隆:
被复制对象(一个新的对象实例)的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象(新内存空间),而不再是原有的那些被引用的对象,换言之深度克隆把要复制的对象所引用的对象都复制了一遍,也就是在浅度克隆的基础上,对于要克隆的对象中的非基本数据类型的属性对应的类也实现克隆,这样对于非基本数据类型的属性复制的不是一份引用。
问:String 克隆的特殊性在哪里?StringBuffer 和 StringBuilder 呢?
答:由于基本数据类型都能自动实现深度 clone,引用类型默认实现的是浅度 clone,而 String 是引用类型的一个特例,我们可以和操作基本数据类型一样认为其实现了深度 clone(实质是浅克隆,切记只是一个假象),由于 String 是不可变类,对于 String 类中的很多修改操作都是通过新 new 对象复制处理的,所以当我们修改 clone 前后对象里面 String 属性的值时其实都是属性引用的重新指向操作,自然对 clone 前后对象里 String 属性是没有相互影响的,类似于深度克隆;所以虽然他是引用类型而且我们在深度克隆时无法调用其 clone 方法,但是其不影响我们深度克隆的使用。
如果要实现深度克隆则 StringBuffer 和 StringBuilder 是需要主动特殊处理的,否则就是真正的对象浅克隆,所以处理的办法就是在类的 clone 方法中对 StringBuffer 或者 StringBuilder 属性进行如下主动拷贝操作。
再次强调,这个区别非常重要,即便不是面试题自己开发中也要注意这个坑。
问:Java 中集合默认的 clone 是深度克隆还是浅度克隆,有什么开发经验可以分享?
答:集合的默认 clone() 方法都是浅克隆,而且集合类提供的拷贝构造方法或 addAll、add 等方法都是浅克隆,也就是说存储在原集合和克隆集合中的对象会保持一致并指向堆中同一内存地址。关于集合克隆(拷贝)的开发经验其实就是一些常见场景的归类分析,具体如下。
常见的集合浅克隆(拷贝复制)操作经验如下:
//使用集合默认的 clone 方法复制(浅)
List destList = (List) srcList.clone(); //使用 add 方法循环遍历复制(浅)
List destList = new ArrayList(srcList.size()); for(
InfoBean bean :srcList )
{
destList.add(bean);
} //使用 addAll 方法复制(浅)
List destList = new ArrayList(); destList.addAll(srcList );
//使用构造方法复制(浅)
List destList = new ArrayList(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 List deepCopy(List 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) objIn.readObject();
}
List destList = deepCopy(srcList);
问:下面程序的运行结果是什么?为什么?
class InfoBean {
public String name;
public int age;
}
InfoBean bean1 = new InfoBean();
InfoBean bean2 = new InfoBean();
List srcList = new ArrayList();
srcList.add(bean1);
srcList.add(bean2);
ArrayList destList = (ArrayList) 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 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);
}
}