Java中对象的传递都是引用传递,那么如果我们只是想将一个对象的属性值复制给另一个对象,就可以使用
protected Object clone() throws CloneNotSupportedException
来实现一个对象的复制。下面就简单说一说 clone() 方法的使用。
浅复制
当一个类中的属性是普通的数据类型,不包含其他类的对象时调用 clone() 函数就是执行浅复制。然而,要复制一个类的对象还要说明该类是可以被复制的,方法就是让该类实现一个标记接口“Cloneable
”,该接口中没有实际的方法和属性,只是作为一个标记,说明该类是可以被复制的。例如:
public class BaseClass implements Cloneable
{
// Rest of the class.
// Notice that you don't even have to write the clone method!
}
如果一个类没有实现该接口就直接使用 clone() 方法,会抛出“CloneNotSupportedException
”的异常。
当一个类实现了“Cloneable
”接口后,就可以重写(override) Object 类的 clone() 方法来实现该类的克隆操作。希望在文章开头部分你就注意到该方法是被 “protect” 修饰的。因此在重写时只能使用比 “protect” 更高的权限。一般的写法如下:
public class BaseClass implements Cloneable
{
/* Rest of the class.*/
public Object clone ()
throws CloneNotSupportedException
{
return super.clone();
}
}
希望你能注意到,“clone()” 方法的返回值类型是 Object 类,因此在调用这个方法的时候往往需要做一个类型转化。
一个小例子:
class Person implements Cloneable { /*实现 Cloneable 接口,说明该类可以被克隆。该接口为空接口,里面没有定义方法和属性,所以不需要实例化该接口。*/
String name;
int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override /*覆盖父类 Object 中的 clone() 方法*/
protected Object clone() throws CloneNotSupportedException {
return super.clone(); /*调用 Object 类的 clone() 方法,返回该对象的副本(是 Object 类的对象)*/
}
public void getPersonInfo() {
System.out.println("Name: "+this.name+" Age:"+this.age);
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Person pA = new Person("张三",20);
System.out.println("pA 的信息:");
pA.getPersonInfo();
Person pB = (Person) pA.clone(); /*克隆 pA 对象给 pB 需要注意的是 clone() 方法返回的是一个 Object 类对象,需要向下转化为 Person 类对象*/
System.out.println("克隆后 pB 的信息:");
pB.getPersonInfo(); /*输出 pB 对象的信息,和 pA 对象信息相同*/
System.out.println("改变 pB 对象 age 的值为 30");
pB.age = 30; /*更改 pB 对象中 age 属性值*/
System.out.println("修改后 pB 的信息如下(已经改变):");
pB.getPersonInfo(); /*pB 对象中 age 属性值已经改变*/
System.out.println("pA 的信息如下(还是原来的值):");
pA.getPersonInfo(); /*pA 对象中的 age 属性值没有被改变,说明 pB 不是 pA 的引用,而是复制*/
}
}
运行结果如下:
pA 的信息:
Name: 张三 Age:20
克隆后 pB 的信息:
Name: 张三 Age:20
改变 pB 对象 age 的值为 30
修改后 pB 的信息如下(已经改变):
Name: 张三 Age:30
pA 的信息如下(还是原来的值):
Name: 张三 Age:20
深复制
当要被克隆的类中的属性含有其他类(或者本类)的对象时,这时的克隆操作就比浅复制更深了,需要对调用 super.clone() 方法返回的结果进行二次开发,否则,复制后的副本和原本其中的引用对象属性值引用的是同一个对象。副本对该引用对象属性进行的改变会影响到原本的引用对象属性值。下面给出一个小例子来说明该问题:
class BirthDay {
String year;
String month;
String day;
public BirthDay(String year, String month, String day) {
this.year = year;
this.month = month;
this.day = day;
}
public BirthDay() {
}
public String toString() {
return this.year + "-" + this.month + "-" + this.day;
}
}
class Person implements Cloneable {
String name;
int age;
BirthDay birthDay;
public Person() {
}
public Person(String name, int age, BirthDay birthDay) {
this.name = name;
this.age = age;
this.birthDay = birthDay;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void getPersonInfo() {
System.out.println("Name: "+this.name+" Age:"+this.age + " Birthday: " + this.birthDay);
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Person pA = new Person("张三", 20, new BirthDay("1990", "01", "01"));
pA.getPersonInfo();
Person pB = (Person) pA.clone();
pB.getPersonInfo();
pA.birthDay.year = "2000"; /*修改 pA 中 birthDay 属性值*/
pB.getPersonInfo(); /*查看 pB 的信息*/
}
}
运行结果:
Name: 张三 Age:20 Birthday: 1990-01-01
Name: 张三 Age:20 Birthday: 1990-01-01
Name: 张三 Age:20 Birthday: 2000-01-01
可以看到,当修改了 pA 中 birthDay 属性值后,pB 的 birthDay 属性值也跟着改变了,原因就是 pB 直接复制了 birthDay 对象的引用,而不是它的值。我们很多时候是想让复制后的两个对象是完全独立的,这就需要深复制了。
深复制怎么用呢?其实很简单,将 BirthDay 类也标记为可复制的,并重写 clone() 方法,然后在 Person 类中的 clone() 方法中调用 BirthDay 类的 clone() 方法,实现 birthDay 对象的独立克隆操作,修正后的示例如下:
class BirthDay implements Cloneable {
String year;
String month;
String day;
public BirthDay(String year, String month, String day) {
this.year = year;
this.month = month;
this.day = day;
}
public BirthDay() {
}
@Override /*BirthDay 类也重写 clone() 方法 */
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String toString() {
return this.year + "-" + this.month + "-" + this.day;
}
}
class Person implements Cloneable {
String name;
int age;
BirthDay birthDay;
public Person() {
}
public Person(String name, int age, BirthDay birthDay) {
this.name = name;
this.age = age;
this.birthDay = birthDay;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person newPerson = (Person) super.clone();
if (this.birthDay != null)
newPerson.birthDay = (BirthDay) this.birthDay.clone(); /*调用 birthDay 对象的 clone() 方法,创建 birthDay 对象的独立副本 */
return newPerson;
}
public void getPersonInfo() {
System.out.println("Name: "+this.name+" Age:"+this.age + " Birthday: " + this.birthDay);
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Person pA = new Person("张三", 20, new BirthDay("1990", "01", "01"));
System.out.println("pA 的信息:");
pA.getPersonInfo();
Person pB = (Person) pA.clone();
System.out.println("克隆后 pB 的信息:");
pB.getPersonInfo();
System.out.println("修改 pA 中 birthDay 属性");
pA.birthDay.year = "2000";
System.out.println("修改 pA 后 pB 的信息:");
pB.getPersonInfo();
System.out.println("修改 pA 后 pA 的信息:");
pA.getPersonInfo();
}
}
运行结果如下:
pA 的信息:
Name: 张三 Age:20 Birthday: 1990-01-01
克隆后 pB 的信息:
Name: 张三 Age:20 Birthday: 1990-01-01
修改 pA 中 birthDay 属性
修改 pA 后 pB 的信息:
Name: 张三 Age:20 Birthday: 1990-01-01
修改 pA 后 pA 的信息:
Name: 张三 Age:20 Birthday: 2000-01-01
可以看出,副本 pB 和原本 pA 已经是相互独立的了。
需要注意的问题
1、不要搞很复杂的深复制,例如一层套一层的对象引用,这样会造成很大的资源开销。
2、当一个类作为一个类库被其他类使用时,最好将该类标记为可复制的,防止其他类在导入该类时,想要使用克隆功能却抛出异常。
参考链接:
How to avoid traps and correctly override methods from java.lang.Object
本作品由 sunriseydy 采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
文章内容如未说明均为原创,欢迎转载,但请注明原作者(sunriseydy)和原文链接(https://blog.sunriseydy.top/technology/java/java-clone/)
部分来自互联网的文章,如有侵权,请联系我,24小时内删除,谢谢
感谢您的支持,SunriseYDY 会继续努力的!



打开支付宝扫一扫,即可进行扫码打赏哦
日出一点一 | 在探索的路上永不止步