定义

了解原型模式前,我们先来了解下Java提供两种克隆方式:

  • 浅拷贝:被克隆对象的所有变量都含有与原来的对象相同的值,而它所有的对其他对象的引用都仍然指向原来的对象。换一种说法就是浅克隆仅仅克隆所考虑的对象,而不克隆它所引用的对象。
  • 深拷贝:被克隆对象的所有变量都含有与原来的对象相同的值,但它所有的对其他对象的引用不再是原有的,而这是指向被复制过的新对象。换言之,深复制把要复制的对象的所有引用的对象都复制了一遍,这种叫做间接复制。

面试题:浅拷贝和深拷贝的区别是什么?


  • 浅拷贝是对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝;(8种基本数据类型byte,char,short,int,long,float,double,boolean)
  • 而深拷贝是对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容。(除了浅拷贝的8中基本数据类型,其他都属于深拷贝,例如数组、对象…)

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

代码示例

1.浅拷贝


public class IdCard {
private String id;

public IdCard(String id) {
this.id = id;
}

@Override
public String toString() {
return "IdCard{" +
"id=" + id +
'}';
}
}



public class Person implements Cloneable {

private String name;
private int age;
private IdCard idCard;

public Person() {
}

public Person(String name, int age, IdCard idCard) {
this.name = name;
this.age = age;
this.idCard = idCard;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public IdCard getIdCard() {
return idCard;
}

public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}

@Override
public String toString() {
return "Person{" +
"name='" + name +
", age=" + age +
", idCard=" + idCard +
", idCard.hashCode=" + idCard.hashCode() +
'}';
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}


执行结果


public class PersonTest {
public static void main(String[] args) throws Exception {
Person person = new Person("张三", 20, new IdCard("10086"));

Person person1 = (Person) person.clone();
Person person2 = (Person) person.clone();

System.out.println(person);
System.out.println(person1);
System.out.println(person2);
}
}



Person{name='张三, age=20, idCard=IdCard{id=10086}, idCard.hashCode=1510467688}
Person{name='张三, age=20, idCard=IdCard{id=10086}, idCard.hashCode=1510467688}
Person{name='张三, age=20, idCard=IdCard{id=10086}, idCard.hashCode=1510467688}


我们发现可以通过实现implements Cloneable来完成浅拷贝,基本变量是值传递克隆,而引用对象IdCard则是引用传递,这不符合我们面向对象思想,每一个Person应该都有一个独立的IdCard,而不是共用一个,而要解决这种问题,我们需要使用深克隆

2.深拷贝(第一种)


//实现Cloneable接口
public class IdCard implements Cloneable {
private String id;

public IdCard(String id) {
this.id = id;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

@Override
public String toString() {
return "IdCard{" +
"id='" + id +
'}';
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}



public class Person implements Cloneable {
private String name;
private int age;
private IdCard idCard;

public Person(String name, int age, IdCard idCard) {
this.name = name;
this.age = age;
this.idCard = idCard;
}

public IdCard getIdCard() {
return idCard;
}

public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}

@Override
public String toString() {
return "Person{" +
"personHashCode=" + this.hashCode() +
", name='" + name +
", age=" + age +
", idCard=" + idCard +
", idCardHashCode=" + idCard.hashCode() +
'}';
}

//深克隆需要自己手动实现,在对象引用中也要实现clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
//完成基本数据类型的拷贝
//通过new关键字创建的对象是引用类型

Object person = super.clone();

//对引用类型单独处理
Person p = (Person) person;
IdCard idCard = (IdCard) p.getIdCard().clone(); //实现自己的克隆
p.setIdCard(idCard);
return p;
}
}


执行结果


public class PersonTest implements Serializable {
public static void main(String[] args) throws Exception {

Person person = new Person("张三", 20, new IdCard("10086"));

Person person1 = (Person) person.clone();
Person person2 = (Person) person.clone();

System.out.println(person);
System.out.println(person1);
System.out.println(person2);

}
}

Person{personHashCode=1510467688, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=1995265320}
Person{personHashCode=746292446, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=1072591677}
Person{personHashCode=1523554304, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=1175962212}


使用这种深克隆的方式,完美的解决了当数据类型为引用类型时,只是拷贝原引用对象地址而不是一个全新的引用对象的引用,但是这种实现有一个很大的弊端,需要在每一个对象中都实现clone方法,如果类全是你自己写的,那自然没问题,实现一下就行了,不过有点麻烦。但是,如果你引用的是第三方的一个类,无法修改源代码,这种方式,显然就无法实现深克隆了

3.深拷贝(第二种)


//实现Serializable接口
public class IdCard implements Serializable {
private String id;

public IdCard(String id) {
this.id = id;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

@Override
public String toString() {
return "IdCard{" +
"id='" + id +
'}';
}

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}



public class Person implements Serializable {
private String name;
private int age;
private IdCard idCard;

public Person(String name, int age, IdCard idCard) {
this.name = name;
this.age = age;
this.idCard = idCard;
}

public IdCard getIdCard() {
return idCard;
}

public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}

@Override
public String toString() {
return "Person{" +
"personHashCode=" + this.hashCode() +
", name='" + name +
", age=" + age +
", idCard=" + idCard +
", idCardHashCode=" + idCard.hashCode() +
'}';
}

//序列化的方式
public Person deelClone() {
//创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;

try {
//序列化
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);

//反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return (Person) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
ois.close();
bis.close();
oos.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}


执行结果


public class PersonTest {
public static void main(String[] args) throws Exception {
//创建一个对象
Person person = new Person("张三", 20, new IdCard("10086"));

//克隆两个对象
Person person1 = (Person) person.deelClone();
Person person2 = (Person) person.deelClone();
System.out.println("深拷贝(第二种 实现序列化接口)");
//打印三人信息
System.out.println(person);
System.out.println(person1);
System.out.println(person2);
}
}


深拷贝(第二种 实现序列化接口)


Person{personHashCode=2055281021, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=1554547125}
Person{personHashCode=804564176, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=1421795058}
Person{personHashCode=1555009629, name='张三, age=20, idCard=IdCard{id='10086}, idCardHashCode=41359092}


这种方式我们需要手动编写deepClone方法,使用Java流中的序列化与反序列化来实现深克隆,但是这种实现,需要在每一个类中都继承序列化Serializable接口,这种方式,如果你调用的是第三方类,也有可能第三方类上没有实现Serializable序列化接口,但是一般来说,大多都会实现,总的来说,这种比较推荐使用,而且效率也高

4 原型模式总结

原型模式本质上就是对象拷贝。使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率,还有一个特点就是保护性拷贝,如果我们操作时,不会对原有的对象造成影响。

优点: 原型模式是在内存中二进制流的拷贝,要比new一个对象的性能要好,特别是需要生产大量对象时。

缺点: 直接在内存中拷贝,构造函数是不会执行的,这样就减少了约束,既是优点也是缺点,在实际开发当中应注意这个问题。

最后分享一下我整理的一些Android 相关学习知识点,大家可以点击下方小卡片进行查看。

设计模式-原型模式(Prototype Pattern)_java