原型设计模式
原型设计模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制已有对象来创建新对象,而无需直接依赖它们的具体类。这种模式通常用于需要频繁创建相似对象的场景,以避免昂贵的创建操作或初始化过程。
概述
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
结构
原型模式包含如下角色:
- 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
实现
原型模式的克隆分为浅克隆和深克隆。
(经典面试题)什么是深克隆和浅克隆?
答:在计算机内存中,每个对象都有一个地址,这个地址指向对象在内存中存储的位置。当我们使用变量引用一个对象时,实际上是将该对象的地址赋值给变量。因此,如果我们将一个对象复制到另一个变量中,实际上是将对象的地址复制到了这个变量中。
<-------------------------------------------------------------------------------------------------------------------->
浅拷贝是指将一个对象复制到另一个变量中,但是只是复制对象的地址,而不是对象本身。也就是说,原始对象的复制对象实际上是共享同一个内存地址的。因此,如果我们修改了复制对象中的属性或元素,原始对象中对应的属性或元素也会被修改。
<-------------------------------------------------------------------------------------------------------------------->
深拷贝是指将一个对象及其所有子对象都复制到另一个变量中,也就是说,它会创建一个全新的对象,并将原始对象中的所有属性或元素都复制到新的对象中。因此,如果我们修改复制对象中的属性或元素,原始对象中对应的属性或元素不会受到影响。
浅克隆:
- Java中的Object类中提供了
clone()
方法来实现浅克隆。 Cloneable 接口是抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下:
/**
* @author OldGj
* @version v1.0
* @apiNote 具体原型类-原型模式
*/
public class Realizetype implements Cloneable {
public Realizetype() {
System.out.println("原型创建");
}
@Override
public Realizetype clone() throws CloneNotSupportedException {
System.out.println("原型克隆");
return (Realizetype) super.clone();
}
}
/**
* @author OldGj
* @version v1.0
* @apiNote 测试类 - 测试原型模式克隆
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Realizetype realizetype = new Realizetype();
Realizetype cloned = realizetype.clone();
System.out.println(cloned==realizetype); // false
}
}
- 使用BeanUtils实现浅拷贝
BeanUtils
是 Apache Commons BeanUtils 库中的一个类,它提供了一组用于操作 Java Bean 对象的工具方法。Java Bean 是一种符合特定约定的 Java 类,通常包含私有字段、公共 getter 和 setter 方法。BeanUtils 库可以用于在不直接访问对象字段的情况下操作 Bean 对象的属性。
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 使用BeanUtils实现浅克隆 - 用户类
*/
public class User {
private String name;
private String password;
private Address address;
--- 省略get/set方法
}
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 使用BeanUtils实现浅拷贝 - 地址类
*/
public class Address {
private String province;
private String city;
--- 省略get/set方法
}
/**
* @author OldGj 2024/02/21
* @version v1.0
* @apiNote 测试类 - 测试原型模式克隆
*/
public class Client {
public static void main(String[] args) throws CloneNotSupportedException, InvocationTargetException, IllegalAccessException {
User user = new User();
user.setAddress(new Address("beijing","shanghai"));
User user1 = new User();
BeanUtils.copyProperties(user,user1);
System.out.println(user == user1); // false
System.out.println(user.getAddress() == user1.getAddress()); // true : 浅拷贝
}
}
深克隆:
- 实现Cloneable接口,重写clone();
在Objecta类中定义了一个clone方法,这个方法其实在不重写的情况下,其实也是浅拷贝的。
如果想要实现深拷贝,就需要重写clone方法,而想要重写clone方法,就必须实现Cloneable,否则会报Clone NotSupportedException异常。
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 重写clone方法实现深克隆- 地址类
*/
public class Address {
private String province;
private String city;
--- 省略get/set方法
}
/**
* @author OldGj
* @version v1.0
* @apiNote 重写clone方法实现深克隆 - 用户类
*/
public class User implements Cloneable{
private String name;
private String password;
private Address address;
@Override
protected Object clone() throws CloneNotSupportedException {
User user = (User) super.clone();
user.setAddress(new Address());
return user;
}
--- 省略get/set方法
}
/**
* @author OldGj
* @version v1.0
* @apiNote 测试类 - 测试原型模式克隆
*/
public class Client {
public static void main(String[] args) throws Exception {
User user = new User();
User clone = (User) user.clone();
System.out.println(user == clone); // false
System.out.println(user.getAddress() == clone.getAddress()); // false 深克隆
}
}
这种方式的逻辑很好理解,就是在重写的clone()
方法中,将对象的所有引用类型的属性全部设置为一个新实例化的引用即可。
但是采用这种方式也有一个缺点就是,如果我们User类中引用类型的属性非常多,那么clone()方法需要写很长,而且后面如果有修改,比如在User类中新增了属性,那么这个地方也要修改,并且通过这种方式实现深克隆后,我们也无法再调用上述浅克隆的方式进行浅克隆了。
- 序列化实现深克隆:我们可以借助序列化来实现深拷贝。先把对象序列化成流,再从流中反序列化成对象,这样就一定是新的对象了。
序列化的方式有很多,比如我们可以使用各种JSON工具,把对象序列化成SON字符串,然后再从字符串中反序列化成对象。
使用fastjson工具包序列化实现深拷贝:
/**
* @author OldGj 2024/02/21
* @version v1.0
* @apiNote 奖状类
*/
public class Citation implements Cloneable, Serializable {
private Student student;
public void show() {
System.out.println(student.getName() + "获得了奖状");
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
@Override
protected Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
}
/**
* @author OldGj 2024/02/21
* @version v1.0
* @apiNote 学生类
*/
public class Student implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* @author OldGj
* @version v1.0
* @apiNote 客户端 - 测试深拷贝 - 使用fastjson工具包实现序列化
*/
public class CitationTest {
public static void main(String[] args) throws Exception {
Citation citation = new Citation();
citation.setStudent(new Student());
String jsonString = JSON.toJSONString(citation);
Citation newCitation = JSON.parseObject(jsonString, Citation.class);
System.out.println(citation == newCitation); // false
System.out.println(citation.getStudent() == newCitation.getStudent()); // false 深拷贝
}
}
使用SerializationUtils工具类实现深拷贝:
SerializationUtils
是 Apache Commons Lang 库中的一个工具类,用于实现 Java 对象的序列化和反序列化。它提供了一组静态方法,可以方便地将对象序列化为字节数组,或者将字节数组反序列化为对象。
引入Apache Commons Lang库依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version> <!-- 或者是当前最新版本 -->
</dependency>
/**
* @author OldGj 2024/02/21
* @version v1.0
* @apiNote 客户端 - 测试类 测试深拷贝
*/
public class CitationTest2 {
public static void main(String[] args) throws CloneNotSupportedException {
Citation citation = new Citation();
Student student = new Student();
student.setName("张三");
citation.setStudent(student);
Citation cloned = SerializationUtils.clone(citation); //基于序列化实现深拷贝
System.out.println(citation==cloned); // false
System.out.println(citation.getStudent()==cloned.getStudent()); // false 深克隆
Student student1 = new Student();
student1.setName("李四");
cloned.setStudent(student1); // 修改克隆对象的引用类型属性,原对象的该属性不变
System.out.println(citation.getStudent().getName());
System.out.println(cloned.getStudent().getName());
}
}
注意事项
序列化版本一致性:确保序列化和反序列化的类具有相同的 serialVersionUID,以避免反序列化时出现 InvalidClassException。
Serializable 接口:要序列化一个对象,该对象的类必须实现 Serializable 接口。
SerializationUtils 提供了一种简单而强大的方式来实现对象的序列化和反序列化,以及对象的深度复制,从而简化了 Java 应用程序中的对象操作。Citation类和Student类必须实现Serializable接口,否则会抛NotSerializableException异常。