1.定义
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。
2.场景
在游戏中,经常会出现带有复制功能的BOSS,为了实现这个复制功能,一般都是先new出该对象然后依次设置其属性到当前状态。虽然功能确实实现了,但是其消耗比较大,尤其是该对象比较"重"的情况下。还好java提供了一种方式可以快速高效复制一个对象,实现方式:实现Cloneable然后重写clone方法。
2.1 实现Cloneable接口
@Data
public class Boss implements Cloneable {
private int x;
private int y;
private String name;
// 重写方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
2.2 测试
public class TestPrototype {
public static void main(String[] args) throws CloneNotSupportedException {
Boss boss = new Boss();
boss.setX(1);
boss.setY(2);
boss.setName("大怪物");
Boss boss2 = (Boss) boss.clone();
boss2.setName("大怪物-副本1");
boss2.setX(10);
boss2.setY(20);
System.out.println(boss);
System.out.println(boss2);
System.out.println(boss == boss2);
}
}
----------------------------console
Boss(x=1, y=2, name=大怪物)
Boss(x=10, y=20, name=大怪物-副本1)
false
虽然产生对象的地址和原对象的地址不一样,但是这种克隆方式,只能是一种浅克隆
。为了证明这个,将x,y属性封装成一个对象。
3.浅克隆实验
3.1 克隆对象
/*
* 位置
*/
@Data
@AllArgsConstructor
public class Point {
private int x;
private int y;
}
@Data
public class Boss implements Cloneable {
private Point point;
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
3.2 测试
public class TestPrototype {
public static void main(String[] args) throws CloneNotSupportedException {
Boss boss = new Boss();
String bossName = "大怪物";
boss.setName(bossName);
Point bossPoint = new Point(1,2);
boss.setPoint(bossPoint);
Boss boss2 = (Boss) boss.clone();
System.out.println(boss);
System.out.println(boss2);
// 修改克隆对象的坐标和名字
Point boss2Point = boss2.getPoint();
String boss2Name = "大怪物-副本1";
boss2.setName(boss2Name);
boss2Point.setX(10);
boss2Point.setY(20);
System.out.println(boss);
System.out.println(boss2);
}
}
----------------------------console
Boss(point=Point(x=1, y=2), name=大怪物)
Boss(point=Point(x=1, y=2), name=大怪物)
Boss(point=Point(x=10, y=20), name=大怪物)
Boss(point=Point(x=10, y=20), name=大怪物-副本1)
从上面可以看出,当修改boss2的pioint时,boss的point也跟着改变,即boss和boss2的point属性指向了同一个位置(地址)。同时也可以得出:如果某类只有基本数据类型或者String类型或者包装类型,使用浅克隆就可以复制另一个"不一样"的对象(虽然String和包装类型属性复制出来的对象和源对象地址是一样的即指向了同一对象,但是一般没办法修改其内部属性)。
4. 深克隆
4.1 引用属性实现Cloneable接口
@Data
@AllArgsConstructor
public class Point implements Cloneable{
private int x;
private int y;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
4.2 克隆对象修改clone方法
@Data
public class Boss implements Cloneable {
private Point point;
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
Boss boss = (Boss) super.clone();
// 该属性使用clone方法克隆对象
boss.point = (Point)point.clone();
return boss;
}
}
打印结果将变为:
Boss(point=Point(x=1, y=2), name=大怪物)
Boss(point=Point(x=1, y=2), name=大怪物)
Boss(point=Point(x=1, y=2), name=大怪物)
Boss(point=Point(x=10, y=20), name=大怪物-副本1) // 说明已经深克隆了
4.3 使用序列化方式
如果引用类型的属性,包含引用类型的属性,并且层级比较多,使用上面的方法将会变得非常复杂,在这种情况下可以使用序列化的方式。
4.3.1 克隆对象和引用属性实现Serializable接口
@Data
public class Boss implements Serializable {
private Point point;
private String name;
}
@Data
@AllArgsConstructor
public class Point implements Serializable{
private int x;
private int y;
}
4.3.2 测试
public class TestPrototype {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Boss boss = new Boss();
String bossName = "大怪物";
boss.setName(bossName);
Point bossPoint = new Point(1,2);
boss.setPoint(bossPoint);
// 写入对象
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(boss);
oos.flush();
// 读出对象
ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
Boss boss2=(Boss)ois.readObject();
// 修改克隆对象的坐标和名字
Point boss2Point = boss2.getPoint();
String boss2Name = "大怪物-副本1";
boss2.setName(boss2Name);
boss2Point.setX(10);
boss2Point.setY(20);
System.out.println(boss);
System.out.println(boss2);
}
}
----------------------------console
Boss(point=Point(x=1, y=2), name=大怪物)
Boss(point=Point(x=10, y=20), name=大怪物-副本1)