Clonable接口与深拷贝
克隆对象
考虑:怎样将对象克隆一份?
答案就在本文,我们先给出一步一步的思考过程,然后总结:
首先设置情景:我们有一个
Person类
,我们要在TestClone测试类
中克隆Person类
实例化出来的的对象//Person.java public class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; } } //TestClone.java public class TestClone { public static void main(String[] args) { //测试代码 } }
先在要克隆的对象的类中重写clone
方法
public class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//重写clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
重写后我们写下测试代码如下:
public class Testclone {
public static void main(String[] args) {
Person person1 = new Person("zhangsan", 23);
Person person2 = person1.clone();
}
}
我们发现报错了,这是因为clone()
方法的返回值是Object
类型:
对此,我们要强制类型转换:
我们发现,代码仍然报错:
这是因为一个类对象想被clone()
必须实现Clonable接口
,我们观察一下Clonable接口
:
这是一个空接口,空接口是标记接口,这里表示当前类实例化的对象可以被克隆。我们只需记住要clone
时一定要实现Clonable接口
完成上述操作后还是会报错,我们需要throws CloneNotSupportedException
这样一段代码:
这里涉及到异常
的知识,这里不具体介绍,后续会发文介绍有关Java中异常的知识。
我们直接看代码:
//Person.java
public class Person implements Cloneable {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//重写clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//重写toString方法,方便对象字段的打印
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
//TestClone.java
public class TestClone {
//在main函数的参数列表之后添加了异常捕获
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person("zhangsan", 23);
Person person2 = (Person)person1.clone();
//打印观察效果
System.out.println("克隆参考person1:" + person1);
System.out.println("person2克隆后:" + person2);
}
}
【注意事项总结】
- 在要被克隆的对象的类中重写
clone()
方法 - 要被克隆的对象的类要实现
Clonable接口
- 调用
clone()
的方法的参数列表的后面要加上throws CloneNotSupportedException
处理异常 - 调用
clone()
方法时要对返回值执行强制类型转换,将其转换成要克隆的对象的类型
我们接下来介绍一下克隆操作的内存情况(以上面的Person
为例):
首先,创建person1
对象,在堆区开辟一块空间,person1
指向这块空间。
克隆时,会在堆区开辟一块新的空间,将person1
的成员变量的值给到新空间,然后将这块新空间的地址返回被接收。
深拷贝
深拷贝与浅拷贝只针对引用类型。
我们对上面的情景进一步修改,创建一个
Money类
,在Person类
中增加一个Money类
的对象。
如下代码:
//Money.java
public class Money {
public double money = 10;
}
//Person.java
public class Person implements Cloneable {
public String name;
public int age;
public Money m = new Money();//实例化Money对象成员
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//TestClone.java
public class TestClone {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person("zhangsan", 23);
Person person2 = (Person) person1.clone();
person1.m.money = 100;//仅对person1的money进行修改
System.out.println("person1的money: " + person1.m.money);
System.out.println("person2的money: " + person2.m.money);
}
}
上述代码打印结果是:
我们只对person1
的money
进行修改,为什么person2
的money
也变化了?
这就是浅拷贝的缘故,浅拷贝只复制指向某个对象的地址,而不复制对象本身,新旧对象还是共享同一块内存。
我们给出克隆完成后的内存分布:
我们知道,类就是一种引用类型,存放其指向的对象的地址。
堆上为person1
对象开辟了一块空间,这块空间包含了引用类型m
,堆区为对象m
开辟一块空间,存放money
值
浅拷贝时,堆上会开辟一块空间,然后将person1
在堆上的数据拷贝给新的堆空间,对于引用变量m
,也是只将值拷贝,最后将这块堆空间的地址返回,person2
接收。造成的结果是person1
和person2
的m
指向了同一个Money
对象,所以,通过person1
改变其money
的值会影响到person2
那么,怎么将上述Money
对象也克隆一份呢?
这里就要用到深拷贝,深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象:
分析:我们需要将person1
的m对象
也克隆一份给到person1
的m
代码实现:
-
修改
Money
类 (实现Clonable接口
并重写clone()方法
),让其可克隆//Money.java public class Money implements Cloneable { public double money = 10; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
-
修改
Person
类的clone()
//Person.java public class Person implements Cloneable { public String name; public int age; public Money m = new Money(); public Person(String name, int age) { this.name = name; this.age = age; } //修改 @Override protected Object clone() throws CloneNotSupportedException { Person tmp = (Person) super.clone();//接收克隆后的对象 tmp.m = (Money) this.m.clone();//克隆原对象的m对象并赋值给新对象的m return tmp; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
-
测试
//TestClone.java public class TestClone { public static void main(String[] args) throws CloneNotSupportedException { Person person1 = new Person("zhangsan", 23); Person person2 = (Person) person1.clone(); person1.m.money = 100; System.out.println("person1的money: " + person1.m.money); System.out.println("person2的money: " + person2.m.money); } }
【总结】
- 如果想要克隆的对象的类中含有自定义的引用类型的成员(排除
String
),则必须代码实现深拷贝 - 深拷贝和浅拷贝是通过具体代码实现的,说
clone()
方法是深拷贝或浅拷贝是不准确的
完
下一篇:String类