2.6 构建一个Java程序
在构建自己的第一个Java程序之前,还必须了解其他一些问题。
2.6.1 名字可见性
名字管理对任何程序设计语言来说,都是一个重要的问题。如果在程序的某个模块里使用了一个名字,而其他人在这个程序的另一个模块里也使用了相同的名字,那么怎样才能区分这两个名字并防止二者互相冲突呢?这个问题在C语言中尤其严重,因为程序往往包含许多难以管理的名字。
C++类(Java类基于此)将函数包于其内,从而避免了与其他类中的函数名相冲突。然而,C++仍允许全局数据和全局函数的存在,所以还是可能发生冲突。为了解决这个问题,C++通过几个关键字引入了命名空间的概念。
Java采用了一种全新的方法来避免上述所有问题。
为了给一个类库生成不与其他名字混淆的名字,Java设计者希望程序员反过来使用自己的Internet域名,因为这样可以保证他们肯定是独一无二的。反转域名后,句点就用来代表子目录的划分。
在 Java 1.0 和 Java 1.1 中,扩展名 com、edu、net 等约定为大写形式。所以上面的库名应该写为 NET.DomainName.utility.foibles。
然而,在Java 2 开发到一半时,设计者们发现这样做会引起一些问题,因此,现在整个包名都小写了。
这种机制意味着所有的文件都能够自动存活于它们自己的名字空间内,而且同一个文件内的每个类都有唯一的标识符——Java语言本身已经解决了这个问题。
2.6.2 运用其他构件
如果想在自己的程序里使用预先定义好的类,那么编译器就必须知道怎么定位它们。当然,这个类可能就在发出调用的那个源文件中;在这种情况下,就可以直接使用这个类——即使这个类在文件的后面才会被定义(Java消除了所谓的“ 向前引用 ” 问题)。
如果那个类位于其他文件中,又会怎样呢?你可能会认为编译器应该有足够的智慧,能够直接找到它的位置,但事实并非如此。想像下面的情况,如果你想使用某个特定名字的类,但其定义却不止一份(假设这些定义各不相同)。更糟糕的是,假设你正在写一个程序,在构建过程中,你想将某个新类添加到类库中,但却与已有的某个类名冲突。
为了解决这个问题,必须消除所有可能的混淆情况。为实现这个目的,可以使用关键字import来准确地告诉编译器你想要的类是什么。
import 指示编译器导入一个包,也就是一个类库(在其它语言中,一个库不仅包含类,还可能包括方法和数据,但是Java中所有的代码都必须写在类里)。
大多时候,我们使用与编译器附在一起的Java标准类库里的构件。有了这些构件,你就不必写一长串的反转域名。
举例来说:
import java.util.ArrayList;
这行代码告诉编译器,你想使用Java的ArrayList类。但是,util包含了数量众多的类,有时你想使用其中的几个,同时又不想明确地逐一声明;那么你很容易使用通配符 “ * ”来表达这个目的:
import java.util.*;
这种一次导入群类的方式比一个一个地导入类的方式更常用。
2.6.3 static 关键字
通常来说,当创建类时,就是在描述那个类的对象的外观和行为。除非用 new 创建那个类的对象,否则,实际上并未获得任何对象。执行 new 来创建对象时,数据存储空间才被分配,其方法才供外界调用。
有两种情形共上述方法是无法解决的:
一种情形是:只想为某特定域分配单一存储空间,而不去考虑究竟要创建多少对象,甚至根本不创建任何对象。
另一种情形是,希望某个方法不与包含它的类的任何对象关联在一起。也就是说,及时没有创建对象,也可以调用这个方法。
通过 static 关键字可以满足这两方面的需要。
当声明一个事物是 static 时,就意味着这个域或方法不会与包含它的那个类的任何对象实例关联在一起。所以,即使从未创建某个类的任何对象,也可以调用 static 方法或访问 其 static 域。通常,你必须创建一个对象,并用它来访问数据或方法。因为非 static 域和方法必须知道它们一起运作的特定对象。
有些面向对象语言采用类数据和类方法两个术语,代表那些数据和方法只是作为整个类,而不是类的某个特定对象而存在的。有时,一些 Java文献里也用到这两个术语。
只需将static关键字放在定义之前,就可以将字段或方法设定为 static。
例如:下面的代码就生成了一个 static字段,并对其进行了初始化:
Class StaticTest {
static int i =55;
}
现在,即使你创建了两个StaticTest对象,StaticTest.i 也只有一份存储空间,这两个对象共享一个i。再看看下面代码:
StaticTest st1= new StaticTest();
StaticTest st2 = new StaticTest();
在这里,str1.i和st2.i指向统一存储空间,因此他们具有相同的 值 55;
引用 static变量有两种方法。
如前例所示,可以通过一个对象去定位它,如st2.i;也可以通过其名直接引用,而这对于非静态成员则不行。
StaticTest.i++;
其中,++运算符对变量进行递加操作。此时,str1.i和str2.i仍具有相同的值 56.
使用类名是引用static变量的首选方式,这不仅是因为它强调了变量的static结构,而且在某些情况下它还未编译器进行优化提供了更好的机会。
类的逻辑也应用于静态方法。既可以像其他方法一样,通过一个对象引用某个静态方法,也可以通过特殊的语法形式ClassName.method()加以引用。定义静态方法的方式也与定义静态变量的方式相似:
Class Incrementable {
static void increment()
{
StaticTest.I++;
}
}
可以看到,Incrementable 的 increment()方法通过++ 运算符将静态数据i 递加。可以采用典型的方式,通过对象来调用 increment():
incrementable sf = new incrementable() ;
sf.increment();
或者,因为increment()是一个静态方法,所以可以通过它的类直接调用;
incrementable.increment();
尽管当 static 作用于某个字段时,肯定会改变数据创建的方式(因为一个static字段对每个类来说都只有一份存储空间,而非static字段则是对每个对象有一个存储空间),但是如果static 作用于某个方法,差别却没有那么大。static方法的一个重要用法就是在不创建任何对象的前提下就可以调用它。正如我们将会看到的那样,这一点对定义main()方法很重要,这个方法是运行一个应用时的入口点。
和其他任何方法一样,static方法可以创建或使用其类型相同的被命名对象,因此,static 方法常常拿来做“ 牧羊人 ”的角色,负责看护或隶属同一类型的实例群。
2.7 第一个Java程序
在每个程序文件的开头,必须声明 import 语句,以便引入在文件代码中需要用到的额外类。
注意,在这里说它们 “ 额外 ”,是因为有一个特定类会自动被导入到每一个Java文件中:java.lang。
由于 java.lang是默认导入到每个Java文件中的,所以它的所有类都可以被直接使用。java.lang里没有Date类,所以必须导入另一个类库才能使用它。若不知道某个特定类在哪个类库里,可在Java文档中选择“Tree”,便可以看到Java配套提供的每一个类。
类的名字必须和文件名相同。如果你像现在这样创建一个独立运行的程序,那么文件中必须存在某个类与该文件同名(否则,编译器会报错),且那个类必须包含一个名为main()的方法,形式如下所示:
public static void main(String[] args){}
其中,public 关键字指这是一个由外部调用的方法。main()方法的参数是一个String对象的数组。在这个程序中并未用到args,但是Java编译器要求必须这样做,因为args要用来存储命令行参数。
2.7.1 编译和运行
要编译、运行这个程序以及本书中的所有的程序,首先必须要有一个Java的开发环境。目前,有相当多的第三方厂商提供开发环境,但是,在本书中,我建设使用的是Sun免费提供的JDK(Java Developer's Kit,Java 开发人员工具包)开发环境。若使用其他的开发系统,请查找该系统的相应文档,以便决定怎样编译和运行程序。
2.8 注释和嵌入是文档
Java里有两种注释风格。一种是传统的C语言风格注释——C++也继承了这种风格。
此种注释以 “/* ”开始,随后是注释内容,并可跨越多行,最后一“*/”结束。注意,许多程序员在连续的注释内容的每一行都以一个“*”开头,所以经常看到像下面的写法:
/* This is a comment
* that continues
* across lines
*/
但请记住,进行编译时,“/*” 和“*/”之间的所有东西都会被忽略,所以上述注释与下面这段注释并没有什么两样:
/* This is a comment that continues across lines */
第二种风格的注释也源于C++。这种注释是“ 单行注释 ”,以一个“//” 起头,知道句末。
这种风格的注释因为书写容易,所以更方便、更常用。
// This is a one-line comment
2.8.1 代码文档撰写的最大问题,大概就是对文档的维护了。如果文档域代码是分离的,那么每次修改代码时,都需要修改响应的文档,这会成为一件相当乏味的事情。解决的方法似乎很简单:将代码通文档“ 链接 ”起来。为达到这个目的,最简单的方法是将所有东西都放在同一个文件内。然而,为实现这一目的,还必须使用一种特殊的注释语法来标记文档;此外还需要一个工具,用于提取那些注释,并将其转换为有用的形式。这正是Java所做的。
2.8.2 语法
所有的javadoc命令都只能在“/**”注释中出现,和通常一样,注释结束语“*/”。
使用jiavadoc的方式有两种:嵌入 Html,或使用 “文档标签”。独立文档标签是一些以“ @ ”字符开头的命令,且要置于注释行的最前面(但是不算前导“*”之后的最前面)。而“行内文档标签”则可以出现在javadoc注释中的任何地方,它们也是以“ @ ”字符开头,但要括在花括号内。
共有三种类型的注释文档,分别对应于注释位置后面的三种元素:
类、域和方法。
也就是说,类注释正好位于类定义之前;域注释正好位于预定义之前;而方法注释也正好位于方法定义的前面。
如下图:
注意 ,javadoc只能为public(公共)和protected(受保护)成员进行文档注释。private(私有)和包内可访问成员的注释会被忽略掉,所以输出结果中看不到它们(不过可以用-private进行标记,以便把private成员的注释也包括在内)。这样做是有道理的,因为只有public和protected成员才能在文件之外被使用,这是客户端程序员所期望的。
上述代码的输出结果是一个HTML文件,它与其他Java文档具有相同的标准格式。
因此,用户会非常熟悉这种格式,从而方便地导航到用户自己设计的类。
所有类型的注释文档——类、域和方法——都支持嵌入式HTML。
2.8.4 一些标签示例:
这里将介绍一些可用于代码文档的javadoc标签。在使用javadoc处理重要事情之前,应该先到JDK文档那里查阅javadoc参考,以学习javadoc的各种不同的使用方法。
1. @see:引用其他类
@see标签允许用户引用其他类的文档。javadoc会在器生成的HTML文件中,通过@see标签链接到其他文档。
格式如下:
2.{@link package.class#member label}
改标签与@see极其相似,只是它用于行内,并且是“ label ”作为超链接文本而不用“ See Also ”。
3.{@docRoot}
该标签产生到文档根目录的相对路径,用于文档树页面的显示超链接。
4.{@inheritDoc}
该标签从当前这个类的最直接的基类中继承相关文档到当前的文档注释中。
5.{version}
该标签的格式如下:
@version version-information
其中,“version-information”可以是任何你认为适合包含在版本说明中的重要信息。如果javadoc命令使用了“-version”标记,那么就从省城HTML文档中特别提取出版本信息。
6.@author
该标签的格式如下:
@author author-information
7.@since
该标签允许你指定程序代码最早使用的版本,可以在HTML Java文档中看到它被用来指定所用的JDK版本的情况。
8.@param
该标签用于方法文档中,形式如下:
@param parameter - name description
9.@return
该标签用于方法文档,格式如下:
@return description
10.@throws
“异常”将在第9章论述,异常的格式如下
@throws fully-qualified-class-name description
11.@deprecated
该标签用于支出一些旧特性已由改进的新特性所取代,建议用户不要在使用这些旧特性,因为在不久的将来它们很可能会被删除。如果使用一个标记为@deprecated的方法,则会引起编译器发布警告。