主页 PC知识 网管技术 黑客帝国 安全技术 开放系统 程序设计 搜索 技术论坛

 

相关联接
 
RHU本级分类

编程语言
程序代码
WIN/*NIX编程
其他类别
JAVA专区

 
RHU阅读排行
·C语言嵌入式系统编程修炼之背景篇
·在 Java 应用程序中访问USB设备
·Java列表对象的性能分析和测试应用解析
·C语言嵌入式系统编程修炼之屏幕操作
·Java升级篇:ANT十五大最佳实践详解
·Delphi编程将数据库数据快速导入Excel
·Java对象转为String的几种常用方法剖析
·Delphi开发嵌入式IE浏览器监控程序
·详解Java中的指针、引用及对象的clone
·链表的C语言实现之循环链表及双向链表

 
 
RHU最新文章
·利用Visual C++实现系统托盘程序
·探索C++的秘密之详解extern "C"
·编程秘籍:使C语言高效的四大绝招
·C#开发的两个基本编程原则的深入讨论
·超线程多核心下Java多线程编程技术分析
·C++箴言:理解隐式接口和编译期多态
·C++对象布局及多态之虚成员函数调用
·C++对象布局及多态实现之带虚函数的类
·C++对象布局及多态实现探索之内存布局
·C++箴言:考虑可选的虚拟函数的替代方法

 
 
RHU相关搜索









 
 
RHU广而告之

 
 
>您的位置:首页 -> 程序设计 -> 编程语言
详解Java中的指针、引用及对象的clone

作者:RHU-TAC编辑员 来自:RHU网络采集 时间:2005-6-16 双击滚屏 收藏本页 字体:


点击 查看RHU2004全年文章


Java语言的一个优点就是取消了指针的概念,但也导致了许多程序员在编程中常常忽略了对象与引用的区别,本文会试图澄清这一概念。

并且由于Java不能通过简单的赋值来解决对象复制的问题,在开发过程中,也常常要要应用clone()方法来复制对象。本文会让你了解什么是影子clone与深度clone,认识它们的区别、优点及缺点。

看到这个标题,是不是有点困惑:Java语言明确说明取消了指针,因为指针往往是在带来方便的同时也是导致代码不安全的根源,同时也会使程序的变得非常复杂难以理解,滥用指针写成的代码不亚于使用早已臭名昭著的"GOTO"语句。

Java放弃指针的概念绝对是极其明智的。但这只是在Java语言中没有明确的指针定义,实质上每一个new语句返回的都是一个指针的引用,只不过在大多时候Java中不用关心如何操作这个"指针",更不用象在操作C++的指针那样胆战心惊。唯一要多多关心的是在给函数传递对象的时候。如下例程:

package reference; 
class Obj
{ 
String str = "init value"; 
public String toString()
{ 
return str; 
} 
} 
public class ObjRef
{ 
Obj aObj = new Obj(); 
int aInt = 11; 
public void changeObj(Obj inObj)
{ 
inObj.str = "changed value"; 
} 
public void changePri(int inInt)
{ 
inInt = 22; 
} 
public static void
main(String[] args) 
{ 
ObjRef oRef = new ObjRef(); 

System.out.println("Before call
changeObj() method:
" + oRef.aObj); 
oRef.changeObj(oRef.aObj); 
System.out.println
("After call changeObj()
method: " + oRef.aObj); 

System.out.println
("==================Print Primtive================="); 
System.out.println("Before call
changePri() method: " + oRef.aInt); 
oRef.changePri(oRef.aInt); 
System.out.println("After call 
changePri() method: " + oRef.aInt); 

} 
} 

/* RUN RESULT 
Before call changeObj() method:
init value 
After call changeObj() method:
changed value 
==================Print Primtive================= 
Before call changePri() method: 11 
After call changePri() method: 11 

* 
*/


这段代码的主要部分调用了两个很相近的方法,changeObj()和changePri()。唯一不同的是它们一个把对象作为输入参数,另一个把Java中的基本类型int作为输入参数。并且在这两个函数体内部都对输入的参数进行了改动。

看似一样的方法,程序输出的结果却不太一样。changeObj()方法真正的把输入的参数改变了,而changePri()方法对输入的参数没有任何的改变。

从这个例子知道Java对对象和基本的数据类型的处理是不一样的。和C语言一样,当把Java的基本数据类型(如int,char,double等)作为入口参数传给函数体的时候,传入的参数在函数体内部变成了局部变量,这个局部变量是输入参数的一个拷贝,所有的函数体内部的操作都是针对这个拷贝的操作,函数执行结束后,这个局部变量也就完成了它的使命,它影响不到作为输入参数的变量。

这种方式的参数传递被称为"值传递"。而在Java中用对象的作为入口参数的传递则缺省为"引用传递",也就是说仅仅传递了对象的一个"引用",这个"引用"的概念同C语言中的指针引用是一样的。当函数体内部对输入变量改变时,实质上就是在对这个对象的直接操作。

除了在函数传值的时候是"引用传递",在任何用"="向对象变量赋值的时候都是"引用传递"。如:

package reference; 
class PassObj 
{ 
String str = "init value"; 
} 
public class ObjPassvalue 
{ 

public static void
main(String[] args) 
{ 
PassObj objA = new PassObj(); 
PassObj objB = objA; 

objA.str = "changed in objA"; 
System.out.println
("Print objB.str value:
" + objB.str); 
} 
} 
/* RUN RESULT 
Print objB.str value:
changed in objA 
*/



第一句是在内存中生成一个新的PassObj对象,然后把这个PassObj的引用赋给变量objA,第二句是把PassObj对象的引用又赋给了变量objB。此时objA和objB是两个完全一致的变量,以后任何对objA的改变都等同于对objB的改变。

即使明白了Java语言中的"指针"概念也许还会不经意间犯下面的错误。

Hashtable真的能存储对象吗?

看一看下面的很简单的代码,先是声明了一个Hashtable和StringBuffer对象,然后分四次把StriingBuffer对象放入到Hashtable表中,在每次放入之前都对这个StringBuffer对象append()了一些新的字符串:


package reference; 
import java.util.*; 
public class HashtableAdd{ 
public static void
main(String[] args){ 
Hashtable ht = new Hashtable(); 
StringBuffer sb =
new StringBuffer(); 
sb.append("abc,"); 
ht.put("1",sb); 
sb.append("def,"); 
ht.put("2",sb); 
sb.append("mno,"); 
ht.put("3",sb); 
sb.append("xyz."); 
ht.put("4",sb); 

int numObj=0; 
Enumeration it = ht.elements(); 
while(it.hasMoreElements())
{ 
System.out.print("get StringBufffer 
"+(++numObj)+" from Hashtable: "); 
System.out.println(it.nextElement()); 
} 
} 
}



如果你认为输出的结果是:

get StringBufffer 1 
from Hashtable: abc, 
get StringBufffer 2 
from Hashtable: abc,def, 
get StringBufffer 3 
from Hashtable: abc,def,mno, 
get StringBufffer 4 
from Hashtable: abc,def,mno,xyz.



那么你就要回过头再仔细看一看上一个问题了,把对象时作为入口参数传给函数,实质上是传递了对象的引用,向Hashtable传递StringBuffer对象也是只传递了这个StringBuffer对象的引用。

每一次向Hashtable表中put一次StringBuffer,并没有生成新的StringBuffer对象,只是在Hashtable表中又放入了一个指向同一StringBuffer对象的引用而已。

对Hashtable表存储的任何一个StringBuffer对象(更确切的说应该是对象的引用)的改动,实际上都是对同一个"StringBuffer"的改动。

所以Hashtable并不能真正存储能对象,而只能存储对象的引用。也应该知道这条原则对与Hashtable相似的Vector, List, Map, Set等都是一样的。

上面的例程的实际输出的结果是:

/* RUN RESULT 
get StringBufffer 1
from Hashtable: abc,def,mno,xyz. 
get StringBufffer 2 
from Hashtable: abc,def,mno,xyz. 
get StringBufffer 3 
from Hashtable: abc,def,mno,xyz. 
get StringBufffer 4
from Hashtable: abc,def,mno,xyz. 
*/



类,对象与引用

Java最基本的概念就是类,类包括函数和变量。如果想要应用类,就要把类生成对象,这个过程被称作"类的实例化"。有几种方法把类实例化成对象,最常用的就是用"new"操作符。类实例化成对象后,就意味着要在内存中占据一块空间存放实例。

想要对这块空间操作就要应用到对象的引用。引用在Java语言中的体现就是变量,而变量的类型就是这个引用的对象。虽然在语法上可以在生成一个对象后直接调用该对象的函数或变量,如:

new String("Hello NDP")).substring(0,3)
  //RETURN RESULT: Hel



但由于没有相应的引用,对这个对象的使用也只能局限这条语句中了。

产生:引用总是在把对象作参数"传递"的过程中自动发生,不需要人为的产生,也不能人为的控制引用的产生。这个传递包括把对象作为函数的入口参数的情况,也包括用"="进行对象赋值的时候。

范围:只有局部的引用,没有局部的对象。引用在Java语言的体现就是变量,而变量在Java语言中是有范围的,可以是局部的,也可以是全局的。

生存期:程序只能控制引用的生存周期。对象的生存期是由Java控制。用"new Object()"语句生成一个新的对象,是在计算机的内存中声明一块区域存储对象,只有Java的垃圾收集器才能决定在适当的时候回收对象占用的内存。

没有办法阻止对引用的改动。
什么是"clone"?

在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象确定的。

在Java语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单,也是最高效的手段。

Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。

怎样应用clone()方法?

一个很典型的调用clone()代码如下:

class CloneClass 
implements Cloneable
{ 
public int aInt; 
public Object clone()
{ 
CloneClass o = null; 
try{ 
o = (CloneClass)super.clone(); 
}catch(CloneNotSupportedException e)
{ 
e.printStackTrace(); 
} 
return o; 
} 
}



有三个值得注意的地方,一是希望能实现clone功能的CloneClass类实现了Cloneable接口,这个接口属于java.lang包,java.lang包已经被缺省的导入类中,所以不需要写成java.lang.Cloneable。

另一个值得请注意的是重载了clone()方法。最后在clone()方法中调用了super.clone(),这也意味着无论clone类的继承结构是什么样的,super.clone()直接或间接调用了java.lang.Object类的clone()方法。下面再详细的解释一下这几点。

应该说第三点是最重要的,仔细观察一下Object类的clone()一个native方法,native方法的效率一般来说都是远高于java中的非native方法。

这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息赋到新对象中,虽然这也实现了clone功能。对于第二点,也要观察Object类中的clone()还是一个protected属性的方法。

这也意味着如果要应用clone()方法,必须继承Object类,在Java中所有的类是缺省继承Object类的,也就不用关心这点了。

然后重载clone()方法。还有一点要考虑的是为了让其它类能调用这个clone类的clone()方法,重载之后要把clone()方法的属性设置为public。

那么clone类为什么还要实现Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类中clone()方法的,如果clone类没有实现Cloneable接口,并调用了Object的clone()方法(也就是调用了super.Clone()方法),那么Object的clone()方法就会抛出CloneNotSupportedException异常。

以上是clone的最基本的步骤,想要完成一个成功的clone,还要了解什么是"影子clone"和"深度clone"。

什么是影子clone?

下面的例子包含三个类UnCloneA,CloneB,CloneMain。CloneB类包含了一个UnCloneA的实例和一个int类型变量,并且重载clone()方法。CloneMain类初始化UnCloneA类的一个实例b1,然后调用clone()方法生成了一个b1的拷贝b2。最后考察一下b1和b2的输出:

[本文共有 2 页,当前是第 1 页] <<上一页 下一页>>



OVER

[1] [2] 页 RedHyphone.Union 投稿邮箱
[特别声明]:
本站文章大多搜索转载自网络中,如果侵犯了您的权利,请告之我们。本站将立即删除。
本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有。
查看评论】【向上滚屏】【关闭窗口】【 打印
-相关文章
  • openSUSE 11.1 Final - 正式发布
  • [视频]Opera Mini 4.2 正式版发布
  • dll注入系统进程(开源代码)
  • 认知盲区 解惑双网卡双线路DNS解析
  • FlashFXP 简体中文版 3.7.5 Build 1303 Beta[烈火]
  • -文章评论 (关闭)
    ·还没有相关的评论!

    网上大名:
    红旋风网络技术联盟 RHUTech.Union
     
    Copyright © 2000-2007 RedHyphone.Union All Rights Reserved. 红旋风联盟版权所有.皖ICP备05011033号
    中国红旋风网络技术联盟 | www.RedHyphone.net
    Mailto:Redhyphone@gamil.com