人物:小菜,大鸟
事件:小菜正在准备求职见面会,打印了很多简历,大鸟让小菜试着用程序写简历
了解原型模型:
1.通过用原始的逻辑实现几份相同简历的打印
2.思考有没有一种模型可以帮助小菜不用新建对象,直接复制简历 --引出原型模型
3.通过试验值复制和引用复制,知道了深复制和浅复制,然后引出深复制的实现原理和实现内容
小菜简历代码初步实现
简历类:
@Slf4j public class Resume { private String name; private String sex; private String age; private String timeArea; private String company; public Resume(String name) { this.name = name; } /** * 设置个人信息 * * @param sex * @param age */ public void setPersonalInfo(String sex, String age) { this.sex = sex; this.age = age; } /** * 设置工作经历 * * @param timeArea * @param company */ public void setWorkExperience(String timeArea, String company) { this.timeArea = timeArea; this.company = company; } /** * 显示 */ public void display() { log.info("名字:{} 性别:{} 年龄:{}", name, sex, age); log.info("工作经历:{}, {}", timeArea, company); } }
打印三份,客户端调用代码:
public class ResumeDisplay { public static void main(String[] args) { Resume a = new Resume("大鸟"); Resume b = new Resume("大鸟"); Resume c = new Resume("大鸟"); a.setPersonalInfo("男", "29"); b.setPersonalInfo("男", "29"); c.setPersonalInfo("男", "29"); a.setWorkExperience("2019-2020", "凡人修仙公司"); b.setWorkExperience("2019-2020", "凡人修仙公司"); c.setWorkExperience("2019-2020", "凡人修仙公司"); a.display(); b.display(); c.display(); } }
大鸟:这样如果要打印20份,也就要实例20个对象,如果写好20份后,如果要改下年份,那不是20个要一个一个改么?你其实可以先这样改下:
public class ResumeDisplay { public static void main(String[] args) { Resume a = new Resume("大鸟"); a.setPersonalInfo("男", "29"); a.setWorkExperience("2018-2020", "凡人修仙公司"); Resume b = a; Resume c = a; a.display(); b.display(); c.display(); } }
大鸟:这样就比刚才好多了,其实是传引用,不是传值,就像一份简历,每次复制与内容有关,与用什么纸无关,下面我们就可以开始了解原型模型了
简历的原型模型实现
原型模型:用原型模型指定创建对象的种类,并通过拷贝这些原型创建新的对象(而且不用知道创建的细节)
简历类:
@Slf4j public class Resume implements Cloneable { private String name; private String sex; private String age; private String timeArea; private String company; public Resume(String name) { this.name = name; } /** * 设置个人信息 * * @param sex * @param age */ public void setPersonalInfo(String sex, String age) { this.sex = sex; this.age = age; } /** * 设置工作经历 * * @param timeArea * @param company */ public void setWorkExperience(String timeArea, String company) { this.timeArea = timeArea; this.company = company; } /** * 显示 */ public void display() { log.info("名字:{} 性别:{} 年龄:{}", name, sex, age); log.info("工作经历:{}, {}", timeArea, company); } @Override public Object clone() { Resume resume = null; try { resume = (Resume) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return resume; } }
客户端调用:
@Slf4j public class ResumeDisplay { public static void main(String[] args) { Resume a = new Resume("大鸟"); a.setPersonalInfo("男", "29"); a.setWorkExperience("2019-2020", "凡人修仙功法公司"); Resume b = (Resume) a.clone(); b.setWorkExperience("2020-2021", "凡人修仙宝器公司"); Resume c = (Resume) a.clone(); c.setWorkExperience("2020-2021", "凡人修仙炼体公司"); a.display(); b.display(); c.display(); } }
大鸟:对的,这样实现后,这样每次复制简历或者要修改简历,都是直接克隆,而不是再去创建一个新的实例,大大提高了性能,还隐藏了实现的细节
大鸟:但注意了,我们刚才复制的,都是String类型,也就是都是值类型,但如果是引用类型,就只会复制引用,不会复制引用的类型,因此原始对象及其复本引用同一对象
如下:
@Slf4j @Data public class Resume implements Cloneable { private String name; private String sex; private String age; private String timeArea; private String company; private WorkExperience work; public Resume(String name) { this.name = name; work = new WorkExperience(); } /** * 设置个人信息 * * @param sex * @param age */ public void setPersonalInfo(String sex, String age) { this.sex = sex; this.age = age; } /** * 设置工作经历 * * @param timeArea * @param company */ public void setWorkExperience(String timeArea, String company) { work.setTimeArea(timeArea); work.setCompany(company); } /** * 显示 */ public void display() { log.info("名字:{} 性别:{} 年龄:{}", name, sex, age); log.info("工作经历:{}, {}", work.getTimeArea(), work.getCompany()); } @Override public Object clone() { Resume resume = null; try { resume = (Resume) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return resume; } }
其中:private WorkExperience work是引用工作类型的对象
在简历实例化时,同时实例化工作经历:
public Resume(String name) { this.name = name; work = new WorkExperience(); }
在设置工作经历时,给对象的两属性值赋值:
/** * 设置工作经历 * * @param timeArea * @param company */ public void setWorkExperience(String timeArea, String company) { work.setTimeArea(timeArea); work.setCompany(company); }
这时如果再执行之前的客户端代码,则都只显示a对象的信息
大鸟小结:如果是值对象,直接复制没有问题,如果是引用类型,只是复制了引用,对引用的对象还是指向了原来的对象,这叫做浅复制。浅复制指被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。当我们需要把所有引用的对象都复制一遍时,这就需要深复制,深复制会把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象
深复制:
现在工作经历类中,复制传进来的值:
@Data public class WorkExperience implements Cloneable { private String timeArea; private String company; public Object cloneInfo() { Object object = null; try { object = (Object)this.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return object; } }
然后在简历类中,先将传进来的工作经历复制,将a里工作经历的内容重新赋值后传了一个新对象给b,b也就得到了改变的值:
@Slf4j @Data public class Resume implements Cloneable { private String name; private String sex; private String age; private String timeArea; private String company; private WorkExperience work; public Resume(String name) { this.name = name; work = new WorkExperience(); } /** * 设置个人信息 * * @param sex * @param age */ public void setPersonalInfo(String sex, String age) { this.sex = sex; this.age = age; } /** * 设置工作经历 * * @param timeArea * @param company */ public void setWorkExperience(String timeArea, String company) { work.setTimeArea(timeArea); work.setCompany(company); } /** * 显示 */ public void display() { log.info("名字:{} 性别:{} 年龄:{}", name, sex, age); log.info("工作经历:{}, {}", work.getTimeArea(), work.getCompany()); } @Override public Object clone() { Resume obj = new Resume(this.work); obj.name = this.name; obj.sex = this.sex; obj.age = this.age; return obj; } private Resume(WorkExperience work) { try { this.work = (WorkExperience)work.cloneInfo(); } catch (Exception e) { e.printStackTrace(); } } }
这样得到的结果就不像浅复制一样都是一样的了,而是:
名字:大鸟 性别:男 年龄:29 工作经历:2019-2020, 凡人修仙功法公司 名字:大鸟 性别:男 年龄:29 工作经历:2020-2021, 凡人修仙宝器公司 名字:大鸟 性别:男 年龄:29 工作经历:2020-2021, 凡人修仙炼体公司 名字:大鸟 性别:男 年龄:29