1. 拷贝的概念

对象的拷贝(复制,克隆):根据原来的对象”复制”一份 属性、状态一致的新对象。
浅拷贝:只”复制”对象的第一层属性,即:浅拷贝将返回该类的新的实例,该实例的引用类型对象共享。
深拷贝:对象的属性进行递归”复制”,即:深拷贝也会返回该类的新的实例,但是该实例的引用类型属性也是拷贝的新对象。

1.1 对象拷贝的应用场景

  1. 构造数据时,需要构造多个类似的对象且只有其中少数值不同,可以利用对象拷贝的方式简化代码。
  2. 多线程环境中,可以利用对象拷贝的方式让各个线程对对象的修改互不影响。

1.2 浅拷贝和深拷贝的区别

浅拷贝和深拷贝的主要区别在于对于引用类型是否共享

2. 浅拷贝

Objectclone函数默认是浅拷贝,可以从源码中找到答案:

1
2
3
4
5
6
7
8
9
10
package java.lang;
public class Object {
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that ...
* 具体注释请查看源码
*/
protected native Object clone() throws CloneNotSupportedException;
}

我们从源码的注释中了解到:

  • 如果调用clone函数的类没有实现Cloneable接口将会抛出CloneNotSupportedException
  • 所有的数组对象都默认实现了Cloneable接口。
  • clone函数实现的是浅拷贝而不是深拷贝。

3.1 通过编码验证

引入的依赖包有:lombokspring-boot-starter-test,文末会附pom.xml

  1. 准备两个实体类:产品Product和订单Order,并且订单实现Cloneable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
public class Product{
private Long id;
private String name;
private String desc;
}
@Data
public class Order implements Cloneable{
private Long id;
private String orderNo;
private List<Product> products;
/** Object的clone函数用protected修饰,需要重写 */
@Override
public Order clone() throws CloneNotSupportedException {
return (Order) super.clone();
}
}
  1. 编写测试方法验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CloneTest {
public static Order mock() {
Order order = new Order();
order.setId(1L);
order.setOrderNo("N001");
List<Product> products = new ArrayList<>();
Product item = new Product();
item.setId(0L);
item.setName("第一个商品名称");
products.add(item);
order.setProducts(products);
return order;
}
@Test
public void testShallowClone() throws CloneNotSupportedException {
Order order = mock();
Order cloneOrder = order.clone();
assertFalse(order == cloneOrder);
assertTrue(order.getProducts() == cloneOrder.getProducts());
}
}

该单元测试可以通过,从而证实了clone函数的注释,证实了浅拷贝的表现。
因此如果使用浅拷贝,修改拷贝订单的商品列表,那么原始订单对象的商品列表也会受到影响。

3. 深拷贝

虽然浅拷贝能够实现拷贝的功能,但是浅拷贝的引用类型成员变量是共享的,修改极可能导致相互影响。
深拷贝有两种实现方式,一种是Cloneable接口并手动实现深拷贝,另一种是用序列化的方式来实现深拷贝

3.1 实现Cloneable接口方式 实现深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Data
public class Product implements Cloneable{
private Long id;
private String name;
private String desc;
/** Object的clone函数用protected修饰,需要重写 */
@Override
public Product clone() throws CloneNotSupportedException {
return (Product) super.clone();
}
}
@Data
public class Order implements Cloneable{
private Long id;
private String orderNo;
private List<Product> products;
/** 手动实现深拷贝 */
@Override
public Order clone() throws CloneNotSupportedException {
Order order = (Order)super.clone();
// 引用对象也需要克隆
List<Product> items = new ArrayList<>();
for (Product product : products) {
items.add(product.clone());
}
order.setProducts(items);
return order;
}
}

编写测试方法验证:

1
2
3
4
5
6
7
@Test
public void testDeepClone() throws CloneNotSupportedException {
Order order = mock();
Order cloneOrder = order.clone();
assertFalse(order == cloneOrder);
assertFalse(order.getProducts() == cloneOrder.getProducts());
}

该单测可顺利通过。由于克隆的对象和内部的引用类型的属性全部都是依据原始对象新建的对象,因此如果修改拷贝对象的商品列表,原始订单对象的商品列表并不会受到影响。

3.2 实现Serializable接口方式 实现深拷贝(序列化)

实体类需要实现Serializable接口,借助对象输入和输出流编写拷贝工具函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Data
public class Product implements Serializable{
private Long id;
private String name;
private String desc;
}
@Data
public class Order implements Serializable{
private Long id;
private String orderNo;
private List<Product> products;
}
public class MyUtils {
/**
* JDK序列化方式深拷贝
*/
public static <T> T deepClone(T origin) throws IOException, ClassNotFoundException{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);){
objectOutputStream.writeObject(origin);
objectOutputStream.flush();
}
byte[] bytes = outputStream.toByteArray();
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);){
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
return (T) objectInputStream.readObject();
}
}
}

编写测试方法验证:

1
2
3
4
5
6
7
8
@Test
public void testDeepClone2() {
Order order = mock();
// 调用自定义工具类深拷贝
Order cloneOrder = MyUtils.deepClone(order);
assertFalse(order == cloneOrder);
assertFalse(order.getProducts() == cloneOrder.getProducts());
}

Debug可以看到,序列化方式得到跟实现Cloneable方式一样的结果,序列化的方式也实现了深拷贝。
需要注意的是:序列化需要实现Serializable接口,而且效率不是特别高。

3.3 常见工具类实现深拷贝(序列化)

我们可以利用项目中引用的常见工具包的工具类实现深拷贝,避免重复造轮子。

  1. 使用commons-lang3的序列化工具类:org.apache.commons.lang3.SerializationUtils#clone
    • 使用方式:SerializationUtils.clone(order)
1
2
3
4
5
6
7
8
/** 使用方式:SerializationUtils.clone(order) */
@Test
public void serialUtil() {
Order order = mock();
Order cloneOrder = SerializationUtils.clone(order);
assertFalse(order == cloneOrder);
assertFalse(order.getItemList() == cloneOrder.getItemList());
}
  1. 利用GoogleGson库,自定义实现基于JSON序列化方式的深拷贝
    • 使用方式:MyUtils.deepCloneByGson(order, Order.class)
1
2
3
4
5
6
7
8
9
public class MyUtils {
/**
* Gson方式实现深拷贝
*/
public static <T> T deepCloneByGson(T origin, Class<T> clazz) {
Gson gson = new Gson();
return gson.fromJson(gson.toJson(origin), clazz);
}
}
1
2
3
4
5
6
7
@Test
public void withGson() {
Order order = mock();
Order cloneOrder = MyUtils.deepCloneByGson(order, Order.class);
assertFalse(order == cloneOrder);
assertFalse(order.getItemList() == cloneOrder.getItemList());
}

4.原型模式

  • 定义:原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
  • 特点:不需要知道任何创建的细节,不调用构造函数
  • 类型:创建型

4.1 原型模式的结构与实现

由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单,如下UML图:

  1. 模式的结构
    1. 抽象原型类:规定了具体原型对象必须实现的接口。
    2. 具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象。
    3. 访问类:使用具体原型类中的clone()方法来复制新的对象。
  2. 模式的实现
    • 原型模式的克隆分为浅克隆和深克隆,Java中的Object类提供了浅克隆的clone()方法,具体原型类只要实现Cloneable接口就可实现对象的浅克隆,下面以Product产品类为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 抽象原型类
*/
public abstract class BaseProduct implements Cloneable {
@Override
public BaseProduct clone() throws CloneNotSupportedException {
return (BaseProduct) super.clone();
}
}
/**
* 具体原型类 ProductA
*/
public class ProductA extends BaseProduct{}
/**
* 具体原型类 ProductB
*/
public class ProductB extends BaseProduct{}
/**
* 访问类(测试类)
*/
public class PrototypeTest {
@Test
public void testProduct() throws CloneNotSupportedException {
BaseProduct productA = new ProductA();
ProductA cloneA1 = (ProductA) productA.clone();
ProductA cloneA2 = (ProductA) productA.clone();
assertNotSame(cloneA1, cloneA2); // 通过测试
}
}

4.2 原型模式的扩展(原型管理器)

原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,访问类可以通过管理器的 get(String id) 方法从中获取复制的原型。其UML结构图如图:

编码实现:

1
2
3
4
5
6
7
8
9
10
/**
* 抽象原型<Ⅰ> Prototype
*/
public interface BaseProduct extends Cloneable {
/** 拷贝 */
public Object clone();
/** 计算售价
* @return*/
public double countPrice();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 具体原型 TypeA
*/
@Data
public class ProductA implements BaseProduct {
private double price;
/** 拷贝 */
@Override
public ProductA clone() {
try {
return (ProductA) super.clone();
} catch (CloneNotSupportedException e) {
// ...
}
return null;
}
/** 计算售价
* @return*/
@Override
public double countPrice() {
return price;
}
}
/**
* 具体原型 TypeB
*/
@Data
public class ProductB implements BaseProduct {
private double price;
/** 拷贝 */
@Override
public ProductB clone() {
try {
return (ProductB) super.clone();
} catch (CloneNotSupportedException e) {
// ...
}
return null;
}
/** 计算售价
* @return*/
@Override
public double countPrice() {
// 折扣商品
return (.8 * price);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 原型管理器 PrototypeManager
*/
public class ProtoTypeManager {
private HashMap<String, BaseProduct> proto=new HashMap<String,BaseProduct>();
public ProtoTypeManager() {
proto.put("A",new ProductA());
proto.put("B",new ProductB());
}
public void addProduct(String type, BaseProduct product) {
proto.put(type, product);
}
public BaseProduct getProduct(String type) {
return (BaseProduct) proto.get(type).clone();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 访问类 PrototypeTest
*/
public class PrototypeTest {
@Test
public void testProduct(){
ProtoTypeManager pm=new ProtoTypeManager();
ProductA a1 = (ProductA) pm.getProduct("A");
a1.setPrice(10);
ProductB b1 = (ProductB) pm.getProduct("B");
b1.setPrice(10);
assertNotEquals(a1.countPrice(), b1.countPrice(), 0.0); // 通过测试
System.out.println("A的售价:" + a1.countPrice()); // A的售价:10.0
System.out.println("B的售价:" + b1.countPrice()); // B的售价:8.0
}
}

4.2 原型模式的适用场景

  1. 类初始化消耗资源较多
  2. new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
  3. 构造函数比较复杂
  4. 循环体中生产大量对象时,可读性下降
  • 优缺点:性能比new对象高,简化创建过程,逃避构造函数的约束;但必须配备克隆方法,深拷贝浅拷贝运用不当容易引入风险。

pom.xml的主要依赖包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>

参考资料: