大概意思是說:返回一個要克隆對象的副本,克隆的類型依賴被克隆對象,換句話說:克隆後的對象類型與被克隆對象的類型相同。
一、簡單用法
只需要在需要clone的對象上實現(implements)Cloneable接口,然後再在類中加上clone方法,在方法中只需要調用super.clone(),根據自己的需要實現即可。
- public class Student implements Cloneable {
- private String name;
- private int age;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- @Override
- protected Student clone() throws CloneNotSupportedException {
- return (Student)super.clone();
- }
- public static void main(String[] args) {
- Student stu = new Student();
- stu.setAge(1);
- stu.setName("aa");
- System.out.println(stu + " age: " + stu.getAge() + " name: " + stu.getName());
- try {
- Student sC = stu.clone();
- System.out.println(sC + " sC.age: " + sC.getAge() + " sC.name: " + sC.getName());
- sC.setAge(12);
- sC.setName("bb");
- System.out.println(stu + " age: " + stu.getAge() + " name: " + stu.getName());
- System.out.println(sC + " sC.age: " + sC.getAge() + " sC.name: " + sC.getName());
- } catch (CloneNotSupportedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
public class Student implements Cloneable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Student clone() throws CloneNotSupportedException {
return (Student)super.clone();
}
public static void main(String[] args) {
Student stu = new Student();
stu.setAge(1);
stu.setName("aa");
System.out.println(stu + " age: " + stu.getAge() + " name: " + stu.getName());
try {
Student sC = stu.clone();
System.out.println(sC + " sC.age: " + sC.getAge() + " sC.name: " + sC.getName());
sC.setAge(12);
sC.setName("bb");
System.out.println(stu + " age: " + stu.getAge() + " name: " + stu.getName());
System.out.println(sC + " sC.age: " + sC.getAge() + " sC.name: " + sC.getName());
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
輸出結果:
testClone.Student@15db9742 age: 1 name: aa
testClone.Student@6d06d69c sC.age: 1 sC.name: aa
testClone.Student@15db9742 age: 1 name: aa
testClone.Student@6d06d69c sC.age: 12 sC.name: bb
分析結果:1、根據輸出結果中前邊的類名,可以得出被克隆對象的與原來的對象是同一種類型。2、根據內存地址(hashcode)知道,被克隆對象的與原來的對象是存在於內存中的不同的兩個對象。所以後邊有一個賦值,對原來對象沒有任何影響。
二、“影子”克隆與深度克隆
首先看一個例子:
- class Bag{//學生的書包
- private int width;
- private String logo;
- public int getWidth() {
- return width;
- }
- public void setWidth(int width) {
- this.width = width;
- }
- public String getLogo() {
- return logo;
- }
- public void setLogo(String logo) {
- this.logo = logo;
- }
- }
- public class Student2 implements Cloneable {
- private String name;
- private int age;
- private Bag bag;
- public Bag getBag() {
- return bag;
- }
- public void setBag(Bag bag) {
- this.bag = bag;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- @Override
- protected Student2 clone() throws CloneNotSupportedException {
- return (Student2)super.clone();
- }
- public static void main(String[] args) {
- Student2 stu = new Student2();
- stu.setAge(1);
- stu.setName("aa");
- Bag b = new Bag();
- b.setWidth(10);
- b.setLogo("Nike");
- stu.setBag(b);
- printStudent(stu);
- try {
- Student2 sC = stu.clone();
- printStudent(sC);
- sC.setAge(12);
- sC.setName("bb");
- sC.getBag().setWidth(100);//改變書包的屬性
- sC.getBag().setLogo("JNike");
- printStudent(stu);
- printStudent(sC);
- } catch (CloneNotSupportedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- /**
- * 輸出
- * @param stu
- */
- private static void printStudent(Student2 stu) {
- System.out.println(stu + " age: " + stu.getAge() + " name: " + stu.getName() +
- " bag: " + stu.getBag() + "(" + stu.getBag().getLogo() + " width: " +
- stu.getBag().getWidth() + ")");
- }
- }
class Bag{//學生的書包
private int width;
private String logo;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public String getLogo() {
return logo;
}
public void setLogo(String logo) {
this.logo = logo;
}
}
public class Student2 implements Cloneable {
private String name;
private int age;
private Bag bag;
public Bag getBag() {
return bag;
}
public void setBag(Bag bag) {
this.bag = bag;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Student2 clone() throws CloneNotSupportedException {
return (Student2)super.clone();
}
public static void main(String[] args) {
Student2 stu = new Student2();
stu.setAge(1);
stu.setName("aa");
Bag b = new Bag();
b.setWidth(10);
b.setLogo("Nike");
stu.setBag(b);
printStudent(stu);
try {
Student2 sC = stu.clone();
printStudent(sC);
sC.setAge(12);
sC.setName("bb");
sC.getBag().setWidth(100);//改變書包的屬性
sC.getBag().setLogo("JNike");
printStudent(stu);
printStudent(sC);
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 輸出
* @param stu
*/
private static void printStudent(Student2 stu) {
System.out.println(stu + " age: " + stu.getAge() + " name: " + stu.getName() +
" bag: " + stu.getBag() + "(" + stu.getBag().getLogo() + " width: " +
stu.getBag().getWidth() + ")");
}
}
輸出結果: testClone.Student2@15db9742 age: 1 name: aa bag: testClone.Bag@6d06d69c(Nike width: 10)
testClone.Student2@7852e922 age: 1 name: aa bag: testClone.Bag@6d06d69c(Nike width: 10)
testClone.Student2@15db9742 age: 1 name: aa bag: testClone.Bag@6d06d69c(JNike width: 100)
testClone.Student2@7852e922 age: 12 name: bb bag: testClone.Bag@6d06d69c(JNike width: 100)
分析:發現是不是跟預期的不太一樣,通過第二個同學改變書包,但是第一個同學的書包也被改變了。並且通過內存地址可知,他們是同一對象(書包)。原因:調用Object類中clone()方法產生的效果是:先在內存中開闢一塊和原始對象一樣的空間,然後原樣拷貝原始對象中的內 容。對基本數據類型,這樣的操作是沒有問題的,但對非基本類型變量,我們知道它們保存的僅僅是對象的引用,這也導致clone後的非基本類型變量和原始對 象中相應的變量指向的是同一個對象。 這就是所謂的“影子”克隆。
解決方案:深度克隆,既是對裏邊的引用也要克隆。以下是實現:
- class Bag implements Cloneable{//學生的書包
- private int width;//寬
- private String logo;//品牌
- public int getWidth() {
- return width;
- }
- public void setWidth(int width) {
- this.width = width;
- }
- public String getLogo() {
- return logo;
- }
- public void setLogo(String logo) {
- this.logo = logo;
- }
- @Override
- protected Bag clone() throws CloneNotSupportedException {
- return (Bag)super.clone();
- }
- }
- public class Student3 implements Cloneable {
- private String name;
- private int age;
- private Bag bag;
- public Bag getBag() {
- return bag;
- }
- public void setBag(Bag bag) {
- this.bag = bag;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- @Override
- protected Student3 clone() throws CloneNotSupportedException {
- Student3 stu = (Student3)super.clone();
- stu.bag = bag.clone();
- return stu;
- }
- public static void main(String[] args) {
- Student3 stu = new Student3();
- stu.setAge(1);
- stu.setName("aa");
- Bag b = new Bag();
- b.setWidth(10);
- b.setLogo("Nike");
- stu.setBag(b);
- printStudent(stu);
- try {
- Student3 sC = stu.clone();
- printStudent(sC);
- sC.setAge(12);
- sC.setName("bb");
- sC.getBag().setWidth(100);//改變書包的屬性
- sC.getBag().setLogo("JNike");
- printStudent(stu);
- printStudent(sC);
- } catch (CloneNotSupportedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- /**
- * 輸出
- * @param stu
- */
- private static void printStudent(Student3 stu) {
- System.out.println(stu + " age: " + stu.getAge() + " name: " + stu.getName() +
- " bag: " + stu.getBag() + "(" + stu.getBag().getLogo() + " width: " +
- stu.getBag().getWidth() + ")");
- }
- }
class Bag implements Cloneable{//學生的書包
private int width;//寬
private String logo;//品牌
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public String getLogo() {
return logo;
}
public void setLogo(String logo) {
this.logo = logo;
}
@Override
protected Bag clone() throws CloneNotSupportedException {
return (Bag)super.clone();
}
}
public class Student3 implements Cloneable {
private String name;
private int age;
private Bag bag;
public Bag getBag() {
return bag;
}
public void setBag(Bag bag) {
this.bag = bag;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Student3 clone() throws CloneNotSupportedException {
Student3 stu = (Student3)super.clone();
stu.bag = bag.clone();
return stu;
}
public static void main(String[] args) {
Student3 stu = new Student3();
stu.setAge(1);
stu.setName("aa");
Bag b = new Bag();
b.setWidth(10);
b.setLogo("Nike");
stu.setBag(b);
printStudent(stu);
try {
Student3 sC = stu.clone();
printStudent(sC);
sC.setAge(12);
sC.setName("bb");
sC.getBag().setWidth(100);//改變書包的屬性
sC.getBag().setLogo("JNike");
printStudent(stu);
printStudent(sC);
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 輸出
* @param stu
*/
private static void printStudent(Student3 stu) {
System.out.println(stu + " age: " + stu.getAge() + " name: " + stu.getName() +
" bag: " + stu.getBag() + "(" + stu.getBag().getLogo() + " width: " +
stu.getBag().getWidth() + ")");
}
}
輸出:
testClone.Student3@15db9742 age: 1 name: aa bag: testClone.Bag@6d06d69c(Nike width: 10)
testClone.Student3@7852e922 age: 1 name: aa bag: testClone.Bag@4e25154f(Nike width: 10)
testClone.Student3@15db9742 age: 1 name: aa bag: testClone.Bag@6d06d69c(Nike width: 10)
testClone.Student3@7852e922 age: 12 name: bb bag: testClone.Bag@4e25154f(JNike width: 100)
與我們期望的相同了。
三、是不是萬事大吉了?
注意:要知道不是所有的類都能實現深度clone的。例如,StringBuffer,看一下 JDK API中關於StringBuffer的說明,StringBuffer沒有重載clone()方法,更爲嚴重的是StringBuffer還是一個 final類,這也是說我們也不能用繼承的辦法間接實現StringBuffer的clone。如果一個類中包含有StringBuffer類型對象或和 StringBuffer相似類的對象,我們有兩種選擇:要麼只能實現影子clone,要麼自己重新生成對象: new StringBuffer(oldValue.toString()); 進行賦值。
你是不是想問上邊例子中的String呢,難道實現了clone?(查詢String.java源碼,發現並沒有)通過以下的例子爲你解答疑惑:
- class StrCloneDemo implements Cloneable {
- public String str;
- public StringBuffer strBuff;
- public Object clone() throws CloneNotSupportedException {
- return (StrCloneDemo) super.clone();
- }
- }
- public class StrCloneDemoTest {
- public static void main(String[] a) {
- StrCloneDemo scd1 = new StrCloneDemo();
- scd1.str = new String("abcdefghijk");
- scd1.strBuff = new StringBuffer("rstuvwxyz");
- System.out.println("before clone,scd1.str = " + scd1.str);
- System.out.println("before clone,scd1.strBuff = " + scd1.strBuff);
- StrCloneDemo scd2 = null;
- try {
- scd2 = (StrCloneDemo) scd1.clone();
- } catch (CloneNotSupportedException e) {
- e.printStackTrace();
- }
- scd2.str = scd2.str.substring(0, 3);
- scd2.strBuff = scd2.strBuff.append("RSTUVWXYZ");
- System.out.println("******************************************");
- System.out.println("after clone,scd1.str = " + scd1.str);
- System.out.println("after clone,scd1.strBuff = " + scd1.strBuff);
- System.out.println("******************************************");
- System.out.println("after clone,scd2.str = " + scd2.str);
- System.out.println("after clone,scd2.strBuff = " + scd2.strBuff);
- }
- }
class StrCloneDemo implements Cloneable {
public String str;
public StringBuffer strBuff;
public Object clone() throws CloneNotSupportedException {
return (StrCloneDemo) super.clone();
}
}
public class StrCloneDemoTest {
public static void main(String[] a) {
StrCloneDemo scd1 = new StrCloneDemo();
scd1.str = new String("abcdefghijk");
scd1.strBuff = new StringBuffer("rstuvwxyz");
System.out.println("before clone,scd1.str = " + scd1.str);
System.out.println("before clone,scd1.strBuff = " + scd1.strBuff);
StrCloneDemo scd2 = null;
try {
scd2 = (StrCloneDemo) scd1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
scd2.str = scd2.str.substring(0, 3);
scd2.strBuff = scd2.strBuff.append("RSTUVWXYZ");
System.out.println("******************************************");
System.out.println("after clone,scd1.str = " + scd1.str);
System.out.println("after clone,scd1.strBuff = " + scd1.strBuff);
System.out.println("******************************************");
System.out.println("after clone,scd2.str = " + scd2.str);
System.out.println("after clone,scd2.strBuff = " + scd2.strBuff);
}
}
輸出: before clone,scd1.str = abcdefghijk
before clone,scd1.strBuff = rstuvwxyz
******************************************
after clone,scd1.str = abcdefghijk
after clone,scd1.strBuff = rstuvwxyzRSTUVWXYZ
******************************************
after clone,scd2.str = abc
after clone,scd2.strBuff = rstuvwxyzRSTUVWXYZ
分析:String類型的變量好象已經實現了深度clone,因爲對scd2.str的改動並沒有影響到scd1.str!實質上,在clone的時候scd1.str與scd2.str仍然是引用,而且都指向了同一個 String對象。但在執行c2.str = c2.str.substring(0,5)的時候,生成了一個新的String類型,然後又賦回給scd2.str。這是因爲String被 Sun公司的工程師寫成了一個不可更改的類(immutable class),在所有String類中的函數都不能更改自身的值。類似的,String類中的其它方法也是如此,都是生成一個新的對象返回。當然StringBuffer還是原來的對象。