想複制一個簡單變量,很簡單:
int apples = 5;
int pears = apples;
基本資料類型都适用。
但若複制的是個對象,就有點複雜了。
先看段代碼:
@Data
class Student {
private int number;
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = stu1;
System.out.println("學生1:" + stu1.getNumber());
System.out.println("學生2:" + stu2.getNumber());
}
}
結果:
學生1:12345
學生2:12345
對象複制不過如此,真就這麼簡單?
改變stu2執行個體的number字段:
stu2.setNumber(54321);
System.out.println("學生1:" + stu1.getNumber());
System.out.println("學生2:" + stu2.getNumber());
學生1:54321
學生2:54321
改變學生2的學号,學生1的學号怎麼也發生變化呢?
原因出在(stu2 = stu1) 這一句。該語句的作用是将stu1的引用指派給stu2,這樣,stu1和stu2指向記憶體堆中同一個對象。
怎樣才能正确複制一個對象?
Object有11個方法,兩個protected方法,其中一個為clone:
第一次聲明保證克隆對象将有單獨的記憶體位址配置設定。
第二次聲明表明,原始和克隆的對象應該具有相同的類類型,但它不是強制性的。
第三聲明表明,原始和克隆的對象應該是平等的equals()方法使用,但它不是強制性的。
因為每個類最終父類都是Object,是以它們都有clone(),但該方法是protected,是以都不能在類外進行通路。
要想對一個對象進行複制,就需重寫clone()。
為什麼克隆
直接new一個新的不行嗎?
克隆的對象可能包含一些已經修改過的屬性,而new出來的對象的屬性都還是初始化時候的值,是以當需要一個新的對象來儲存目前對象的“狀态”就靠clone。
那我把這個對象的臨時屬性一個一個的指派給我新new的對象不也行嘛。是可以,但是
麻煩
而clone是個native方法,快
常見的
Object a = new Object();
Object b;
b = a;
這種形式的代碼複制的是引用,即對象在記憶體中的位址。
而通過clone方法指派的對象跟原來的對象時同時獨立存在。
如何實作克隆
淺、深克隆的主要差別在于是否支援引用類型的成員變量的複制。
淺克隆
被複制的類需要實作Clonenable接口(不實作的話在調用clone方法會抛CloneNotSupportedException), 該接口為标記接口(不含任何方法)
覆寫clone()方法,通路修飾符設為public。方法中調用super.clone()方法得到需要的複制對象
對上面那個方法進行改造:
@Data
class Student implements Cloneable {
private int number;
@Override
public Object clone() {
Student stu = null;
try {
stu = (Student)super.clone();
} catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone();
System.out.println("學生1:" + stu1.getNumber());
System.out.println("學生2:" + stu2.getNumber());
stu2.setNumber(54321);
System.out.println("學生1:" + stu1.getNumber());
System.out.println("學生2:" + stu2.getNumber());
}
}
學生1:12345
學生2:12345
學生1:12345
學生2:54321
如果不相信這兩個對象不是同一個對象,那麼你可以看看這一句:
System.out.println(stu1 == stu2); // false
深克隆
在學生類裡再加一個Address類
@Data
class Address {
private String add;
}
@Data
class Student implements Cloneable {
private int number;
private Address addr;
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("學生1:" + stu1.getNumber() + ",位址:" + stu1.getAddr().getAdd());
System.out.println("學生2:" + stu2.getNumber() + ",位址:" + stu2.getAddr().getAdd());
}
}
學生1:123,位址:杭州市
學生2:123,位址:杭州市
在main方法中試着改變addr執行個體的位址。
addr.setAdd("西湖區");
System.out.println("學生1:" + stu1.getNumber() + ",位址:" + stu1.getAddr().getAdd());
System.out.println("學生2:" + stu2.getNumber() + ",位址:" + stu2.getAddr().getAdd());
結果:
學生1:123,位址:杭州市
學生2:123,位址:杭州市
學生1:123,位址:西湖區
學生2:123,位址:西湖區
怎麼兩個學生的位址都改變了?
淺克隆隻是複制了addr變量的引用,并沒有真正的開辟另一塊空間,将值複制後再将引用傳回給新對象。
達到真複制對象,不是純粹引用複制。要将Address類可複制化,且修改clone方法:
@Data
class Address implements Cloneable {
private String add;
@Override
public Object clone() {
Address addr = null;
try{
addr = (Address)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
}
@Data
class Student implements Cloneable{
private int number;
private Address addr;
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone(); // 淺複制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr = (Address)addr.clone(); // 深複制
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("學生1:" + stu1.getNumber() + ",位址:" + stu1.getAddr().getAdd());
System.out.println("學生2:" + stu2.getNumber() + ",位址:" + stu2.getAddr().getAdd());
addr.setAdd("西湖區");
System.out.println("學生1:" + stu1.getNumber() + ",位址:" + stu1.getAddr().getAdd());
System.out.println("學生2:" + stu2.getNumber() + ",位址:" + stu2.getAddr().getAdd());
}
}
結果:
學生1:123,位址:杭州市
學生2:123,位址:杭州市
學生1:123,位址:西湖區
學生2:123,位址:杭州市
最後我們可以看看API裡其中一個實作了clone方法的類:
該類其實也屬于深克隆。