由于指针不仅会给开发人员带来使用上的不便,而且也是造成程序不稳定的根源,为了消除C/C++语言的这些缺点,Java语言取消了指针的概念,但这只是在Java语言中没有明确提供指针的概念和用法,而实质上每个new语句返回的都是一个指针的引用,只不过在大部分情况下开发人员不需要关心如何去操作这个指针而已。
由于Java取消了指针的概念,因此开发人员在编程过程中往往忽略了对象和引用的区别,示例如下:
class Obj{
public void setStr(String str){
this.str = str;
}
private String str = "default value";
public String toString(){
return str;
}
}
public class TestRef{
private Obj aObj = new Obj();
private int aInt = 0;
public Obj get AObj(){
return aObj;
}
public int getAInt(){
return aInt;
}
public void changeObj(Obj inObj){
inObj.setStr("changed value");
}
public void changeInt(int inInt){
inInt = 1;
}
public static void main(String[] args){
TestRef oRef = new TestRef();
System.out.println("***************引用类型***************");
System.out.println("调用changeObj()前:"+ oRef.getAObj());
oRef.changeObj(oRef.getAObj());
System.out.println("调用changeObj()后:"+ oRef.getAObj());
System.out.println("***************基本数据类型***************");
System.out.println("调用changeInt()前:"+ oRef.getAInt());
oRef.changeInt(oRef.getAInt());
System.out.println("调用changeInt()后:"+ oRef.getAInt());
}
}
程序运行结果为:
***************引用类型***************
调用changeObj()前:default value
调用changeObj()后:changed value
***************基本数据类型***************
调用changeInt()前:0
调用changeInt()后:0
上面两个看似类似的方法却有着不同的运行结果,主要原因是Java在处理基本数据类型(例如int、char、double等)时,都是采用按值传递(传递的是输入参数的复制)的方式执行,除此之外的其他类型都是按引用传递(传递的是对象的一个引用)的方式执行。对象除了在函数调用时是引用传递,在使用“=”赋值时也采用引用传递,示例代码如下:
class Obj{
private int aInt = 0;
public int getAInt(){
return aInt;
}
public void setAInt(int int1){
aInt = int1;
}
public void changeInt(){
this.aInt = 1;
}
}
public class TestRef{
public static void main(String[] args){
Obj a = new Obj();
Obj b = a;
b.changeInt();
System.out.println("a:" + a.getAInt());
System.out.println("b:" + b.getAInt());
}
}
程序运行结果为:
a:1
b:1
在实际编程中,经常会遇到从某个已有的对象A创建出另外一个与A具有相同状态的对象B,并且对B的修改不会影响到A的状态,例如,Prototype(原型)模式中,就需要clone一个对象实例。在Java语言中,仅仅通过简单的赋值操作显然无法达到这个目的,而Java提供了一个简单有效的clone()方法来满足这个需求。
Java中的所有类默认都继承自Object类,而Object类中提供了一个clone()方法。这个方法的作用是返回一个Object对象的复制。这个复制函数返回的是一个新的对象而不是一个引用。以下是使用clone()方法的步骤。
- 实现clone的类首先需要继承Cloneable接口。Cloneable接口实质上是一个标识接口,没有任何接口方法。
- 在类中重写Object类中的clone()方法。
- 在clone方法中调用super.clone()。无论clone类的继承结构是什么,super.clone()都会直接或间接调用java.lang,Object类的clone()方法。
- 把浅复制的引用指向原型对象新的克隆体。
对上面的例子引入clone方法如下:
class Obj implements Cloneable{
private int aInt = 0;
public int getAInt(){
return aInt;
}
public void setAInt(int int1){
aInt = int1;
}
public void changeInt(){
this.aInt = 1;
}
public Object clone(){
Object o = null;
try{
o = (Obj)super.clone();
} catch(CloneNotSupportException e){
e.printStackTrace();
}
return o;
}
}
public class TestRef{
public static void main(String[] args){
Obj a = new Obj();
Obj b = (Obj)a.clone();
b.changeInt();
System.out.println("a:" + a.getAInt());
System.out.println("b:" + b.getAInt());
}
}
程序运行结果为:
a:0
b:1
在C++语言中,当开发人员自定义复制构造函数时,会存在浅复制与深复制之分。Java语言在重载clone()方法时也存在同样的问题,当类中只有一些基本的数据类型时,采用上述方法就可以了,但是当类中包含了一些对象时,就需要用到深复制了,实现方法是在对对象调用clone()方法完成复制后,接着对对象中的非基本类型的属性也调用clone()方法完成深复制,示例如下:
import java.util.Date;
class Obj implements Cloneable{
private Date birth = new Date();
public Date getBirth(){
return birth;
}
public void setBirth(Date birth){
this.birth = birth;
}
public void changeDate(){
this.birth.setMonth(4);
}
public Object clone(){
Obj o = null;
try{
o = (Obj)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
//实现深复制
o.birth = (Date)this.getBirth().clone();
return o;
}
}
public class TestRef{
public static void main(String[] args){
Obj a = new Obj();
Obj b = (Obj)a.clone();
b.changeDate();
System.out.println("a = " + a.getBirth());
System.out.println("b = " + b.getBirth());
}
}
那么在编程时,如何选择使用哪种复制方式呢?首先,检查类有无非基本类型(即对象)的数据成员。若没有,则返回super.clone()即可;若有,确保类中包含的所有的非基本类型的成员变量都实现了深复制。
Object o = super.clone();//先执行浅复制
对每一个对象attr执行以下语句:
o.attr = this.getAttr().clone();
最后返回o。
需要注意的是,clone()方法的保护机制在Object中clone()是被声明为protected的。以User类为例,通过声明为protected,就可以保证只有User类里面才能“克隆”User对象。
参考《Java程序员面试笔试宝典》