假如想复制一个简单变量。很简单:
int apples = 5;
int pears = apples;
不仅int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。
但是如果你复制的是一个对象,情况就复杂了。
假设说我是一个beginner,我会这样写:
class Student {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = stu1;
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
}
}
结果:
学生1:12345
学生2:12345
这里自定义学生类,该类只有number字段。
我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)
再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,
难道真的是这样吗?
我们试着改变stu2实例的number字段,再打印结果看看:
stu2.setNumber(54321);
System.out.println("学生1:" + stu1.getNumber()); // 学生1:54321
System.out.println("学生2:" + stu2.getNumber()); // 学生2:54321
为什么改变学生2的学号,学生1的学号也发生变化?
原因出在(stu2 = stu1) 这一句。该语句是将stu1的引用赋值给stu2,
这样,stu1和stu2指向内存堆中同一个对象。如图:
那么,怎样才能达到复制一个对象呢?
是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。
在Java中所有的类都是继承自Java语言包中的Object类的,查看它的源码,发现里面有一个访问限定符为protected的方法clone():
/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;
仔细一看,它还是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序运行在JVM虚拟机上,要想访问比较底层与操作系统相关的就没办法,只能由靠近操作系统的语言来实现。
- 第一次声明保证克隆对象将有单独的内存地址分配。
- 第二次声明表明,原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
- 第三声明表明,原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
要想对一个对象进行复制,就需对clone方法覆盖。
为什么要克隆?
为什么需要克隆对象?直接new一个对象不行吗?
答案是:克隆的对象可能包含一些已修改过的属性,而new出来的对象的属性都是初始化时的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法。
那把这个对象的临时属性一个个赋值给新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,通过上面的源码都发现clone是一个native方法,就是快,在底层实现。
提个醒,我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍指向同一个对象。
而通过clone方法赋值的对象跟原来的对象是同时独立存在的。
如何实现克隆
介绍下两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。
Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。
一般步骤是(浅克隆):
1. 被复制的类需实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
下面对上面那个方法进行改造:
class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber()); // 学生1:12345
System.out.println("学生2:" + stu2.getNumber()); // 学生2:12345
stu2.setNumber(54321);
System.out.println("学生1:" + stu1.getNumber()); // 学生1:12345
System.out.println("学生2:" + stu2.getNumber()); // 学生2:54321
}
}
如果还不相信这两个对象不是同一个对象,可以看看这一句:
System.out.println(stu1 == stu2); // false
上面被称为浅克隆。
还有一种复杂的深度复制:
我们在学生类里再加一个Address类。
class Address {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
乍一看没问题,真的是这样吗?在main方法中改变addr实例的地址。
addr.setAdd("西湖区");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:西湖区
这就奇怪了,怎么两个学生的地址都改变了?
原因是浅复制只复制addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
所以,为了达到真正的复制对象,而不是纯粹引用复制。需要将Address类可复制化,并且修改clone方法,完整代码如下:
class Address implements Cloneable {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address addr = null;
try{
addr = (Address)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone(); //浅复制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr = (Address)addr.clone(); //深度复制
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
addr.setAdd("西湖区");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:杭州市