引言
我们知道在Java中存在这个接口Cloneable,实现该接口的类都会具备被拷贝的能力,同时拷贝是在内存中进行,在性能方面比我们直接通过new生成对象来的快,特别是在大对象的生成上,使得性能的提升非常明显。然而我们知道拷贝分为深拷贝和浅拷贝之分,但是浅拷贝存在对象属性拷贝不彻底问题。
浅拷贝问题
package util.clone;
/**
* 职员
* @project Test
* @date 2018年4月23日 下午2:34:52
* @author Huaxu-Charles
*/
public class Staff implements Cloneable{
private String name;
private Uniform uniform;
public Staff(String name, Uniform uniform) {
this.name = name;
this.uniform = uniform;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Uniform getUniform() {
return uniform;
}
public void setUniform(Uniform uniform) {
this.uniform = uniform;
}
protected Staff clone() {
Staff staff = null;
try {
staff = (Staff) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return staff;
}
@Override
public String toString() {
return "Staff [name=" + name + ", uniform=" + uniform.getType() + "]";
}
}
package util.clone;
/**
* 制服
* @project Test
* @date 2018年4月23日 下午2:33:14
* @author Huaxu-Charles
*/
public class Uniform {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Uniform(String type) {
this.type = type;
}
@Override
public String toString() {
return "Uniform [type=" + type + "]";
}
}
上面代码展示的就是,公司新员工入职这个时候需要订做新的制服,因为每个人身材大小不一样,但是据大数据程序猿分析,穿xxl的占据80%,所以就优先声明制服的默认大小为xxl。这个时候看输出结果:
public class Client {
public static void main(String[] args) {
Staff staff1 = new Staff("张三", new Uniform("XXL"));
Staff staff2 = staff1.clone();
staff2.setName("李四");
Staff staff3 = staff1.clone();
staff3.setName("王五");
System.out.println(staff1);
System.out.println(staff2);
System.out.println(staff3);
}
}
输出结果
Staff [name=张三, uniform=XXL]
Staff [name=李四, uniform=XXL]
Staff [name=王五, uniform=XXL]
现在看起来是没问题,大概率通用一套。如果王五的需要的尺码是xxxl,这个时候我们只需要修改一下代码
public class Client {
public static void main(String[] args) {
Staff staff1 = new Staff("张三", new Uniform("XXL"));
Staff staff2 = staff1.clone();
staff2.setName("李四");
Staff staff3 = staff1.clone();
staff3.setName("王五");
// 王五需要XXXL的
staff3.getUniform().setType("XXXL");
System.out.println(staff1);
System.out.println(staff2);
System.out.println(staff3);
}
}
Staff [name=张三, uniform=XXXL]
Staff [name=李四, uniform=XXXL]
Staff [name=王五, uniform=XXXL]
这个时候结果就不是那么满意,你王五需要xxxl的,但是张三李四需要的是xxl的啊!其实出现问题的关键在于clone()方法上我们知道该clone()方法是使用object的clone()方法,但是该方法存在缺陷,它并不会将对象的所有属性全部拷贝过来,而是有选择性的拷贝,基本规则整理如下:
基本类型 | 如果变量是基本数据类型,则拷贝其值,如int,float等 |
对象 | 如果变量是一个实例对象,则拷贝其地址引用,也就是说此时新对象与原来对象是公用该实例变量 |
String 字符串 | 若变量为String字符串,则拷贝其地址引用,但是在修改的时候,它会从字符串池中重新生成一个新的字符串,原来的保持不变 |
protected Staff clone() {
Staff staff = null;
try {
staff = (Staff) super.clone();
// 重新建一个对象
staff.setUniform(new Uniform(staff.getUniform().getType()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return staff;
}
所以:clone不利于直接使用,它只是java提供的一个简单的方法。
这样问题虽然解决,但是如果工程很大,这样就会需要new大量的对象,根本还是深拷贝。而且每一个类都需要写一个这样的方法,不利于后期维护。
利用序列化实现对象的拷贝
输出结果
/**
* 克隆工具类
* @project Test
* @date 2018年4月23日 下午3:00:16
* @author Huaxu-Charles
*/
public class CloneUtils {
@SuppressWarnings("unchecked")
public static<T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
// 写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs =new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
// 分配内存,写入原始对象,生成新对象
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(in);
// 返回生成的对象
cloneObj = (T) ois.readObject();
ois.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return cloneObj;
}
}
值得注意的是序列化的对象必须实现Serializable接口,至此这个问题完美解决,需要用此工具类的方法只需要实现Serializable接口。
输出结果
Staff [name=张三, uniform=XXL]
Staff [name=李四, uniform=XXL]
Staff [name=王五, uniform=XXXL]