淺複製
首先建立兩個類,方便理解淺複製
@Data
class Student implements Cloneable{
//年齡和名字是基本屬性
private int age;
private String name;
//書包是引用屬性
private Bag bag;
public Student(int age, String name, Bag bag) {
this.age = age;
this.name = name;
this.bag = bag;
}
@Override
public String toString() {
return "age=" + age + ", name='" + name + ", bag=" + bag;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
@Data
class Bag {
private String color;
private int price;
public Bag(String color, int price) {
this.color = color;
this.price = price;
}
@Override
public String toString() {
return "color='" + color + ", price=" + price;
}
}
Cloneable 介面只是一個標記介面(沒屬性和方法):
public interface Cloneable {
}
標記介面的作用其實很簡單,用來表示某個功能在執行的時候是合法的。
如果不實現Cloneable介面直接重寫並呼叫clone()方法,會丟擲 CloneNotSupportedException 異常。
測試類
class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student(18, "張三", new Bag("紅",100));
Student student2 = (Student) student1.clone();
System.out.println("淺複製後:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
//修改非引用型別屬性name
student2.setName("李四");
//修改引用型別屬性bag
Bag bag = student2.getBag();
bag.setColor("藍");
bag.setPrice(200);
System.out.println("修改了 student2 的 name 和 bag 後:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
}
}
//列印結果
淺複製後:
student1:age=18, name='張三, bag=color='紅, price=100
student2:age=18, name='張三, bag=color='紅, price=100
修改了 student2 的 name 和 bag 後:
student1:age=18, name='張三, bag=color='藍, price=200
student2:age=18, name='李四, bag=color='藍, price=200
可以看得出,淺複製後:
修改了student2的非引用型別屬性name,student1的name並不會跟著改變
但修改了student2的引用型別屬性bag,student1的bag跟著改變了
說明淺複製克隆的物件中,引用型別的欄位指向的是同一個,當改變任何一個物件,另外一個物件也會隨之改變。
深複製
深複製和淺複製不同的,深複製中的引用型別欄位也會克隆一份,當改變任何一個物件,另外一個物件不會隨之改變。
例子
@Data
class Bag implements Cloneable {
private String color;
private int price;
public Bag(String color, int price) {
this.color = color;
this.price = price;
}
@Override
public String toString() {
return "color='" + color + ", price=" + price;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
注意,此時的 Bag 類和淺複製時不同,重寫了 clone() 方法,並實現了 Cloneable 介面。為的就是深複製的時候也能夠克隆該欄位。
@Data
class Student implements Cloneable{
//年齡和名字是基本屬性
private int age;
private String name;
//書包是引用屬性
private Bag bag;
public Student(int age, String name, Bag bag) {
this.age = age;
this.name = name;
this.bag = bag;
}
@Override
public String toString() {
return "age=" + age + ", name='" + name + ", bag=" + bag;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student s = (Student) super.clone();
s.setBag((Bag) s.getBag().clone());
return s;
}
}
注意,此時 Student 類也與之前的不同,clone() 方法當中,不再只呼叫 Object 的 clone() 方法對 Student 進行克隆了,還對 Bag 也進行了克隆。
來看測試類
class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
Student student1 = new Student(18, "張三", new Bag("紅",100));
Student student2 = (Student) student1.clone();
System.out.println("深複製後:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
//修改非引用型別屬性name
student2.setName("李四");
//修改引用型別屬性bag
Bag bag = student2.getBag();
bag.setColor("藍");
bag.setPrice(200);
System.out.println("修改了 student2 的 name 和 bag 後:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
}
}
//這個測試類和之前的淺複製的測試類一樣,但執行結果是不同的。
深複製後:
student1:age=18, name='張三, bag=color='紅, price=100
student2:age=18, name='張三, bag=color='紅, price=100
修改了 student2 的 name 和 bag 後:
student1:age=18, name='張三, bag=color='紅, price=100
student2:age=18, name='李四, bag=color='藍, price=200
不只是 student1 和 student2 是不同的物件,它們中的 bag 也是不同的物件。所以,改變了 student2 中的 bag 並不會影響到 student1。
不過,透過 clone() 方法實現的深複製比較笨重,因為要將所有的引用型別都重寫 clone() 方法。
更好的方法是利用序列化
序列化
序列化是將物件寫入流中,而反序列化是將物件從流中讀取出來。寫入流中的物件就是對原始物件的複製。需要注意的是,每個要序列化的類都要實現 Serializable 介面,該介面和 Cloneable 介面類似,都是標記型介面。
來看例子
@Data
class Bag implements Serializable {
private String color;
private int price;
public Bag(String color, int price) {
this.color = color;
this.price = price;
}
@Override
public String toString() {
return "color='" + color + ", price=" + price;
}
}
Bag 需要實現 Serializable 介面
@Data
class Student implements Serializable {
//年齡和名字是基本屬性
private int age;
private String name;
//書包是引用屬性
private Bag bag;
public Student(int age, String name, Bag bag) {
this.age = age;
this.name = name;
this.bag = bag;
}
@Override
public String toString() {
return "age=" + age + ", name='" + name + ", bag=" + bag;
}
//使用序列化複製
public Object serializeClone() throws IOException, ClassNotFoundException {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
Student 類也需要實現 Serializable 介面,並且在該類中,增加了一個 serializeClone() 的方法,利用 OutputStream 進行序列化,InputStream 進行反序列化,這樣就實現了深複製。
來看示例
class TestClone {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
Student student1 = new Student(18, "張三", new Bag("紅",100));
Student student2 = (Student) student1.serializeClone();
System.out.println("淺複製後:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
//修改非引用型別屬性name
student2.setName("李四");
//修改引用型別屬性bag
Bag bag = student2.getBag();
bag.setColor("藍");
bag.setPrice(200);
System.out.println("修改了 student2 的 name 和 bag 後:");
System.out.println("student1:" + student1);
System.out.println("student2:" + student2);
}
}
//與之前測試類不同的是,呼叫了 serializeClone() 方法。
淺複製後:
student1:age=18, name='張三, bag=color='紅, price=100
student2:age=18, name='張三, bag=color='紅, price=100
修改了 student2 的 name 和 bag 後:
student1:age=18, name='張三, bag=color='紅, price=100
student2:age=18, name='李四, bag=color='藍, price=200
測試結果和之前用 clone() 方法實現的深複製一樣。
clone() 方法同時是一個本地(native)方法,它的具體實現會交給 HotSpot 虛擬機器,那就意味著虛擬機器在執行該方法的時候,會將其替換為更高效的 C/C++ 程式碼,進而呼叫作業系統去完成物件的克隆工作。
需要注意,由於是序列化涉及到輸入流和輸出流的讀寫,在效能上要比 HotSpot 虛擬機器實現的 clone() 方法差很多。