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();
    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代码文件,所以这些类在任何时候都可直接使用。在这个列表里,可发现 SystemRuntime,我们在 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() 是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对象。如果将一个数值“加”到一个字串身上,会发生什么情况呢?同我们一样,编译器也会意识到这个问题,并魔术般地调用一个方法,将那个数值(intfloat等等)转换成字串。经这样处理后,它们当然能利用加号“加”到一起。这种“自动类型转换”亦划入“运算符过载”处理的范畴。

许多Java著作都在热烈地辩论“运算符过载”(C++的一项特性)是否有用。目前就是反对它的一个好例子!然而,这最多只能算编译器(程序)的问题,而且只是对String对象而言。对于自己编写的任何源代码,都不可能使运算符“过载”。

通过为 Runtime 类调用 getRuntime() 方法,main() 的第五行创建了一个Runtime对象。返回的则是指向一个Runtime对象的句柄。而且,我们不必关心它是一个静态对象,还是由 new 命令创建的一个对象。这是由于我们不必为清除工作负责,可以大模大样地使用对象。正如显示的那样,Runtime可告诉我们与内存使用有关的信息。