引言
我们知道在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]