4.2.1 对象与对象变量
要想使用对象,就必须首先构造对象,并指定其初始状态。然后,对对象应用方法。
在Java程序设计语言中,使用构造器(constructor)构造新实例。构造器是一种特殊的方法,用来构造并初始化对象。下面看一个例子。在标准Java库中包含一个Date类。它的对象将描述一个时间点,例如:“December 31,1999,23:59:59 GMT”。
注释:你可能会感到奇怪:为什么用类描述时间,而不像其他语言那样用一个内置的(built-in)类型?例如,在Visual Basic中有一个内置的date类型,程序员可以采用#6/1/1995#格式指定日期。从表面上看,这似乎很方便,因为程序员使用内置的date类型,而不必为设计类而操心。但实际上,Visual Basic这样设计的适应性如何呢?在有些地区日期表示为月/日/年,而另一些地区表示为日/月/年。语言设计者是否能够预见这些问题呢?如果没有处理好这类问题,语言就有可能陷入混乱,对此感到不满的程序员也会丧失使用这种语言的热情。如果使用类,这些设计任务就交给了类库的设计者。如果类设计的不完善,其他的操作员可以很容易地编写自己的类,以便增强或替代(replace)系统提供的类(作为这个问题的印证:Java的日期类库有些混乱,对它的重新设计正在进行中。请参看http://jcp.org/en/jsr/detail?id=310)。
构造器的名字应该与类名相同。因此Date类的构造器名为Date。要想构造一个Date对象,需要在构造器前面加上new操作符,如下所示:
这个表达式构造了一个新对象。这个对象被初始化为当前的日期和时间。
如果需要的话,也可以将这个对象传递给一个方法:
相反,也可以将一个方法应用于刚刚创建的对象上。Date类中有一个toString方法。这个方法将返回日期的字符串描述。下面的语句可以说明如何将toString方法应用于新构造的Date对象上。
在这两个例子中,构造的对象仅使用了一次。通常,希望构造的对象可以多次使用,因此,需要将对象存放在一个变量中:
图4-3显示了引用新构造的对象变量birthday。
图4-3 创建一个新对象
在对象与对象变量之间存在着一个重要的区别。例如,语句
定义了一个对象变量deadline,它可以引用Date类型的对象。但是,一定要认识到:变量deadline不是一个对象,实际上也没有引用对象。此时,不能将任何Date方法应用于这个变量上。语句
将产生编译错误。
必须首先初始化变量deadline,这里有两个选择。当然,可以用新构造的对象初始化这个变量:
也让这个变量引用一个已存在的对象:
现在,这两个变量引用同一个对象(请参见图4-4)。
图4-4 引用同一个对象的对象变量
一定要认识到:一个对象变量并没有实际包含一个对象,而仅仅引用一个对象。
在Java中,任何对象变量的值都是对存储在另外一个地方的一个对象的引用。new操作符的返回值也是一个引用。下列语句:
有两个部分。表达式new Date()构造了一个Date类型的对象,并且它的值是对新创建对象的引用。这个引用存储在变量deadline中。
可以显式地将对象变量设置为null,表明这个对象变量目前没有引用任何对象。
如果将一个方法应用于一个值为null的对象上,那么就会产生运行错误。
局部变量不会自动地初始化为null,而必须通过调用new或将它们设置为null进行初始化。
C++注释:很多人错误地认为Java对象变量与C++的引用类似。然而,在C++中没有空引用,并且引用不能被赋值。可以将Java的对象变量看作C++的对象指针。例如,
实际上,等同于
一旦理解了这一点,一切问题就迎刃而解了。当然,一个Date*指针只能通过调用new进行初始化。就这一点而言,C++与Java的语法几乎是一样的。
如果把一个变量的值赋给另一个变量,两个变量就指向同一个日期,即它们是同一个对象的指针。在Java中的null引用对应C++中的NULL指针。
所有的Java对象都存储在堆中。当一个对象包含另一个对象变量时,这个变量依然包含着指向另一个堆对象的指针。
在C++中,指针十分令人头疼,并常常导致程序错误。稍不小心就会创建一个错误的指针,或者造成内存溢出。在Java语言中,这些问题都不复存在。如果使用一个没有初始化的指针,运行系统将会产生一个运行时错误,而不是生成一个随机的结果。同时,不必担心内存管理问题,垃圾收集器将会处理相关的事宜。
C++确实做了很大的努力,它通过拷贝型构造器和复制操作符来实现对象的自动拷贝。例如,一个链表(linked list)拷贝的结果将会得到一个新链表,其内容与原始链表相同,但却是一组独立的链接。这使得将同样的拷贝行为内置在类中成为可能。在Java中,必须使用clone方法获得对象的完整拷贝。