猪哥 发表于 2009-3-17 08:14:25

JAVA中的指针,引用及对象的clone

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操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。
页: [1]
查看完整版本: JAVA中的指针,引用及对象的clone