预备知识:
浅拷贝和深拷贝:
浅拷贝:被复制对象的所有属性的值都与原来对象的相同,而所有的对象引用属性仍然指向原来的对象得属性引用,这种拷贝是危险的。String类比较特殊,它不存在clone函数,管理是通过常量池管理,因此浅拷贝对其无影响。浅拷贝对可变的(非final)基本数据类型无影响;
深拷贝:
① 如果有一个非原生成员(基本数据类型和String),如自定义对象的成员,那么就需要:
该成员实现Cloneable接口并覆盖clone()方法,不要忘记提升为public可见。
同时,修改被复制类的clone()方法,增加成员的克隆逻辑。
② 如果被复制对象不是直接继承Object,中间还有其它继承层次,每一层super类都需要实现Cloneable接口并覆盖clone()方法。
一句话来说,如果实现完整的深拷贝,需要被复制对象的继承链、引用链上的每一个对象都实现克隆机制。
在多种深拷贝方法中,序列化方式是性能最好速度最快的方式。
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
关键字:拷贝
优点:1.原型模式是在内存二进制流拷贝,要比直接new出对象的性能好很多,特别是在循环体内创建大量对象;2.逃避构造函数的约束:直接在内存中拷贝,构造函数是不会执行的。
使用场景:
1、资源优化场景。
2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
3、性能和安全要求的场景。
4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
5、一个对象多个修改者的场景。
6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
结构:
1.抽象类 Shape:定义行为,并且实现克隆方法;
2.抽象类的实现类:实现其具体行为;
3.多个实现类对象的管理类:存储一个各种待使用的实现类对象,在使用的时候,需要哪一种类型,就从这个管理类中取该类型的对象,并且进行克隆。
使用:
1.抽象类:
public abstract class Shape implements Cloneable {
private String id;
protected Type type;
abstract void draw();
public Type getType(){
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Object clone() {
Object clone = null;
try {
clone = super.clone();
this.type = (Type)this.type.clone();//Type的clone方法也是重写方法,将其中的数组和引用对象进行拷贝
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
2.抽象的多个实现类:
public class Square extends Shape {
public Square(){
type = new Type("Square");
}
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public class Circle extends Shape {
public Circle(){
type = new Type("Circle");
}
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
3.多个实现类对象的管理类:
public class ShapeCache {
private static Hashtable<String, Shape> shapeMap = new Hashtable<>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
return (Shape) cachedShape.clone();
}
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);
Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(),square);
}
}
4.使用例子:
ShapeCache.loadCache();
Shape clonedShape = ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.getType().toString());
Shape clonedShape2 = ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType().toString());
输出结果:
Shape : Type --> Circle
Shape : Type --> Square
下面是序列化深拷贝方法:
注意:
1.这样做的前提是对象以及对象内部所有引用到的对象都是可串行化的,否则,就需要仔细考察那些不可串行化的对象可否设成transient,从而将其排除在复制过程之外;
2.serialVersionUID:
如果你没有考虑到兼容性的问题,就把它关掉,不过有这个功能是好的,只要任何类别实现了Serializable接口,如果没有加入serialVersionUID,Eclipse都会给你提示,这个serialVersionUID为了让该类别Serializable向后兼容。
serialVersionUID赋值方法:
(1)默认的1L:private static final long serialVersionUID = 1L;
(2)根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
private static final long serialVersionUID = -932183802511122207L;
public class Type implements Serializable {
private String type;
public Type(String type) {
this.type = type;
}
@Override
public String toString() {
return "Type --> " + type;
}
public Object clone() {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = null;
try {
obs = new ObjectOutputStream(out);
obs.writeObject(this);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (obs != null) {
try {
obs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = null;
Object cloneObj = null;
try {
ois = new ObjectInputStream(ios);
//返回生成的新对象
cloneObj = ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return cloneObj;
}
}