为什么要用clone()方法去克隆对象,而不是直接用赋值号=
在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的 我们用代码来解释使用 = 克隆对象
public class Student{
private String name;
private int age;
private Address address;
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;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Student(String name, int age, Address address) {
super();
this.name = name;
this.age = age;
this.address = address;
}
public Student() {
super();
}
@Override
public String toString() {
return "[" + name + "*******" + age + "*******" + address + "]";
}
public class Address{
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Address(String address) {
super();
this.address = address;
}
@Override
public String toString() {
return " [" + address + "]";
}
public class CloneTest {
public static void main(String[] args) {
Student stu1 = new Student("哼哼",22,new Address("上海"));
Student stu2 = stu1;
System.out.println("直接复制没有修改的");
System.out.println(stu1);
System.out.println(stu2);
stu2.setName("小哈");
stu2.setAge(20);
System.out.println("修改stu2后");
System.out.println(stu1);
System.out.println(stu2);
}
}
输出结果是
直接复制没有修改的
[哼哼*******22******* [上海]]
[哼哼*******22******* [上海]]
修改stu2后
[小哈*******20******* [上海]]
[小哈*******20******* [上海]]
我们可以看出,当我们修改stu2后,stu1的值也发生了一样的改变,这显然不是我们想要的结果。造成这种的原因是:
原因出在Student stu2 = stu1; 这一句。该语句的作用是将stu1的引用赋值给stu2,这样,stu1和stu2指向内存堆中同一个对象。
那么,怎么能干干净净清清楚楚地复制一个对象呢。在 Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求有很多途径,
使用 Object的clone() 克隆对象
这个分为浅克隆(ShallowClone)和深克隆(DeepClone)。
在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。
浅克隆
1、被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
2,重写clone()方法。
代码实现
package cn.bzu.look.dao;
public class Student implements Cloneable{
private String name;
private int age;
private Address address;
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;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public Student(String name, int age, Address address) {
super();
this.name = name;
this.age = age;
this.address = address;
}
public Student() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "[" + name + "*******" + age + "*******" + address + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student stu = (Student) super.clone();//浅复制
return stu;
}
}
public class Address{
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Address(String address) {
super();
this.address = address;
}
@Override
public String toString() {
return " [" + address + "]";
}
}
public class CloneTest {
public static void main(String[] args) {
Address arr = new Address("北京");
Student stu3 = new Student("哼哼", 22, arr);
try {
System.out.println(stu3);
Student stu4 = (Student) stu3.clone();
System.out.println("两个对象是否是同一个对象" + (stu3 == stu4));
System.out.println("两个对象是都属于同一个类:" + stu3.getClass().equals(stu4.getClass()));
stu3.setAge(34);
stu3.getAddress().setAddress("上海");
System.out.println("修改stu3后");
System.out.println(stu3);
System.out.println(stu4);
} catch (CloneNotSupportedException e) { // TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出结果是
[哼哼*******22******* [北京]]
两个对象是否是同一个对象false
两个对象是都属于同一个类:true
修改stu3后
[哼哼*******34******* [上海]]
[哼哼*******22******* [上海]]
从输出结果中我们可以看出同过clone方法克隆出来的对象只是原有对象的副本,是不同的两个对象,但是他们属于同一个类。
可以很明显的发现当我们修改stu3的年龄时,stu4 的年龄没有变化,但是修改stu3的地址时,stu4的地址也发生了变化,这肯定不是我们想要的,这是为什么呢?
在浅克隆中,如果原型对象的成员变量是基本类型时,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的基本类型的成员变量,而引用类型的成员对象并没有复制
通过深克隆可以解决这个问题
深克隆
要想实现 Address的深克隆,首先让Address类实现 Cloneable 接口,重写clone方法。
//实现Cloneable接口
public class Address implements Cloneable{
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Address(String address) {
super();
this.address = address;
}
@Override
public String toString() {
return " [" + address+ "]";
}
//重写clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//在Student类的clone方法里添加一行代码:stu.address = address.clone();
public class Student implements Cloneable{
//注意这里的clone()方法和浅克隆的区别
@Override
protected Object clone() throws CloneNotSupportedException {
Student stu = (Student) super.clone();
stu.address = (Address) address.clone();
return stu;
}
}
public class CloneTest {
public static void main(String[] args) {
Address arr = new Address("北京");
Student stu3 = new Student("哼哼", 22, arr);
try {
System.out.println(stu3);
Student stu4 = (Student) stu3.clone();
System.out.println("两个对象是否是同一个对象" + (stu3 == stu4));
System.out.println("两个对象是都属于同一个类:" + stu3.getClass().equals(stu4.getClass()));
stu3.setAge(34);
stu3.getAddress().setAddress("上海");
System.out.println("修改stu3后");
System.out.println(stu3);
System.out.println(stu4);
} catch (CloneNotSupportedException e) { // TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出结果是
[哼哼*******22******* [北京]]
两个对象是否是同一个对象false
两个对象是都属于同一个类:true
修改stu3后
[哼哼*******34******* [上海]]
[哼哼*******22******* [北京]]
这里我们发现修改stu3后,只有stu3发现了改变,这就是深克隆的作用,也达到了我们的预期效果。
在深克隆中,无论原型对象的成员变量是基本类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。