原型模式(Prototype Pattern)
原型模式的核心思想是通过复制(克隆)现有对象来创建新对象。
原型模式通常涉及两个角色:原型对象和具体原型对象。原型对象是需要被复制的对象,而具体原型对象是实现了克隆方法的原型对象。
在Java中,原型模式通常通过实现Cloneable接口和重写clone()方法来实现。当需要创建新对象时,可以直接调用原型对象的clone()方法来得到一个新的对象副本,而无需调用构造函数大概暴露对象创建的具体细节。
原型模式在一些场景中非常有用,例如:
- 简化对象创建:在对象的构造过程比较复杂或时,使用原型模式可以很容易地复制整个对象结构,而不需要关心对象的具体构成和组装方式。
- 隐藏具体实现:客户端可以针对抽象接口进行编程,而不需要知道具体的实现细节。
- 通过为每个线程创建 独立的对象实例 ,来避免差别线程对该对象的操作可能会产生数据冲突大概不一致。
- 在某些情况下,对象的创建过程可能是受限的,而原型模式可以绕过这些限定。
简单案例
案例概述
简单实现原型模式,观察原型创建的对象是不是同一个对象,内容是否雷同- // 原型对象和具体原型对象
- public class ConcretePrototype implements Cloneable {
- private String name;
- public ConcretePrototype(String name) {
- this.name = name;
- }
- public String getName() {
- return name;
- }
- @Override
- public ConcretePrototype clone() {
- try {
- return (ConcretePrototype) super.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- return null;
- }
- }
- }
- // 测试代码
- class PrototypePatternDemo {
- public static void main(String[] args) {
- ConcretePrototype original = new ConcretePrototype("Prototype1");
- // 克隆出新对象
- ConcretePrototype cloned = original.clone();
- // 观察输出的地址和内容
- System.out.println(original+"|"+"Original: " + original.getName());
- System.out.println(cloned+"|"+"Cloned: " + cloned.getName());
- }
- }
复制代码 测试结果:
上面的简单案例是属于原型模式中的浅拷贝,除此之外原型模式另有深拷贝。
浅拷贝
浅拷贝:创建的新对象和原始对象的基本数据类型属性会被直接复制,而对于引用类型的属性,原始对象和克隆对象会共享雷同的引用。所以,如果克隆对象和原始对象共享了某些引用类型的属性,对此中一个对象的修改会影响到另一个对象。
浅拷贝示例
- public class ShallowCopy {
- public static void main(String[] args) throws CloneNotSupportedException {
- Address addr = new Address("New York");
- Person p1 = new Person("John", 25, addr);
- System.out.println("复制修改前--地址:"+ p1 + "|" + p1.name + " - " + p1.address.city); // John - New York
- Person p2 = (Person) p1.clone();
- p2.name = "Denny";
- p2.address.city = "Los Angeles"; // 修改了引用类型的属性
- // 引用对象的值被修改后原始对象的值也发生了改变
- System.out.println("原始对象地址:"+ p1 + "|" + p1.name + " - " + p1.address.city); // John - Los Angeles
- System.out.println("复制对象地址:"+ p2 + "|" + p2.name + " - " + p2.address.city); // Denny - Los Angeles
- }
- }
- class Person implements Cloneable {
- String name;
- int age;
- Address address; // 引用类型
- public Person(String name, int age, Address address) {
- this.name = name;
- this.age = age;
- this.address = address;
- }
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone(); // 浅拷贝
- }
- }
- class Address {
- String city;
- public Address(String city) {
- this.city = city;
- }
- }
复制代码 测试结果:
在这个例子中,p1 和 p2 是两个差别的 Person 对象,但是它们共享同一个 Address 对象的引用。修改 p2 的 address 属性时,也会影响到 p1。
深拷贝
深拷贝:创建的新对象不光会复制原对象的基本数据类型属性,还会递归地复制对象中全部引用类型的属性。深拷贝确保原始对象和克隆对象之间完全独立,修改一个对象不会影响另一个对象。
深拷贝方式1--一个个手动克隆
对应字段为引用对象的进行一个个实例化新对象并拷贝对象数据,大概层层往下clone()。适用于,引用对象少的情况。- public class DeepCopy {
- public static void main(String[] args) throws CloneNotSupportedException {
- Address addr = new Address("New York");
- Person p1 = new Person("John", 25, addr);
- System.out.println("复制修改前--地址:"+ p1 + "|" + p1.name + " - " + p1.address.city); // John - New York
- Person p2 = (Person) p1.clone();
- p2.name = "Denny";
- p2.address.city = "Los Angeles"; // 修改了引用类型的属性
- // 引用对象的值被修改后原始对象的值--不受影响
- System.out.println("原始对象地址:"+ p1 + "|" + p1.name + " - " + p1.address.city); // John - Los Angeles
- System.out.println("复制对象地址:"+ p2 + "|" + p2.name + " - " + p2.address.city); // Denny - Los Angeles
- }
- }
- class Person implements Cloneable {
- String name;
- int age;
- Address address; // 引用类型
- public Person(String name, int age, Address address) {
- this.name = name;
- this.age = age;
- this.address = address;
- }
- @Override
- protected Object clone() throws CloneNotSupportedException {
- Person cloned = (Person) super.clone();
- cloned.address = new Address(this.address.city); // 手动克隆引用类型的属性
- return cloned;
- }
- }
- class Address {
- String city;
- public Address(String city) {
- this.city = city;
- }
- }
复制代码 测试结果:
在这个例子中,p1 和 p2 是两个独立的 Person 对象,且它们的 address 属性是独立的。修改 p2 的 address 不会影响 p1。

深拷贝方式2--序列化
序列化是将对象转换为字节流的过程,而反序列化则是将字节流转换回对象。通过序列化和反序列化,可以实现在不依赖于类构造器的情况下创建一个完全独立的对象副本。这种方式适用于对象较为复杂且具有多个引用的情况。- package org.example.prototype;
- import java.io.*;
- public class DeepCopySerial {
- public static void main(String[] args) {
- Address addr = new Address("New York");
- Person p1 = new Person("Jack", 25, addr);
- System.out.println("原始对象地址:"+ p1 + "|" + p1.name + " - " + p1.address.city); // Jack - New York
- // 使用深拷贝方法复制对象
- Person p2 = p1.deepClone();
- p2.name = "Denny";
- p2.address.city = "Los Angeles"; // 修改引用类型的属性
- // 打印两个对象的状态
- System.out.println("原始对象地址:"+ p1 + "|" + p1.name + " - " + p1.address.city); // Jack - New York
- System.out.println("复制对象地址:"+ p2 + "|" + p2.name + " - " + p2.address.city); // Denny - Los Angeles
- }
- }
- class Address implements Serializable {
- String city;
- public Address(String city) {
- this.city = city;
- }
- }
- class Person implements Serializable {
- String name;
- int age;
- Address address;
- public Person(String name, int age, Address address) {
- this.name = name;
- this.age = age;
- this.address = address;
- }
- // 序列化深拷贝方法
- public Person deepClone() {
- try {
- // 将当前对象序列化到字节数组流
- ByteArrayOutputStream byteOutStream = new ByteArrayOutputStream();
- ObjectOutputStream out = new ObjectOutputStream(byteOutStream);
- out.writeObject(this);
- // 从字节数组流反序列化为新对象
- ByteArrayInputStream byteInStream = new ByteArrayInputStream(byteOutStream.toByteArray());
- ObjectInputStream in = new ObjectInputStream(byteInStream);
- return (Person) in.readObject();
- } catch (IOException | ClassNotFoundException e) {
- e.printStackTrace();
- return null;
- }
- }
- }
复制代码 测试结果:

优点:
- 简便和通用:无论对象的深度如何复杂,序列化和反序列化都可以精确地复制对象。
- 无需手动处理每个字段:不需要像手动实现深拷贝那样去关心对象的字段及其引用关系。
缺点:
- 性能开销:序列化和反序列化过程相对较慢,尤其是对于大型对象。
- 依赖Serializable:对象必须实现 Serializable 接口,如果类没有实现这个接口,无法进行序列化。
深拷贝方式3--第三方库
使用第三方库进行拷贝,比如Apache Commons Lang 提供了 SerializationUtils.clone() 方法,这个方法通过序列化来实现深拷贝,使用起来非常简便。- import org.apache.commons.lang3.SerializationUtils;
- import java.io.Serializable;
- public class DeepCopyWithLibrary {
- public static void main(String[] args) {
- Address addr = new Address("New York");
- Person p1 = new Person("Jack", 25, addr);
- System.out.println("原始对象地址:"+ p1 + "|" + p1.name + " - " + p1.address.city); // Jack - New York
- // 使用 Apache Commons 库进行深拷贝
- Person p2 = SerializationUtils.clone(p1);
- p2.name = "Denny";
- p2.address.city = "Los Angeles"; // 修改引用类型的属性
- // 打印两个对象的状态
- System.out.println("原始对象地址:"+ p1 + "|" + p1.name + " - " + p1.address.city); // Jack - New York
- System.out.println("复制对象地址:"+ p2 + "|" + p2.name + " - " + p2.address.city); // Denny - Los Angeles
- }
- }
- class Address implements Serializable {
- String city;
- public Address(String city) {
- this.city = city;
- }
- }
- class Person implements Serializable {
- String name;
- int age;
- Address address;
- public Person(String name, int age, Address address) {
- this.name = name;
- this.age = age;
- this.address = address;
- }
- }
复制代码 测试结果和序列化雷同。
看看JDK源码怎么写的
java.util.Calendar类的拷贝源码,这里说的就是对应方式1,一个个手动克隆,通过new的方式和层层调用clone()方法other.zone = (TimeZone) zone.clone();来实现。
[code]public Object clone()
{
try {
Calendar other = (Calendar) super.clone();
other.fields = new int[FIELD_COUNT];
other.isSet = new boolean[FIELD_COUNT];
other.stamp = new int[FIELD_COUNT];
for (int i = 0; i |