作用:通过拷贝一个对象实例来创建一个新的对象。
Java 提供了 clone() 方法用于实现对象的拷贝,clone() 方法来自于 Object,Java 中所有的类都直接或间接继承自 Object,因此重写 clone() 方法即可实现对象的拷贝。
- 重写的 clone() 方法中,依次调用父类的 clone() 方法,最终调用 Object.clone() 方法,通知 JVM 实现对象的拷贝
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class Order {
private String orderNo;
private List<Product> products;
private String customer;
private String contactNumber;
private String shippingAddress;
private BigDecimal amount;
public BigDecimal getAmount() {
if (products == null || products.size() == 0) {
return BigDecimal.ZERO;
}
BigDecimal totalAmount = new BigDecimal(0);
for (Product prod : products) {
totalAmount = totalAmount.add(prod.getPrice());
}
return totalAmount;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//调用 Object.clone();
return super.clone();
}
}
import lombok.Data;
import java.math.BigDecimal;
@Data
public class Product {
private String productNo;
private BigDecimal price;
}
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class OrderTester {
public static void main(String args[]) {
Product product1 = new Product();
product1.setProductNo("P0001");
product1.setPrice(new BigDecimal(2));
List<Product> products = new ArrayList<>(2);
products.add(product1);
Order order = new Order();
order.setOrderNo("O201904170001");
order.setCustomer("ZhangSan");
order.setProducts(products);
order.setContactNumber("130 xxxx xxxx");
order.setShippingAddress("xx省-xx市-xx区-xx小区");
System.out.println("Order: \n" + order);
Order newOrder = null;
try {
if ((newOrder = (Order) order.clone()) == null) {
System.err.println("\nOrder clone failed!");
}
System.out.println("\nCopy Order: \n" + newOrder);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
当执行测试类时,程序抛出了异常:
java.lang.CloneNotSupportedException: com.designpatterns.prototype.complex.Order
at java.lang.Object.clone(Native Method)
at com.designpatterns.prototype.complex.Order.clone(Order.java:31)
at com.designpatterns.prototype.complex.OrderTester.main(OrderTester.java:26)
查看 JDK 源码发现,clone 方法是一个 native 方法,重写 clone() 方法的同时,类必须实现 Cloneable 接口,用来告知 JVM 该对象可以拷贝,Cloneable 接口未定义任何方法,仅作为一种标识存在:
Object.clone()
/*
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown.
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
Cloneable 接口
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
* Therefore, it is not possible to clone an object merely by virtue of the
* fact that it implements this interface. Even if the clone method is invoked
* reflectively, there is no guarantee that it will succeed.
*
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}
Order 实现 Cloneable 接口后,执行测试类,对象拷贝成功
public class Order implements Cloneable {
...
}
执行结果
Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
Copy Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
修改测试类,添加存款属性:deposit
import java.util.HashMap;
import java.util.Map;
public class OrderTester {
public static void main(String args[]) {
Product product1 = new Product();
product1.setProductNo("P0001");
product1.setPrice(new BigDecimal(2));
List<Product> products = new ArrayList<>(2);
products.add(product1);
Order order = new Order();
order.setOrderNo("O201904170001");
order.setCustomer("ZhangSan");
order.setProducts(products);
order.setContactNumber("130 xxxx xxxx");
order.setShippingAddress("xx省-xx市-xx区-xx小区");
System.out.println("Order: \n" + order);
Order newOrder = null;
try {
if ((newOrder = (Order) order.clone()) == null) {
System.err.println("\nOrder clone failed!");
}
System.out.println("\nCopy Order: \n" + newOrder);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
product1.setPrice(new BigDecimal(100));
System.out.println("\nUpdate product #1 price for Order:\n" + order);
System.out.println("\nNew Order's product #1 price is updated at same time:\n" + newOrder);
}
}
通过运行结果发现,对象 clone 之后,更新原始对象 Product #1 的 price 属性值后,新对象的 Product #1 的 price 属性也相应发生了改变,这是因为 Java 引用对象保存的是引用的地址,即堆内存中分配的地址,这里就涉及到 浅拷贝 和 深拷贝。
- 浅拷贝:仅拷贝自身对象和值类型的成员变量,对引用类型的成员变量只拷贝其引用
- 深拷贝:拷贝自身对象、值类型的成员变量、引用类型的成员变量,实现方式有两种:
- 涉及相关 引用类型 实现 Cloneable 接口,重写 clone 方法,调用 super.clone(),(String、BigDecimal 类型除外;String 因为线程池的设计,replace、substring 等方法会生成新的 String 对象;BigDecimal 运算也会生成新的对象)
- 通过 序列化 与 反序列化 方式实现,需要通过 字节数组 或 文件 的方式作为临时缓存
注:这里 String 虽然也是 引用类型,因为 字符串常量池的设计,改变原始对象的属性将不会对新对象造成影响。
Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
Copy Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
Update product #1 price for Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=100)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=100)
New Order's product #1 price is updated at same time:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=100)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
深拷贝
- Product 实现 Cloneable 接口并重写 clone 方法
import lombok.Data;
import java.math.BigDecimal;
@Data
public class Product implements Cloneable {
private String productNo;
private BigDecimal price;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
- 更新 Order.clone() 方法
@Override
protected Object clone() throws CloneNotSupportedException {
Order newOrder = null;
try {
newOrder = (Order) super.clone();
} catch (CloneNotSupportedException e) {
System.err.println("Order clone failed!");
}
List<Product> newProducts = new ArrayList<>(products.size());
for (Product product : products) {
newProducts.add((Product) product.clone());
}
newOrder.setProducts(newProducts);
return newOrder;
}
再次执行测试类,新对象中 Product #1 的 price 属性并未发生改变,原始对象与新对象完全独立,互不影响。
Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
Copy Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
Update product #1 price for Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=100)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=100)
New Order's product #1 price is updated at same time:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
通过 序列化 & 反序列化 的方式实现深拷贝,必须实现 Serializable 接口(生成 serialVersionUID)
- 默认情况下,被 trainsient 关键字修饰的成员变量将不会被序列化
public class Order implements Cloneable, Serializable {
private static final long serialVersionUID = 3631376205708889405L;
...
}
public class Product implements Cloneable, Serializable {
private static final long serialVersionUID = -1207754602611415304L;
...
}
import java.io.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class OrderTester {
public static void main(String args[]) {
Product product1 = new Product();
product1.setProductNo("P0001");
product1.setPrice(new BigDecimal(2));
List<Product> products = new ArrayList<>(2);
products.add(product1);
Order order = new Order();
order.setOrderNo("O201904170001");
order.setCustomer("ZhangSan");
order.setProducts(products);
order.setContactNumber("130 xxxx xxxx");
order.setShippingAddress("xx省-xx市-xx区-xx小区");
System.out.println("Order: \n" + order);
byte[] bytes = writeObject(order);
Order newOrder = readObject(bytes);
if (newOrder == null) {
System.err.println("\nOrder clone failed!");
return;
}
System.out.println("\nCopy Order: \n" + newOrder);
product1.setPrice(new BigDecimal(50));
System.out.println("\nUpdate product #1 price for Order:\n" + order);
System.out.println("\nNew Order's product #1 price is updated at same time:\n" + newOrder);
}
public static Order readObject(byte[] bytes) {
Order newOrder = null;
try (ByteArrayInputStream byteIS = new ByteArrayInputStream(bytes);
ObjectInputStream input = new ObjectInputStream(byteIS)){
newOrder = (Order) input.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return newOrder;
}
public static byte[] writeObject(Order order) {
byte[] bytes = null;
try (ByteArrayOutputStream byteOS = new ByteArrayOutputStream();
ObjectOutputStream output = new ObjectOutputStream(byteOS)) {
output.writeObject(order);
bytes = byteOS.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return bytes;
}
}
Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
Disconnected from the target VM, address: '127.0.0.1:58009', transport: 'socket'
Copy Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
Update product #1 price for Order:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=50)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=50)
New Order's product #1 price is updated at same time:
Order(orderNo=O201904170001, products=[Product(productNo=P0001, price=2)], customer=ZhangSan, contactNumber=130 xxxx xxxx, shippingAddress=xx省-xx市-xx区-xx小区, amount=2)
参考文章:
http://c.biancheng.net/view/1343.html
https://www.cnblogs.com/shakinghead/p/7651502.html