Java程序员面试笔试宝典(第2版)
上QQ阅读APP看书,第一时间看更新

1.3 Java中clone方法的作用

由于指针的存在不仅会给开发人员带来不便,同时也是造成程序不稳定的根源之一,为了消除C/C++语言的这些缺点,Java语言取消了指针的概念,但这只是在Java语言中没有明确提供指针的概念与用法,而实质上每个new语句返回的都是一个指针的引用,只不过在大部分情况下开发人员不需要关心如何去操作这个指针而已。

由于Java取消了指针的概念,所以开发人员在编程中往往忽略了对象和引用的区别。如下例所示:

上述代码的输出结果为:

上面两个看似类似的方法却有着不同的运行结果,主要原因是Java在处理基本数据类型(例如int、char、double等)的时候,都是采用按值传递(传递的是输入参数的拷贝)的方式,除此之外的其他类型都是按引用传递(传递的是对象的一个引用)的方式执行。对象除了在函数调用的时候是引用传递,在使用“=”赋值的时候也采用引用传递,示例代码如下:

上述代码的运行结果为:

在实际的编程中,经常会遇到从某个已有的对象A创建出另外一个与A具有相同状态的对象B,并且对B的修改不会影响到A的状态,例如Prototype(原型)模式中,就需要复制(clone)一个对象实例。在Java语言中,仅通过简单的赋值操作显然无法达到这个目的,而Java提供了一个简单且有效的clone方法来满足这个需求。

Java中所有的类默认都继承自Object类,而Object类中提供了一个clone方法。这个方法的作用是返回一个Object对象的拷贝,这个拷贝函数返回的是一个新的对象而不是一个引用。那么怎样使用这个方法呢?以下是使用clone方法的步骤:

1)实现clone的类首先需要继承Cloneable接口。Cloneable接口实质上是一个标识接口,没有任何接口方法。

2)在类中重写Object类中的clone方法。

3)在clone方法中调用super.clone()。无论clone类的继承结构是什么,super.clone()都会直接或间接调用java.lang.Object类的clone()方法。

4)把浅拷贝的引用指向原型对象新的克隆体。

对上面的例子引入clone方法如下:

程序运行结果为:

在C++语言中,当开发人员自定义拷贝构造函数的时候,会存在浅拷贝与深拷贝之分。Java在重载clone方法的时候也存在同样的问题,当类中只有一些基本的数据类型的时候,采用上述方法就可以了,但是当类中包含了一些对象的时候,就需要用到深拷贝了,实现方法是对对象调用clone方法完成深拷贝后,接着对对象中的非基本类型的属性也调用clone方法完成深拷贝。如下例所示:

运行结果为:

那么在编程的时候如何选择使用哪种拷贝方式呢?首先,检查类有无非基本类型(即对象)的数据成员。如果没有,则返回super.clone()即可,如果有,则要确保类中包含的所有非基本类型的成员变量都实现了深拷贝。

引申:

(1)浅拷贝和深拷贝的区别

浅拷贝(Shallow Clone):被复制对象的所有变量都含有与原来对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。

深拷贝(Deep Clone):被复制对象的所有变量都含有与原来对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把复制的对象所引用的对象都复制了一遍。

假如定义如下一个类:

图1-1给出了对这个类的对象进行拷贝时,浅拷贝与深拷贝的区别。

图1-1 深拷贝与浅拷贝的区别

从图1-1可以看出,对于浅拷贝而言,新的对象中的变量i有单独的存储空间,但是对于s而言,只拷贝了它的引用,从而导致新对象与原来的对象中的s指向相同的字符串。而深拷贝则为s指向的字符串单独分配了一块存储空间。

(2)clone()方法的保护机制

clone()方法的保护机制在Object中是被声明为protected的。以User类为例,通过声明为protected,就可以保证只有User类里面才能“克隆”User对象,原理可以参考前面关于public、protected、private的讲解。