小叮当买了一台新手机,小叮咚看了表示很喜欢,要买一台一模一样的。要怎么实现呢?需要注意的是,这里和单例模式不一样,单例模式只有一台汽车,但这里需要两台手机,但是属性要一样。一个简单粗暴的实现方法:
public class Demo {
public static void main(String[] args) {
Phone phone = new Phone("苹果", "星光色", "iPhone 14");
System.out.println("小叮当买了一台"+phone+phone.hashCode());
Phone phone1 = new Phone("苹果", "星光色", "iPhone 14");
System.out.println("小叮咚买了一台"+phone1+phone.hashCode());
}
}
复制一份代码,也就是把构造函数的参数设置一份一模一样的创建一个新的实例。这个是最简单实现。但是有一个缺点,实际开发中,对象的参数很多,然后买同样一款手机的人也很多,这样我们就要手动复制很多份代码设置超级多的参数,会让代码看着很臃肿。做为一个优雅的程序员,这种重复性的脏话,我们是要想办法避免的。下面我们使用浅克隆来实现自动复制属性。
1-浅克隆:实现Cloneable接口然后继承clone()方法。
public class Phone implements Cloneable {
//品牌
private String band;
//颜色
private String color;
//型号
private String type;
public Phone(String band, String color, String type) {
this.band = band;
this.color = color;
this.type = type;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Phone{" +
"band='" + band + '\'' +
", color='" + color + '\'' +
", type='" + type + '\'' +
'}';
}
}
如何使用呢?
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
Phone phone = new Phone("苹果", "星光色", "iPhone 14");
System.out.println("小叮当买了一台"+phone+phone.hashCode());
Phone clone = (Phone) phone.clone();
System.out.println("小叮咚买了一台"+clone+clone.hashCode());
}
}
可以看到使用克隆方法简洁了很多,不用再填构造函数里的参数了。更优雅的实现了我们的需求。
2-浅克隆存在的问题
如果我们在Phone类新增一个属性Owner
private Owner owner;
Owner实现如下
public class Owner {
private String name;
public Owner(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
再测试一下clone方法
可以看到Phone的子属性Owner没有被复制,指向的是同一个对象实例,实际上,不止自定义的类型会有这个问题,使用Intger等包装类型也会出现则会个问题。如何解决这个问题呢,下面我们用深拷贝来解决这个问题。
3-深拷贝
3-1第一个办法是把Owner也实现Clone接口;并重写Phone的clone方法
public class Owner implements Cloneable {
private String name;
public Owner(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//重写Phone的clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
Phone phone = (Phone) super.clone();
phone.owner = (Owner) phone.getOwner().clone();
return phone;
}
public Owner getOwner() {
return owner;
}
测试一下
可以看到Owner指向的实例不一样了,说明克隆成功了,但是这样写比较麻烦,每加一个属性就要重写一下clone方法,如果嵌套多层的话,很容易漏写或者其他原因导致出错。下面我们使用第二种深拷贝来实现。
3-2使用序列化实现深拷贝
首先Phone和Owner都要实现Serializable接口,不用实现Cloneable接口了,然后添加serialVersionUID,保证序列号过程中不出错,然后重写Phone中的clone()方法,完整代码如下:
public class Phone implements Serializable {
//序列化版本号
private static final long serialVersionUID = 123456789L;
//品牌
private String band;
//颜色
private String color;
//型号
private String type;
private Owner owner;
public Phone(String band, String color, String type, Owner owner) {
this.band = band;
this.color = color;
this.type = type;
this.owner = owner;
}
@Override
protected Object clone(){
Phone phone = null;
try(ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
phone = (Phone) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return phone;
}
@Override
public String toString() {
return "Phone{" +
"band='" + band + '\'' +
", color='" + color + '\'' +
", type='" + type + '\'' +
", owner=" + owner +
'}';
}
}
public class Owner implements Serializable {
//序列化版本号
private static final long serialVersionUID = 123456789L;
private String name;
public Owner(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Owner{" +
"name='" + name + '\'' +
'}' + hashCode();
}
}
测试一下
hashcode不一致,但是属性一致,说明克隆成功了。
总结一下:克隆模式在实际生产中用到的比较少,所以我暂时总结不出啥有价值的东西。