51
返回类型 方法名( /* 自变量列
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
*/ ) {/* 方法主体 */}
返回类型是指调用方法之后返回的数值类型。显然,方法名的作用是对具体的方法进行标识和引用。自变量
列表列出了想传递给方法的信息类型和名称。
Java的方法只能作为类的一部分创建。只能针对某个对象调用一个方法(注释③),而且那个对象必须能够
执行那个方法调用。若试图为一个对象调用错误的方法,就会在编译期得到一条出错消息。为一个对象调用
方法时,需要先列出对象的名字,在后面跟上一个句点,再跟上方法名以及它的参数列表。亦即“对象名.方
法名(自变量1,自变量2,自变量3...)。举个例子来说,假设我们有一个方法名叫f(),它没有自变量,返
回的是类型为int的一个值。那么,假设有一个名为a的对象,可为其调用方法f(),则代码如下:
int x = a.f();
返回值的类型必须兼容x的类型。
象这样调用一个方法的行动通常叫作“向对象发送一条消息”。在上面的例子中,消息是f(),而对象是a。
面向对象的程序设计通常简单地归纳为“向对象发送消息”。
③:正如马上就要学到的那样,“静态”方法可针对类调用,毋需一个对象。
2.5.1 自变量列表
自变量列表
规定
关于下班后关闭电源的规定党章中关于入党时间的规定公务员考核规定下载规定办法文件下载宁波关于闷顶的规定
了我们传送给方法的是什么信息。正如大家或许已猜到的那样,这些信息——如同Java内其
他任何东西——采用的都是对象的形式。因此,我们必须在自变量列表里指定要传递的对象类型,以及每个
对象的名字。正如在Java其他地方处理对象时一样,我们实际传递的是“句柄”(注释④)。然而,句柄的
类型必须正确。倘若希望自变量是一个“字串”,那么传递的必须是一个字串。
④:对于前面提及的“特殊”数据类型boolean,char,byte,short,int,long,,float以及double来
说是一个例外。但在传递对象时,通常都是指传递指向对象的句柄。
下面让我们考虑将一个字串作为自变量使用的方法。下面列出的是定义代码,必须将它置于一个类定义里,
否则无法编译:
int storage(String s) {
return s.length() * 2;
}
这个方法告诉我们需要多少字节才能容纳一个特定字串里的信息(字串里的每个字符都是16位,或者说2个
字节、长整数,以便提供对Unicode字符的支持)。自变量的类型为String,而且叫作s。一旦将s传递给
方法,就可将它当作其他对象一样处理(可向其发送消息)。在这里,我们调用的是length()方法,它是
String的方法之一。该方法返回的是一个字串里的字符数。
通过上面的例子,也可以了解return关键字的运用。它主要做两件事情。首先,它意味着“离开方法,我已
完工了”。其次,假设方法生成了一个值,则那个值紧接在return语句的后面。在这种情况下,返回值是通
过计算表达式“s.length()*2”而产生的。
可按自己的愿望返回任意类型,但倘若不想返回任何东西,就可指示方法返回void(空)。下面列出一些例
子。
boolean flag() { return true; }
float naturalLogBase() { return 2.718; }
void nothing() { return; }
void nothing2() {}
若返回类型为void,则return关键字唯一的作用就是退出方法。所以一旦抵达方法末尾,该关键字便不需
要了。可在任何地方从一个方法返回。但假设已指定了一种非void的返回类型,那么无论从何地返回,编译
器都会确保我们返回的是正确的类型。
到此为止,大家或许已得到了这样的一个印象:一个程序只是一系列对象的集合,它们的方法将其他对象作
52
为自己的自变量使用,而且将消息发给那些对象。这种说法大体正确,但通过以后的学习,大家还会知道如
何在一个方法里作出决策,做一些更细致的基层工作。至于这一章,只需理解消息传送就足够了。
2.6 构建 Java程序
正式构建自己的第一个Java程序前,还有几个问题需要注意。
2.6.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.6.2 使用其他组件
一旦要在自己的程序里使用一个预先定义好的类,编译器就必须知道如何找到它。当然,这个类可能就在发
出调用的那个相同的源码文件里。如果是那种情况,只需简单地使用这个类即可——即使它直到文件的后面
仍未得到定义。Java消除了“向前引用”的问题,所以不要关心这些事情。
但假若那个类位于其他文件里呢?您或许认为编译器应该足够“联盟”,可以自行发现它。但实情并非如
此。假设我们想使用一个具有特定名称的类,但那个类的定义位于多个文件里。或者更糟,假设我们准备写
一个程序,但在创建它的时候,却向自己的库加入了一个新类,它与现有某个类的名字发生了冲突。
为解决这个问题,必须消除所有潜在的、纠缠不清的情况。为达到这个目的,要用import关键字准确告诉
Java编译器我们希望的类是什么。import的作用是指示编译器导入一个“包”——或者说一个“类库”(在
其他语言里,可将“库”想象成一系列函数、数据以及类的集合。但请记住,Java的所有代码都必须写入一
个类中)。
大多数时候,我们直接采用来自标准Java库的组件(部件)即可,它们是与编译器配套提供的。使用这些组
件时,没有必要关心冗长的保留域名;举个例子来说,只需象下面这样写一行代码即可:
import java.util.Vector;
它的作用是告诉编译器我们想使用Java的Vector类。然而,util包含了数量众多的类,我们有时希望使用
其中的几个,同时不想全部明确地声明它们。为达到这个目的,可使用“*”通配符。如下所示:
import java.util.*;
需导入一系列类时,采用的通常是这个办法。应尽量避免一个一个地导入类。
2.6.3 static关键字
通常,我们创建类时会指出那个类的对象的外观与行为。除非用new创建那个类的一个对象,否则实际上并
未得到任何东西。只有执行了new后,才会正式生成数据存储空间,并可使用相应的方法。
但在两种特殊的情形下,上述方法并不堪用。一种情形是只想用一个存储区域来保存一个特定的数据——无
论要创建多少个对象,甚至根本不创建对象。另一种情形是我们需要一个特殊的方法,它没有与这个类的任
何对象关联。也就是说,即使没有创建对象,也需要一个能调用的方法。为满足这两方面的要求,可使用
static(静态)关键字。一旦将什么东西设为static,数据或方法就不会同那个类的任何对象实例联系到一
起。所以尽管从未创建那个类的一个对象,仍能调用一个static方法,或访问一些static数据。而在这之
53
前,对于非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方法作为一个“领头
羊”使用,用它生成一系列自己类型的“实例”。
2.7 我们的第一个 Java程序
最后,让我们正式编一个程序(注释⑤)。它能打印出与当前运行的系统有关的资料,并利用了来自Java标
准库的System对象的多种方法。注意这里引入了一种额外的注释样式:“//”。它表示到本行结束前的所有
内容都是注释:
// Property.java
import java.util.*;
public class Property {
public static void main(String[] args) {
System.out.println(new Date());
Properties p = System.getProperties();
54
p.list(System.out);
System.out.println("--- Memory Usage:");
Runtime rt = Runtime.getRuntime();
System.out.println("Total Memory = "
+ rt.totalMemory()
+ " Free Memory = "
+ rt.freeMemory());
}
}
⑤:在某些编程环境里,程序会在屏幕上一切而过,甚至没机会看到结果。可将下面这段代码置于main()的
末尾,用它暂停输出:
try {
Thread.currentThread().sleep(5 * 1000);
} catch(InterruptedException e) {}
}
它的作用是暂停输出5秒钟。这段代码涉及的一些概念要到本书后面才会讲到。所以目前不必深究,只知道
它是让程序暂停的一个技巧便可。
在每个程序文件的开头,都必须放置一个import语句,导入那个文件的代码里要用到的所有额外的类。注意
我们说它们是“额外”的,因为一个特殊的类库会自动导入每个Java文件:java.lang。启动您的Web浏览
器,查看由Sun提供的用户文档(如果尚未从http://www.java.sun.com下载,或用其他方式安装了Java文
档,请立即下载)。在packages.html文件里,可找到Java配套提供的所有类库名称。请选择其中的
java.lang。在“Class Index”下面,可找到属于那个库的全部类的列表。由于java.lang默认进入每个
Java代码文件,所以这些类在任何时候都可直接使用。在这个列表里,可发现System和Runtime,我们在
Property.java里用到了它们。java.lang里没有列出Date类,所以必须导入另一个类库才能使用它。如果
不清楚一个特定的类在哪个类库里,或者想检视所有的类,可在Java用户文档里选择“Class Hierarchy”
(类分级结构)。在Web浏览器中,虽然要花不短的时间来建立这个结构,但可清楚找到与Java配套提供的
每一个类。随后,可用浏览器的“查找”(Find)功能搜索关键字“Date”。经这样处理后,可发现我们的
搜索目标以java.util.Date的形式列出。我们终于知道它位于util库里,所以必须导入java.util.*;否
则便不能使用Date。
观察packages.html文档最开头的部分(我已将其设为自己的默认起始页),请选择java.lang,再选
System。这时可看到System类有几个字段。若选择out,就可知道它是一个static PrintStream对象。由
于它是“静态”的,所以不需要我们创建任何东西。out对象肯定是3,所以只需直接用它即可。我们能对这
个out对象做的事情由它的类型决定:PrintStream。PrintStream在说明文字中以一个超链接的形式列出,
这一点做得非常方便。所以假若单击那个链接,就可看到能够为PrintStream调用的所有方法。方法的数量
不少,本书后面会详细介绍。就目前来说,我们感兴趣的只有println()。它的意思是“把我给你的东西打
印到控制台,并用一个新行结束”。所以在任何Java程序中,一旦要把某些内容打印到控制台,就可条件反
射地写上System.out.println("内容")。
类名与文件是一样的。若象现在这样创建一个独立的程序,文件中的一个类必须与文件同名(如果没这样
做,编译器会及时作出反应)。类里必须包含一个名为main()的方法,形式如下:
public static void main(String[] args) {
其中,关键字“public”意味着方法可由外部世界调用(第5章会详细解释)。main()的自变量是包含了
String对象的一个数组。args不会在本程序中用到,但需要在这个地方列出,因为它们保存了在命令行调用
的自变量。
程序的第一行非常有趣:
System.out.println(new Date());
请观察它的自变量:创建Date对象唯一的目的就是将它的值发送给println()。一旦这个语句执行完毕,
Date就不再需要。随之而来的“垃圾收集器”会发现这一情况,并在任何可能的时候将其回收。事实上,我
们没太大的必要关心“清除”的细节。
第二行调用了System.getProperties()。若用Web浏览器查看联机用户文档,就可知道getProperties()是
55
System类的一个static方法。由于它是“静态”的,所以不必创建任何对象便可调用该方法。无论是否存
在该类的一个对象,static方法随时都可使用。调用getProperties()时,它会将系统属性作为Properties
类的一个对象生成(注意Properties是“属性”的意思)。随后的的句柄保存在一个名为p的Properties
句柄里。在第三行,大家可看到Properties对象有一个名为list()的方法,它将自己的全部内容都发给一
个我们作为自变量传递的PrintStream对象。
main()的第四和第六行是典型的打印语句。注意为了打印多个String值,用加号(+)分隔它们即可。然
而,也要在这里注意一些奇怪的事情。在String对象中使用时,加号并不代表真正的“相加”。处理字串
时,我们通常不必考虑“+”的任何特殊含义。但是,Java的String类要受一种名为“运算符过载”的机制
的制约。也就是说,只有在随同String对象使用时,加号才会产生与其他任何地方不同的表现。对于字串,
它的意思是“连接这两个字串”。
但事情到此并未结束。请观察下述语句:
System.out.println("Total Memory = "
+ rt.totalMemory()
+ " Free Memory = "
+ rt.freeMemory());
其中,totalMemory()和freeMemory()返回的是数值,并非String对象。如果将一个数值“加”到一个字串
身上,会发生什么情况呢?同我们一样,编译器也会意识到这个问题,并魔术般地调用一个方法,将那个数
值(int,float等等)转换成字串。经这样处理后,它们当然能利用加号“加”到一起。这种“自动类型转
换”亦划入“运算符过载”处理的范畴。
许多Java著作都在热烈地辩论“运算符过载”(C++的一项特性)是否有用。目前就是反对它的一个好例
子!然而,这最多只能算编译器(程序)的问题,而且只是对String对象而言。对于自己编写的任何源代
码,都不可能使运算符“过载”。
通过为Runtime类调用getRuntime()方法,main()的第五行创建了一个Runtime对象。返回的则是指向一个
Runtime对象的句柄。而且,我们不必关心它是一个静态对象,还是由new命令创建的一个对象。这是由于
我们不必为清除工作负责,可以大模大样地使用对象。正如显示的那样,Runtime可告诉我们与内存使用有
关的信息。
2.8 注释和嵌入文档
Java里有两种类型的注释。第一种是传统的、C语言风格的注释,是从C++继承而来的。这些注释用一个
“/*”起头,随后是注释内容,并可跨越多行,最后用一个“*/”结束。注意许多程序员在连续注释内容的
每一行都用一个“*”开头,所以经常能看到象下面这样的内容:
/* 这是
* 一段注释,
* 它跨越了多个行
*/
但请记住,进行编译时,/*和*/之间的所有东西都会被忽略,所以上述注释与下面这段注释并没有什么不
同:
/* 这是一段注释,
它跨越了多个行 */
第二种类型的注释也起源于C++。这种注释叫作“单行注释”,以一个“//”起头,表示这一行的所有内容
都是注释。这种类型的注释更常用,因为它书写时更方便。没有必要在键盘上寻找“/”,再寻找“*”(只
需按同样的键两次),而且不必在注释结尾时加一个结束标记。下面便是这类注释的一个例子:
// 这是一条单行注释
56
2.8.1 注释文档
对于Java语言,最体贴的一项设计就是它并没有打算让人们为了写程序而写程序——人们也需要考虑程序的
文档化问题。对于程序的文档化,最大的问题莫过于对文档的维护。若文档与代码分离,那么每次改变代码
后都要改变文档,这无疑会变成相当麻烦的一件事情。解决的方法看起来似乎很简单:将代码同文档“链
接”起来。为达到这个目的,最简单的方法是将所有内容都置于同一个文件。然而,为使一切都整齐划一,
还必须使用一种特殊的注释语法,以便标记出特殊的文档;另外还需要一个工具,用于提取这些注释,并按
有价值的形式将其展现出来。这些都是Java必须做到的。
用于提取注释的工具叫作javadoc。它采用了部分来自Java编译器的技术,查找我们置入程序的特殊注释标
记。它不仅提取由这些标记指示的信息,也将毗邻注释的类名或方法名提取出来。这样一来,我们就可用最
轻的工作量,生成十分专业的程序文档。
javadoc输出的是一个HTML文件,可用自己的Web浏览器查看。该工具允许我们创建和管理单个源文件,并
生动生成有用的文档。由于有了jvadoc,所以我们能够用标准的方法创建文档。而且由于它非常方便,所以
我们能轻松获得所有Java库的文档。
2.8.2 具体语法
所有javadoc命令都只能出现于“/**”注释中。但和平常一样,注释结束于一个“*/”。主要通过两种方式
来使用javadoc:嵌入的HTML,或使用“文档标记”。其中,“文档标记”(Doc tags)是一些以“@”开头
的命令,置于注释行的起始处(但前导的“*”会被忽略)。
有三种类型的注释文档,它们对应于位于注释后面的元素:类、变量或者方法。也就是说,一个类注释正好
位于一个类定义之前;变量注释正好位于变量定义之前;而一个方法定义正好位于一个方法定义的前面。如
下面这个简单的例子所示:
/** 一个类注释 */
public class docTest {
/** 一个变量注释 */
public int i;
/** 一个方法注释 */
public void f() {}
}
注意javadoc只能为public(公共)和protected(受保护)成员处理注释文档。“private”(私有)和
“友好”(详见5章)成员的注释会被忽略,我们看不到任何输出(也可以用-private标记包括private成
员)。这样做是有道理的,因为只有public和protected成员才可在文件之外使用,这是客户程序员的希
望。然而,所有类注释都会包含到输出结果里。
上述代码的输出是一个HTML文件,它与其他Java文档具有相同的标准格式。因此,用户会非常熟悉这种格
式,可在您设计的类中方便地“漫游”。设计程序时,请务必考虑输入上述代码,用javadoc处理一下,观
看最终HTML文件的效果如何。
2.8.3 嵌入HTML
javadoc将HTML命令传递给最终生成的HTML文档。这便使我们能够充分利用HTML的巨大威力。当然,我们
的最终动机是格式化代码,不是为了哗众取宠。下面列出一个例子:
/**
*
* System.out.println(new Date());
*
*/
亦可象在其他Web文档里那样运用HTML,对普通文本进行格式化,使其更具条理、更加美观:
/**
57
* 您
甚至可以插入一个列表:
*
* - 项目一
*
- 项目二
*
- 项目三
*
*/
注意在文档注释中,位于一行最开头的星号会被javadoc丢弃。同时丢弃的还有前导空格。javadoc会对所
有内容进行格式化,使其与标准的文档外观相符。不要将
或
这样的标题当作嵌入HTML使用,因为
javadoc会插入自己的标题,我们给出的标题会与之冲撞。
所有类型的注释文档——类、变量和方法——都支持嵌入HTML。
2.8.4 @see:引用其他类
所有三种类型的注释文档都可包含@see标记,它允许我们引用其他类里的文档。对于这个标记,javadoc会
生成相应的HTML,将其直接链接到其他文档。格式如下:
@see 类名
@see 完整类名
@see 完整类名#方法名
每一格式都会在生成的文档里自动加入一个超链接的“See Also”(参见)条目。注意javadoc不会检查我
们指定的超链接,不会验证它们是否有效。
2.8.5 类文档标记
随同嵌入HTML和@see引用,类文档还可以包括用于版本信息以及作者姓名的标记。类文档亦可用于“接
口”目的(本书后面会详细解释)。
1. @version
格式如下:
@version 版本信息
其中,“版本信息”代表任何适合作为版本说明的资料。若在javadoc命令行使用了“-version”标记,就
会从生成的HTML文档里提取出版本信息。
2. @author
格式如下:
@author 作者信息
其中,“作者信息”包括您的姓名、电子函件地址或者其他任何适宜的资料。若在javadoc命令行使用了“-
author”标记,就会专门从生成的HTML文档里提取出作者信息。
可为一系列作者使用多个这样的标记,但它们必须连续放置。全部作者信息会一起存入最终HTML代码的单独
一个段落里。
2.8.6 变量文档标记
变量文档只能包括嵌入的HTML以及@see引用。
2.8.7 方法文档标记
除嵌入HTML和@see引用之外,方法还允许使用针对参数、返回值以及违例的文档标记。
1. @param
格式如下:
58
@param 参数名 说明
其中,“参数名”是指参数列表内的标识符,而“说明”代表一些可延续到后续行内的说明文字。一旦遇到
一个新文档标记,就认为前一个说明结束。可使用任意数量的说明,每个参数一个。
2. @return
格式如下:
@return 说明
其中,“说明”是指返回值的含义。它可延续到后面的行内。
3. @exception
有关“违例”(Exception)的详细情况,我们会在第9章讲述。简言之,它们是一些特殊的对象,若某个方
法失败,就可将它们“扔出”对象。调用一个方法时,尽管只有一个违例对象出现,但一些特殊的方法也许
能产生任意数量的、不同类型的违例。所有这些违例都需要说明。所以,违例标记的格式如下:
@exception 完整类名 说明
其中,“完整类名”明确指定了一个违例类的名字,它是在其他某个地方定义好的。而“说明”(同样可以
延续到下面的行)告诉我们为什么这种特殊类型的违例会在方法调用中出现。
4. @deprecated
这是Java 1.1的新特性。该标记用于指出一些旧功能已由改进过的新功能取代。该标记的作用是建议用户不
必再使用一种特定的功能,因为未来改版时可能摒弃这一功能。若将一个方法标记为@deprecated,则使用该
方法时会收到编译器的警告。
2.8.8 文档示例
下面还是我们的第一个Java程序,只不过已加入了完整的文档注释:
//: Property.java
import java.util.*;
/** The first Thinking in Java example program.
* Lists system information on current machine.
* @author Bruce Eckel
* @author http://www.BruceEckel.com
* @version 1.0
*/
public class Property {
/** Sole entry point to class & application
* @param args array of string arguments
* @return No return value
* @exception exceptions No exceptions thrown
*/
public static void main(String[] args) {
System.out.println(new Date());
Properties p = System.getProperties();
p.list(System.out);
System.out.println("--- Memory Usage:");
Runtime rt = Runtime.getRuntime();
System.out.println("Total Memory = "
+ rt.totalMemory()
+ " Free Memory = "
+ rt.freeMemory());
}
59
} ///:~
第一行:
//: Property.java
采用了我自己的方法:将一个“:”作为特殊的记号,指出这是包含了源文件名字的一个注释行。最后一行也
用这样的一条注释结尾,它标志着源代码清单的结束。这样一来,可将代码从本书的正文中方便地提取出
来,并用一个编译器检查。这方面的细节在第17章讲述。
2.9 编码样式
一个非正式的Java编程标准是大写一个类名的首字母。若类名由几个单词构成,那么把它们紧靠到一起(也
就是说,不要用下划线来分隔名字)。此外,每个嵌入单词的首字母都采用大写形式。例如:
class AllTheColorsOfTheRainbow { // ...}
对于其他几乎所有内容:方法、字段(成员变量)以及对象句柄名称,可接受的样式与类样式差不多,只是
标识符的第一个字母采用小写。例如:
class AllTheColorsOfTheRainbow {
int anIntegerRepresentingColors;
void changeTheHueOfTheColor(int newHue) {
// ...
}
// ...
}
当然,要注意用户也必须键入所有这些长名字,而且不能输错。
2.10 总结
通过本章的学习,大家已接触了足够多的Java编程知识,已知道如何自行编写一个简单的程序。此外,对语
言的总体情况以及一些基本思想也有了一定程度的认识。然而,本章所有例子的模式都是单线形式的“这样
做,再那样做,然后再做另一些事情”。如果想让程序作出一项选择,又该如何设计呢?例如,“假如这样
做的结果是红色,就那样做;如果不是,就做另一些事情”。对于这种基本的编程方法,下一章会详细说明
在Java里是如何实现的。
2.11 练习
(1) 参照本章的第一个例子,创建一个“Hello,World”程序,在屏幕上简单地显示这句话。注意在自己的
类里只需一个方法(“main”方法会在程序启动时执行)。记住要把它设为static形式,并置入自变量列
表——即使根本不会用到这个列表。用javac编译这个程序,再用java运行它。
(2) 写一个程序,打印出从命令行获取的三个自变量。
(3) 找出Property.java第二个版本的代码,这是一个简单的注释文档示例。请对文件执行javadoc,并在
自己的Web浏览器里观看结果。
(4) 以练习(1)的程序为基础,向其中加入注释文档。利用javadoc,将这个注释文档提取为一个HTML文
件,并用Web浏览器观看。
60
第 3章 控制程序
流程
快递问题件怎么处理流程河南自建厂房流程下载关于规范招聘需求审批流程制作流程表下载邮件下载流程设计
“就象任何有感知的生物一样,程序必须能操纵自己的世界,在执行过程中作出判断与选择。”
在Java里,我们利用运算符操纵对象和数据,并用执行控制语句作出选择。Java是建立在C++基础上的,所
以对C和C++程序员来说,对Java这方面的大多数语句和运算符都应是非常熟悉的。当然,Java也进行了自
己的一些改进与简化工作。
3.1 使用 Java运算符
运算符以一个或多个自变量为基础,可生成一个新值。自变量采用与原始方法调用不同的一种形式,但效果
是相同的。根据以前写程序的经验,运算符的常规概念应该不难理解。
加号(+)、减号和负号(-)、乘号(*)、除号(/)以及等号(=)的用法与其他所有编程语言都是类似
的。
所有运算符都能根据自己的运算对象生成一个值。除此以外,一个运算符可改变运算对象的值,这叫作“副
作用”(Side Effect)。运算符最常见的用途就是修改自己的运算对象,从而产生副作用。但要注意生成的
值亦可由没有副作用的运算符生成。
几乎所有运算符都只能操作“主类型”(Primitives)。唯一的例外是“=”、“==”和“!=”,它们能操作
所有对象(也是对象易令人混淆的一个地方)。除此以外,String类支持“+”和“+=”。
3.1.1 优先级
运算符的优先级决定了存在多个运算符时一个表达式各部分的计算顺序。Java对计算顺序作出了特别的规
定。其中,最简单的规则就是乘法和除法在加法和减法之前完成。程序员经常都会忘记其他优先级规则,所
以应该用括号明确规定计算顺序。例如:
A = X + Y - 2/2 + Z;
为上述表达式加上括号后,就有了一个不同的含义。
A = X + (Y - 2)/(2 + Z);
3.1.2 赋值
赋值是用等号运算符(=)进行的。它的意思是“取得右边的值,把它复制到左边”。右边的值可以是任何常
数、变量或者表达式,只要能产生一个值就行。但左边的值必须是一个明确的、已命名的变量。也就是说,
它必须有一个物理性的空间来保存右边的值。举个例子来说,可将一个常数赋给一个变量(A=4;),但不可
将任何东西赋给一个常数(比如不能4=A)。
对主数据类型的赋值是非常直接的。由于主类型容纳了实际的值,而且并非指向一个对象的句柄,所以在为
其赋值的时候,可将来自一个地方的内容复制到另一个地方。例如,假设为主类型使用“A=B”,那么B处的
内容就复制到A。若接着又修改了A,那么B根本不会受这种修改的影响。作为一名程序员,这应成为自己的
常识。
但在为对象“赋值”的时候,情况却发生了变化。对一个对象进行操作时,我们真正操作的是它的句柄。所
以倘若“从一个对象到另一个对象”赋值,实际就是将句柄从一个地方复制到另一个地方。这意味着假若为
对象使用“C=D”,那么C和D最终都会指向最初只有D才指向的那个对象。下面这个例子将向大家阐示这一
点。
这里有一些题外话。在后面,大家在代码示例里看到的第一个语句将是“package 03”使用的“package”语
句,它代表本书第3章。本书每一章的第一个代码清单都会包含象这样的一个“package”(封装、打包、包
裹)语句,它的作用是为那一章剩余的代码建立章节编号。在第17章,大家会看到第3章的所有代码清单
(除那些有不同封装名称的以外)都会自动置入一个名为c03的子目录里;第4章的代码置入c04;以此类
推。所有这些都是通过第17章展示的CodePackage.java程序实现的;“封装”的基本概念会在第5章进行
详尽的解释。就目前来说,大家只需记住象“package 03”这样的形式只是用于为某一章的代码清单建立相
应的子目录。
为运行程序,必须保证在classpath里包含了我们安装本书源码文件的根目录(那个目录里包含了c02,
c03c,c04等等子目录)。
61
对于Java后续的版本(1.1.4和更高版本),如果您的main()用package语句封装到一个文件里,那么必须
在程序名前面指定完整的包裹名称,否则不能运行程序。在这种情况下,命令行是:
java c03.Assignment
运行位于一个“包裹”里的程序时,随时都要注意这方面的问题。
下面是例子:
//: Assignment.java
// Assignment with objects is a bit tricky
package c03;
class Number {
int i;
}
public class Assignment {
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
n1.i = 9;
n2.i = 47;
System.out.println("1: n1.i: " + n1.i +
", n2.i: " + n2.i);
n1 = n2;
System.out.println("2: n1.i: " + n1.i +
", n2.i: " + n2.i);
n1.i = 27;
System.out.println("3: n1.i: " + n1.i +
", n2.i: " + n2.i);
}
} ///:~
Number类非常简单,它的两个实例(n1和n2)是在main()里创建的。每个Number中的i值都赋予了一个不
同的值。随后,将n2赋给n1,而且n1发生改变。在许多程序设计语言中,我们都希望n1和n2任何时候都
相互独立。但由于我们已赋予了一个句柄,所以下面才是真实的输出:
1: n1.i: 9, n2.i: 47
2: n1.i: 47, n2.i: 47
3: n1.i: 27, n2.i: 27
看来改变n1的同时也改变了n2!这是由于无论n1还是n2都包含了相同的句柄,它指向相同的对象(最初
的句柄位于n1内部,指向容纳了值9的一个对象。在赋值过程中,那个句柄实际已经丢失;它的对象会由
“垃圾收集器”自动清除)。
这种特殊的现象通常也叫作“别名”,是Java操作对象的一种基本方式。但假若不愿意在这种情况下出现别
名,又该怎么操作呢?可放弃赋值,并写入下述代码:
n1.i = n2.i;
这样便可保留两个独立的对象,而不是将n1和n2绑定到相同的对象。但您很快就会意识到,这样做会使对
象内部的字段处理发生混乱,并与标准的面向对象设计准则相悖。由于这并非一个简单的话题,所以留待第
12章详细论述,那一章是专门讨论别名的。其时,大家也会注意到对象的赋值会产生一些令人震惊的效果。
1. 方法调用中的别名处理
将一个对象传递到方法内部时,也会产生别名现象。
//: PassObject.java
// Passing objects to methods can be a bit tricky
62
class Letter {
char c;
}
public class PassObject {
static void f(Letter y) {
y.c = 'z';
}
public static void main(String[] args) {
Letter x = new Letter();
x.c = 'a';
System.out.println("1: x.c: " + x.c);
f(x);
System.out.println("2: x.c: " + x.c);
}
} ///:~
在许多程序设计语言中,f()方法表面上似乎要在方法的作用域内制作自己的自变量Letter y的一个副本。
但同样地,实际传递的是一个句柄。所以下面这个程序行:
y.c = 'z';
实际改变的是f()之外的对象。输出结果如下:
1: x.c: a
2: x.c: z
别名和它的对策是非常复杂的一个问题。尽管必须等至第12章才可获得所有答案,但从现在开始就应加以重
视,以便提早发现它的缺点。
3.1.3 算术运算符
Java的基本算术运算符与其他大多数程序设计语言是相同的。其中包括加号(+)、减号(-)、除号
(/)、乘号(*)以及模数(%,从整数除法中获得余数)。整数除法会直接砍掉小数,而不是进位。
Java也用一种简写形式进行运算,并同时进行赋值操作。这是由等号前的一个运算符标记的,而且对于语言
中的所有运算符都是固定的。例如,为了将4加到变量x,并将结果赋给x,可用:x+=4。
下面这个例子展示了算术运算符的各种用法:
//: MathOps.java
// Demonstrates the mathematical operators
import java.util.*;
public class MathOps {
// Create a shorthand to save typing:
static void prt(String s) {
System.out.println(s);
}
// shorthand to print a string and an int:
static void pInt(String s, int i) {
prt(s + " = " + i);
}
// shorthand to print a string and a float:
static void pFlt(String s, float f) {
prt(s + " = " + f);
}
public static void main(String[] args) {
// Create a random number generator,
63
// seeds with current time by default:
Random rand = new Random();
int i, j, k;
// '%' limits maximum value to 99:
j = rand.nextInt() % 100;
k = rand.nextInt() % 100;
pInt("j",j); pInt("k",k);
i = j + k; pInt("j + k", i);
i = j - k; pInt("j - k", i);
i = k / j; pInt("k / j", i);
i = k * j; pInt("k * j", i);
i = k % j; pInt("k % j", i);
j %= k; pInt("j %= k", j);
// Floating-point number tests:
float u,v,w; // applies to doubles, too
v = rand.nextFloat();
w = rand.nextFloat();
pFlt("v", v); pFlt("w", w);
u = v + w; pFlt("v + w", u);
u = v - w; pFlt("v - w", u);
u = v * w; pFlt("v * w", u);
u = v / w; pFlt("v / w", u);
// the following also works for
// char, byte, short, int, long,
// and double:
u += v; pFlt("u += v", u);
u -= v; pFlt("u -= v", u);
u *= v; pFlt("u *= v", u);
u /= v; pFlt("u /= v", u);
}
} ///:~
我们注意到的第一件事情就是用于打印(显示)的一些快捷方法:prt()方法打印一个String;pInt()先打
印一个String,再打印一个int;而pFlt()先打印一个String,再打印一个float。当然,它们最终都要用
System.out.println()结尾。
为生成数字,程序首先会创建一个Random(随机)对象。由于自变量是在创建过程中传递的,所以Java将
当前时间作为一个“种子值”,由随机数生成器利用。通过Random对象,程序可生成许多不同类型的随机数
字。做法很简单,只需调用不同的方法即可:nextInt(),nextLong(),nextFloat()或者nextDouble()。
若随同随机数生成器的结果使用,模数运算符(%)可将结果限制到运算对象减1的上限(本例是99)之
下。
1. 一元加、减运算符
一元减号(-)和一元加号(+)与二元加号和减号都是相同的运算符。根据表达式的书写形式,编译器会自
动判断使用哪一种。例如下述语句:
x = -a;
它的含义是显然的。编译器能正确识别下述语句:
x = a * -b;
但读者会被搞糊涂,所以最好更明确地写成:
x = a * (-b);
一元减号得到的运算对象的负值。一元加号的含义与一元减号相反,虽然它实际并不做任何事情。
64
3.1.4 自动递增和递减
和C类似,Java提供了丰富的快捷运算方式。这些快捷运算可使代码更清爽,更易录入,也更易读者辨读。
两种很不错的快捷运算方式是递增和递减运算符(常称作“自动递增”和“自动递减”运算符)。其中,递
减运算符是“--”,意为“减少一个单位”;递增运算符是“++”,意为“增加一个单位”。举个例子来
说,假设A是一个int(整数)值,则表达式++A就等价于(A = A + 1)。递增和递减运算符结果生成的是
变量的值。
对每种类型的运算符,都有两个版本可供选用;通常将其称为“前缀版”和“后缀版”。“前递增”表示++
运算符位于变量或表达式的前面;而“后递增”表示++运算符位于变量或表达式的后面。类似地,“前递
减”意味着--运算符位于变量或表达式的前面;而“后递减”意味着--运算符位于变量或表达式的后面。对
于前递增和前递减(如++A或--A),会先执行运算,再生成值。而对于