用Clonable接口实现对象的克隆——浅拷贝和深拷贝
- 1. 浅拷贝
- 2. 深拷贝
在Object类中提供了clone方法,用来是实现对象的克隆!
1. 浅拷贝
我们首先来尝试用clone方法去克隆一个Person对象
public class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "{" +"name='" + name + '\'' +", age=" + age +'}';}
}
public class Test {public static void main(String[] args) throws CloneNotSupportedException {Person person1 = new Person("zhangsan",10);System.out.println("person1: " + person1);Person person2 = (Person) person1.clone();}
}
这段代码会报错:
报错原因:
大概意思就是clone方法在Object类中是被protected修饰的,主类无法访问到clone方法。
我们打开Object类,找到clone方法,如下:
分析:被protected修饰的方法只能在同一个包或者不同包的子类中使用。在上述代码中,Person类继承了Object类,因此可以在Person类中去访问clone方法,而当想在Test类中通过person1去访问Person类继承下来的clone方法,这是不可行的!因为Test类是不同包的非子类,无法访问!
这可怎么办呀?
第一步: 可以在Person类中重写clone方法,这样就可以在同一包的Test类中去调用clone方法了!
快捷生成重写的clone方法:
生成了如下代码:
@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
重写clone方法后,我们会发现依然会报错,查看报错结果:
意思就是有异常没有去处理
第二步: 我们发现,clone方法里有个异常声明,当去调用clone方法时,需要去处理异常,我们可以将其交给JVM处理,即在main方法处也进行声明!
显然,还是有报错,再看看有什么报错信息
Person类中的clone方法返回的是Object类,而去用一个Person类去接收,这是不可行的!向下转型需要进行强转!
第三步: 向下转型
这时,好像没有报错信息了欸,这时你嘴角开始微微上扬,仿佛看到了曙光!
这时,你开始运行你的代码
怎么又又又报错了?!坚持住!仔细来看报错信息,CloneNotSupportedException,就是不支持克隆的意思。
第四步: 解决办法:实现Cloneable接口
这时,运行就不会报错啦!就成功克隆了person1。
我们再来看看Cloneable接口:
打开后发现,怎么是个空接口嘞?这个空接口有什么用?
答:空接口也叫做标记接口,表示当前的类可以被克隆
这不就恰巧解决了刚刚不支持克隆的问题吗
最后奉上修改后的代码:
public class Person implements Cloneable{public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "{" +"name='" + name + '\'' +", age=" + age +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
public class Test {public static void main(String[] args) throws CloneNotSupportedException{Person person1 = new Person("zhangsan",10);System.out.println("person1: " + person1);Person person2 = (Person)person1.clone();System.out.println("person2: " + person2);}
}
2. 深拷贝
假如,在原来代码基础上,我们再创建一个money对象
class Money {public double money;public Money(double money) {this.money = money;}
}public class Person implements Cloneable{public String name;public int age;public Money m = new Money(9.9);public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", money=" + m.money +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}
public class Test {public static void main(String[] args) throws CloneNotSupportedException{Person person1 = new Person("zhangsan",10);Person person2 = (Person)person1.clone();System.out.println("修改前person1: " + person1);System.out.println("修改前person2: " + person2);person2.m.money = 99.99;System.out.println("修改后person1: " + person1);System.out.println("修改后person2: " + person2);}
}
运行结果:
我们发现:在克隆person1对象后,修改person2中的money属性,本应该不会影响person1中money属性的值,但是运行结果却是影响了。究其根本根本就是浅拷贝导致的,下面来画图演示:
其中绿色表示拷贝后的,我们不难发现,拷贝person1时,只拷贝了person1的属性,而m对象里的money属性并没有进行拷贝。其中,在拷贝person1的属性m时,拷贝的是money的地址。所以,person1和person2的m属性所指是同一个money对象。这也就是为什么在修改person2中m的money属性时,person1中m的money属性也会变化。
那么该如何去解决这个问题呢?答案就是进行深拷贝,将m对象也进行克隆
class Money implements Cloneable{public double money;public Money(double money) {this.money = money;}@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
}public class Person implements Cloneable{public String name;public int age;public Money m = new Money(9.9);public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", money=" + m.money +'}';}@Overrideprotected Object clone() throws CloneNotSupportedException {Person tmp = (Person)super.clone();tmp.m = (Money)this.m.clone();//克隆money属性return tmp;}
}
在Money类中也实现Cloneable接口,重写clone方法;并修改Person类中的clone方法(仔细思考修改后的clone方法)
这时,运行结果:
画图演示当前深拷贝: