2.6 构建一个Java程序

正式构建自己的第一个Java程序前,还有几个问题需要注意。

1 名字的可见性

在所有程序设计语言里,一个不可避免的问题是对名字或名称的控制。假设您在程序的某个模块里使用了一个名字,而另一名程序员在另一个模块里使用了相同的名字。此时,如何区分两个名字,并防止两个名字互相冲突呢?这个问题在C语言里特别突出。因为程序未提供很好的名字管理方法。C的类(即Java类的基础)嵌套使用类里的函数,使其不至于同其他类里的嵌套函数名冲突。然而,C仍然允许使用全局数据以及全局函数,所以仍然难以避免冲突。为解决这个问题,C++用额外的关键字引入了“命名空间”的概念。

由于采用全新的机制,所以Java能完全避免这些问题。为了给一个库生成明确的名字,采用了与Internet域名类似的名字。事实上,Java的设计者鼓励程序员反转使用自己的Internet域名,因为它们肯定是独一无二的。由于我的域名是BruceEckel.com,所以我的实用工具库就可命名为com.bruceeckel.utility.foibles。反转了域名后,可将点号想象成子目录。

在Java 1.0和Java 1.1中,域扩展名com,edu,org,net等都约定为大写形式。所以库的样子就变成:COM.bruceeckel.utility.foibles。然而,在Java 1.2的开发过程中,设计者发现这样做会造成一些问题。所以目前的整个软件包都以小写字母为标准。

Java的这种特殊机制意味着所有文件都自动存在于自己的命名空间里。而且一个文件里的每个类都自动获得一个独一无二的标识符(当然,一个文件里的类名必须是唯一的)。所以不必学习特殊的语言知识来解决这个问题——语言本身已帮我们照顾到这一点。

2 使用其他组件

一旦要在自己的程序里使用一个预先定义好的类,编译器就必须知道如何找到它。当然,这个类可能就在发出调用的那个相同的源码文件里。如果是那种情况,只需简单地使用这个类即可——即使它直到文件的后面仍未得到定义。Java消除了“向前引用”的问题,所以不要关心这些事情。

但假若那个类位于其他文件里呢?您或许认为编译器应该足够“联盟”,可以自行发现它。但实情并非如此。假设我们想使用一个具有特定名称的类,但那个类的定义位于多个文件里。或者更糟,假设我们准备写一个程序,但在创建它的时候,却向自己的库加入了一个新类,它与现有某个类的名字发生了冲突。

为解决这个问题,必须消除所有潜在的、纠缠不清的情况。为达到这个目的,要用import关键字准确告诉Java编译器我们希望的类是什么。import的作用是指示编译器导入一个“包”——或者说一个“类库”(在其他语言里,可将“库”想象成一系列函数、数据以及类的集合。但请记住,Java的所有代码都必须写入一个类中)。

大多数时候,我们直接采用来自标准Java库的组件(部件)即可,它们是与编译器配套提供的。使用这些组件时,没有必要关心冗长的保留域名;举个例子来说,只需象下面这样写一行代码即可:

import java.util.Vector;

它的作用是告诉编译器我们想使用Java的 Vector 类。然而,util包含了数量众多的类,我们有时希望使用其中的几个,同时不想全部明确地声明它们。为达到这个目的,可使用“*”通配符。如下所示:

import java.util.*;

需导入一系列类时,采用的通常是这个办法。应尽量避免一个一个地导入类。

3 static关键字

通常,我们创建类时会指出那个类的对象的外观与行为。除非用 new 创建那个类的一个对象,否则实际上并未得到任何东西。只有执行了 new 后,才会正式生成数据存储空间,并可使用相应的方法。

但在两种特殊的情形下,上述方法并不堪用。一种情形是只想用一个存储区域来保存一个特定的数据——无论要创建多少个对象,甚至根本不创建对象。另一种情形是我们需要一个特殊的方法,它没有与这个类的任何对象关联。也就是说,即使没有创建对象,也需要一个能调用的方法。为满足这两方面的要求,可使用 static(静态)关键字。一旦将什么东西设为static,数据或方法就不会同那个类的任何对象实例联系到一起。所以尽管从未创建那个类的一个对象,仍能调用一个static方法,或访问一些static数据。而在这之前,对于非static数据和方法,我们必须创建一个对象,并用那个对象访问数据或方法。这是由于非static数据和方法必须知道它们操作的具体对象。当然,在正式使用前,由于static方法不需要创建任何对象,所以它们不可简单地调用其他那些成员,同时不引用一个已命名的对象,从而直接访问非static成员或方法(因为非static成员和方法必须同一个特定的对象关联到一起)。 有些面向对象的语言使用了“类数据”和“类方法”这两个术语。它们意味着数据和方法只是为作为一个整体的类而存在的,并不是为那个类的任何特定对象。有时,您会在其他一些Java书刊里发现这样的称呼。

为了将数据成员或方法设为static,只需在定义前置和这个关键字即可。例如,下述代码能生成一个static数据成员,并对其初始化:

class StaticTest {
static int i = 47;
}

现在,尽管我们制作了两个StaticTest对象,但它们仍然只占据StaticTest.i的一个存储空间。这两个对象都共享同样的i。请考察下述代码:

StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();

此时,无论st1.i还是st2.i都有同样的值47,因为它们引用的是同样的内存区域。

有两个办法可引用一个static变量。正如上面展示的那样,可通过一个对象命名它,如st2.i。亦可直接用它的类名引用,而这在非静态成员里是行不通的(最好用这个办法引用static变量,因为它强调了那个变量的“静态”本质)。

StaticTest.i++;

其中,++运算符会使变量增值。此时,无论st1.i还是st2.i的值都是48。

类似的逻辑也适用于静态方法。既可象对其他任何方法那样通过一个对象引用静态方法,亦可用特殊的语法格式“类名.方法()”加以引用。静态方法的定义是类似的:

class StaticFun {
static void incr() { StaticTest.i++; }
}

从中可看出,StaticFun 的方法 incr() 使静态数据i增值。通过对象,可用典型的方法调用 incr()

StaticFun sf = new StaticFun();
sf.incr();

或者,由于 incr() 是一种静态方法,所以可通过它的类直接调用:

StaticFun.incr();

尽管是“静态”的,但只要应用于一个数据成员,就会明确改变数据的创建方式(一个类一个成员,以及每个对象一个非静态成员)。若应用于一个方法,就没有那么戏剧化了。对方法来说,static一项重要的用途就是帮助我们在不必创建对象的前提下调用那个方法。正如以后会看到的那样,这一点是至关重要的——特别是在定义程序运行入口方法 main() 的时候。

和其他任何方法一样,static方法也能创建自己类型的命名对象。所以经常把static方法作为一个“领头羊”使用,用它生成一系列自己类型的“实例”。