博主介绍: ✌博主从事应用安全和大数据领域,有8年研发经验,5年面试官经验,Java技术专家✌
Java知识图谱点击链接:体系化学习Java(Java面试专题)
💕💕 感兴趣的同学可以收藏关注下 ,不然下次找不到哟💕💕
文章目录
- 1、什么是原型模式
- 2、原型模式有哪些应用场景
- 3、浅克隆
- 4、深克隆
- 5、有几种实现深克隆的方式
- 5.1、通过序列化的方式实现深克隆
- 5.3、使用 Apache Commons进行深克隆
- 5.3、递归克隆引用
1、什么是原型模式
原型模式是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是通过实例化类来创建。在原型模式中,一个原型对象被克隆以生成新的对象,新对象的属性可以和原型对象相同,也可以有所改变。这种方式可以避免重复创建对象,提高创建对象的效率。
在实现原型模式时,需要让原型对象实现一个克隆方法,该方法返回一个新的对象,新对象的属性与原型对象相同。在使用原型对象创建新对象时,只需要调用原型对象的克隆方法即可。
原型模式可以分为浅克隆和深克隆两种类型。浅克隆只复制对象的基本属性,而不复制对象中的引用类型属性;深克隆则会复制对象中的所有属性,包括引用类型属性。需要根据具体的业务需求选择使用哪种克隆方式。
原型模式的优点是可以避免重复创建对象,提高创建对象的效率,同时也可以减少内存占用。缺点是需要让原型对象实现克隆方法,这可能会增加代码量和复杂度。
2、原型模式有哪些应用场景
原型模式适用于以下场景:
- 对象的创建成本较大,例如创建对象需要进行复杂的计算、连接数据库或者网络等操作,使用原型模式可以避免这些操作,提高对象的创建效率。
- 需要避免重复创建对象,例如在多次创建相似对象时,使用原型模式可以避免重复创建,提高性能。
- 需要动态地创建对象,根据不同的条件创建不同的对象,使用原型模式可以通过复制已有的对象来创建新的对象,而不需要知道创建新对象的具体细节。
- 需要保护对象的复杂状态,例如创建一个对象时需要进行一系列的操作才能得到一个完整的对象,使用原型模式可以复制一个已经完成了这些操作的对象,而不需要重新进行这些操作。
- 需要减少子类的构造,例如在一个类的子类中需要创建相同的对象,使用原型模式可以避免在每个子类中都创建相同的对象,减少代码的重复。
总之,原型模式适用于需要创建相似对象并且创建对象的成本较高的场景,可以提高性能和代码复用性。
3、浅克隆
原型模式中的浅克隆是指创建一个新对象时,只复制对象的基本数据类型和引用类型的地址,而不复制引用类型所指向的对象。也就是说,新对象和原对象共享同一个引用类型对象。这种克隆方式相对较快,但可能会导致新对象和原对象之间的状态相互影响。
浅克隆的代码如下:
package com.pany.camp.design.principle;
import com.google.common.collect.Lists;
import lombok.Data;
import java.util.List;
/**
*
* @description: 浅克隆
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-06-27 8:19
*/
@Data
public class Prototype implements Cloneable {
private String name;
private int age;
private List<String> hobbies;
public Prototype(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
@Override
public Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Prototype prototype = new Prototype("张三", 18, Lists.newArrayList("篮球"));
Prototype clonePrototype = prototype.clone();
clonePrototype.setName("李四");
clonePrototype.setAge(19);
System.out.println(prototype == clonePrototype);
System.out.println(prototype.getName() + ":" + prototype.getAge());
System.out.println(clonePrototype.getName() + ":" + clonePrototype.getAge());
}
}
Prototype 类实现了 Cloneable 接口,并重写了 clone() 方法。 clone() 方法使用了默认的浅克隆方式,即调用 super.clone() 方法进行复制。需要注意的是, hobbies 属性是一个引用类型,它所指向的对象并没有被复制,因此新对象和原对象共享同一个 hobbies 对象。如果需要避免这种情况,可以使用深克隆方式,即复制引用类型所指向的对象。
输出结果如下:
false
张三:18
李四:19
Process finished with exit code 0
浅克隆得到的对象和原对象不是同一个对象,它们是两个独立的对象。浅克隆只会复制原对象中基本数据类型和引用类型的地址,而不会复制引用类型指向的对象本身。因此,浅克隆得到的对象和原对象共享引用类型指向的对象,但是它们本身是不同的对象。
4、深克隆
原型模式中的深克隆是指创建一个新对象时,不仅复制对象的基本数据类型和引用类型的地址,还复制引用类型所指向的对象。也就是说,新对象和原对象拥有完全独立的引用类型对象,互相之间不会产生影响。这种克隆方式相对较慢,但可以确保新对象和原对象之间的状态相互独立。
代码如下:
package com.pany.camp.design.principle;
import com.google.common.collect.Lists;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
*
* @description: 深克隆
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-06-27 8:24
*/
@Data
public class Prototype1 implements Cloneable {
private String name;
private int age;
private List<String> hobbies;
public Prototype1(String name, int age, List<String> hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
@Override
public Prototype1 clone() throws CloneNotSupportedException {
Prototype1 prototype = (Prototype1) super.clone();
prototype.hobbies = new ArrayList<>(hobbies);
return prototype;
}
public static void main(String[] args) throws CloneNotSupportedException {
Prototype1 prototype = new Prototype1("张三", 18, Lists.newArrayList("篮球"));
Prototype1 clonePrototype = prototype.clone();
clonePrototype.setName("李四");
clonePrototype.setAge(19);
System.out.println(prototype == clonePrototype);
System.out.println(prototype.getName() + ":" + prototype.getAge());
System.out.println(clonePrototype.getName() + ":" + clonePrototype.getAge());
}
}
输出结果如下:
false
张三:18
李四:19
Process finished with exit code 0
5、有几种实现深克隆的方式
实现深克隆的方式有以下几种:
- 重写
clone()
方法。如果要实现深克隆,需要在clone()
方法中对所有引用类型的成员变量进行递归复制。 - 实现
Serializable
接口,通过序列化和反序列化实现深克隆。将对象序列化为字节数组,再将字节数组反序列化为新的对象。这种方式需要注意对象及其成员变量必须都是可序列化的。 - 使用第三方库,如 Apache Commons 中的
SerializationUtils
类、Spring Framework 中的SerializationUtils
类或 Google 的Protobuf
库等,这些库提供了方便的深克隆方法,可以直接使用。 - 递归克隆引用类型,引用类型也实现Cloneable接口,在源对象的克隆方法里,循环嵌套(或者可以说递归)克隆引用对象。
需要注意的是,以上方法都需要确保对象及其成员变量都是可序列化的,否则会抛出 NotSerializableException
异常。同时,深克隆的效率可能会比浅克隆低,因为需要递归复制所有引用类型的成员变量。
5.1、通过序列化的方式实现深克隆
代码如下:
package com.pany.camp.design.principle;
import lombok.Data;
import java.io.*;
/**
*
* @description: 通过序列化的方式实现深克隆
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-06-27 8:35
*/
@Data
public class DeepCopy implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private Address address;
public DeepCopy(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public DeepCopy deepClone() {
DeepCopy clone = null;
try {
// 将对象序列化为字节数组
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
oos.close();
// 将字节数组反序列化为新的对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
clone = (DeepCopy) ois.readObject();
ois.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return clone;
}
public static void main(String[] args) {
Address address = new Address("China", "Beijing");
DeepCopy original = new DeepCopy("Tom", 20, address);
DeepCopy copy = original.deepClone();
System.out.println(copy.getName()); // Tom
System.out.println(copy.getAge()); // 20
System.out.println(copy.getAddress().getCountry()); // China
System.out.println(copy.getAddress().getCity()); // Beijing
}
}
@Data
class Address implements Serializable {
private static final long serialVersionUID = 1L;
private String country;
private String city;
public Address(String country, String city) {
this.country = country;
this.city = city;
}
}
注意这种方式需要用到流,DeepCopy 类和 Address 类都实现了 Serializable 接口,这样才能被序列化和反序列化。在 deepClone() 方法中,将对象序列化为字节数组,再将字节数反序列化为新的对象。
输出结果如下:
Tom
20
China
Beijing
Process finished with exit code 0
5.3、使用 Apache Commons进行深克隆
代码如下:
package com.pany.camp.design.principle;
import org.apache.commons.lang.SerializationUtils;
import java.io.Serializable;
/**
*
* @description: 使用 Apache Commons进行深克隆
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-06-27 8:45
*/
public class DeepCopyExample {
public static void main(String[] args) {
Address2 address = new Address2("中国", "北京");
Person original = new Person("Tom", 20, address);
Person copy = (Person) SerializationUtils.clone(original);
System.out.println(copy.getName()); // Tom
System.out.println(copy.getAge()); // 20
System.out.println(copy.getAddress().getCountry()); // 中国
System.out.println(copy.getAddress().getCity()); // 北京
}
}
class Person implements Serializable {
private String name;
private int age;
private Address2 address;
public Person(String name, int age, Address2 address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public Address2 getAddress() {
return address;
}
}
class Address2 implements Serializable {
private String country;
private String city;
public Address2(String country, String city) {
this.country = country;
this.city = city;
}
public String getCountry() {
return country;
}
public String getCity() {
return city;
}
}
输出如下:
Tom
20
中国
北京
Process finished with exit code 0
我们使用了 Apache Commons 中的 SerializationUtils.clone() 方法进行深克隆。需要注意的是,被克隆的对象必须实现 Serializable 接口,否则会抛出 SerializationException 异常。
Apache Commons 中的深克隆方法还有其他的实现方式,例如使用 BeanUtils.cloneBean() 方法进行克隆等等。具体使用哪种方法,可以根据实际情况进行选择。
5.3、递归克隆引用
代码如下:
package com.pany.camp.design.principle;
import lombok.Data;
/**
*
* @description: 递归克隆引用
* @copyright: @Copyright (c) 2022
* @company: Aiocloud
* @author: pany
* @version: 1.0.0
* @createTime: 2023-06-27 8:41
*/
@Data
public class DeepCopy1 implements Cloneable {
private String name;
private int age;
private Address1 address;
public DeepCopy1(String name, int age, Address1 address) {
this.name = name;
this.age = age;
this.address = address;
}
public DeepCopy1 deepClone() {
DeepCopy1 clone = null;
try {
clone = (DeepCopy1) super.clone();
clone.address = address.deepClone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
public static void main(String[] args) {
Address1 address = new Address1("China", "Beijing");
DeepCopy1 original = new DeepCopy1("Tom", 20, address);
DeepCopy1 copy = original.deepClone();
System.out.println(copy.getName()); // Tom
System.out.println(copy.getAge()); // 20
System.out.println(copy.getAddress().getCountry()); // China
System.out.println(copy.getAddress().getCity()); // Beijing
}
}
@Data
class Address1 implements Cloneable {
private String country;
private String city;
public Address1(String country, String city) {
this.country = country;
this.city = city;
}
public Address1 deepClone() {
Address1 clone = null;
try {
clone = (Address1) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
输出结果如下:
Tom
20
China
Beijing
Process finished with exit code 0
DeepCopy 类和 Address 类都实现了 Cloneable 接口,这样才能被克隆。在 deepClone() 方法中,首先调用 super.clone() 方法创建一个浅克隆的对象,然后对引用类型的成员变量 address 进行递归克隆。在 Address 类中,也要实现 Cloneable 接口,并在 deepClone() 方法中调用 super.clone() 方法创建一个浅克隆的对象。最后,在 main() 方法中测试深克隆的效果。