Java程序语言和环境的设计目的是为了解决现代程序设计上的问题。它最初是一项较大规模『消费者电子产品先进软件发展专案』的一部份,该专案目的是为了发展小型、可靠、可移植、分布式、实时的嵌入式系统。我们在专案一开始原决定采用C++,不过碰到一些问题。最初,那只是编译器技术方面的问题,可是后来我们遭遇更多困难,且发现最好的解决方式就是更换编程语言。
(1) Java
Java是一种简单、面向对象、分布式、解释、健 壮、安全、结构中立、可移植、高效能、多线程、动态的语言。描述系统特性的方法之一就是把一堆高效能的术 语摆在一起。一如上述,我们也使用一组标准的术语来描述Java。以下我们将解释这些术语在 Java上代表的意义,以及我们试图解决的问题是哪些。『首先,我们要介绍阿基米得公司(Archimedes Inc.);它是我们为了方便说明Java功能而虚构的一家软件公司。阿基米得从事有关基本物理教学 用途的软件开发,其软体设计目的是要与使用者产生交互关系,不仅提供一如传统教科书的文字 与插图,而且也包括一组软件实验工作室,供使用者进行实验和模拟真实世界的物理行为。例 如,它最基本的一项实验允许学生组成杠杆和滑轮,然后观察它们的物理作用。在以下的论述 中,我们将借由这些实验和阿基米得设计
师的旁白,以说明Java语言概念。』
(2) 简单
我们想建立一种真正简单的系统,它允许使用者 轻易地设计程序而不需要像现今的标准程序一样接受深奥的训练。目前大多数程序设计师都采用 C语言,而面向对象程序设计师则大多使用C++。因此,即使我们发现C++并非适合我们,我 们在Java的设计上还是尽可能让它与C++相近,以确保系统更容易被理解。Java删除许多极少被使用、不容易理解和令人混淆 的C++功能,这些功能在我们的使用经验中只能带来麻烦而非效益。删除的功能主要包括运算符 重载(operator overloading)、多重继承(inheritance)以及广泛的自动强迫同型(automatic coercions); 重载是指以一个辨识元参照多重项目,Java语言也提供重载函数,不过它重载的对象是方法(method)而非变量或运算符。我们增加自动内存垃圾收集(auto garbage collection) 功能,因此简化Java程序工作,不过同时也让系统变得稍复杂一些。储存管理(storage management)是使C与C++ 应用程序变得复杂的常见的一项原因,即关于内存的分配与释放。Java语言的自动垃圾收集功能(周期性地 释放未被使用的内存)不仅简化了程序设计工作,而且能大幅度减少小错误(bugs)数量。『阿基米得设计师们原想花时间思考杠杆与滑轮原 理,但是后来却发现大部份时间都浪费在世俗的程序工作上。事实上,他们的核心专业知识在于 教学而非程序设计。这些程序工作中最复杂的部份就是从他们那二万行程序码中,找出那些地方 浪费了内存。』 确保Java『简单化』的另一特性在于『小而单 纯』。Java的目标之一是要协助开发能独立地在小型机器上顺利执行的软件。Java的基本解释器 (interpreter)和对象类(class)支援约占4OKB,而若增加基本的标准程序库和线程(thread)支援 (它 实际上是一整自含式的微核心) 需增加175K。体积小对于在嵌入式的系统中的使用是很重要的,并因 此Java可以通过网络轻易下载。
(3) 面向对象
这是在业界被过度渲染的术语之一。不过,面向 对象是一项很强的设计功能,因为它促成明确的接口定义,并允许发展者建立可重复使用的『软 件IC』。简单的说,面向对象设计是一种以数据(对象) 及其接口为重心的程序设计技术。我们以木匠比例;一位『面向对象』木匠最重视的是他想制作的椅子,其次才是他用来制做椅子的工具。相反的,一位『非面向对象』木匠是把主要的心思放在工具上。面向对象设计也是定义程序模块如何『即插即用』的机制。Java的面向对象设施实质上就是C++,并包含 Objective C的一些延伸,以提供更有动态的解决方案。『阿基米得设计师在他们的模拟式中设计许多 东西,包括绳子与橡皮筋等。他们初期的C版本产品是一个非常庞大的系统,因为他们必须个别地设计软件来描述绳子和橡皮筋。当以面向对象方式重新设计应用程序时,他们发现可以定义一个基本对象来代表绳子与橡皮筋两者之间的共同部份,然后绳子与橡皮筋即定义成基本对象类型的子对象类。其后,当他们有需要增加链结时就能非常轻易地建立起来,因为他们可以将这些链结建立在先前已设计妥当的对象之上,而不需要整个重新设计新对象模拟。』
(4) 分布式
Java拥有广泛的例程库(routine library)能轻易地处 理TCP/IP协议,例如HTTP与FTP等。这使得在Java中比在C或C++中更容易创建网络连接。Java应用程 序可以借由URL在通过网络开启和存取对象,就如同存取一个本地(local)文件系统一样简单。 『阿基米得设计师最初把他们的程序建立在CD-ROM上。不过他们想把一些交互式教学游戏程序 概念放在下一个产品内。例如,他们想让不同电脑上的学生一起建立一台模拟机器供大家学习。 不过,他们评估过的所有网络系统都过于复杂且要求深奥的软件工程专业能力。于是,他们只好 放弃这个概念。』
(5) 健壮
Java的目标是要协助发展者建立各方面都必须 可靠的程序。Java强调在设计初期即检查可能存在的问题,其后则执行动态(runtime)检查,并排除 容易出现错误的条件。强类型(strongly typed)语言例如C++的优点是允许 在编译时进行深入的检查,以便提前发现错误。不幸的是,C++承袭了C在编译检查(compile-time checking)时的一些漏洞;C的检查较为松散,特别是在方法/过程声明方面。我们在Java中要求 声明,但并不支持C风格的隐性声明。链结器(linker)了解类型系统并重复执行许多 已由编译器完成的类型检查,以避免出现版本不匹配问题。Java与C/C++之间最大的不同点之一在于Java拥有 一种指针(pointer)模型,能排除发生内存被覆盖和毁损数据之可能性。Java不采用指针算 术法,而是提供真正的阵列。这允许程序执行下标检查;再者,它也不可能发生借由对象 类型转换将一个任意整数转成指针的情形。『阿基米得设计师的应用程序在C执行时基本上相 当快速。不过他们的软件开发日程一再落后,因为总有清理不完的小错误逃过他们的检查。他们 面对许多麻烦,包括内存分配失败、版本不一致、接口不匹配等。C让他们能在程序码中增加一些 巧妙的设计,然而却必须因此付出时间代价以求确保程序品质。由于错误清不干净,因此他们的软件推出第一版之后就得忙着写修补程序。』Java虽不能排除品质确认问题,不过它让这道程 序变得简单许多。许多动态语言例如Lisp、TCL与Smalltalk等,通 常被用来制作原型程序(prototyping)。它们在这方面成功的理由是因为它们都非常健壮(robust),让程序设计师不再怕处理内存问题,因为他们不须担心内存失败。Java即拥有这项特性。Java 程序员相对地可以不害怕处理内存问题,因为在Java中不存在指针,Java程序不可能意外覆盖一 片内存缓冲区的末尾。Java程序也不可能非法访问内存,但这些在C或C++中都有可能发生。动态语言适合开发原型程序发展的理由之一,就 是它们不会在发展初期就要求你拟定明确的决策。Java刚好相反,它强迫你明确地做选择。这些选择伴随许多辅助:你可以编写方法调用(method invocations),而如果你有某些错误的地 方,你将会在编译时获得通知,而不须担心方法调用上的错误。
(6) 安全
Java设计目的是要供使用于网络/分布式运算环 境。为此,Java非常强调安全性,以确保建立无病毒且不会被侵入的系统。Java的验证技术是以公钥 (public-key)加密法为基础。『健壮性』与『安全』之间存在一种很强的相互 作用关系。例如,指针语意的改变,让应用程式不可能伪造对象结构存取权,或存取它们在对 象中没有存取权的私有数据。这等于关起大门,阻绝大多数病毒活动。『有人为阿基米得的PC版软件写了一个有趣的修 补程序,然后将它贴到一个主要的电子布告栏上面流通。由于这支修补程序很容易取得而且又能 为系统增加一些有趣的功能,于是很多人都下载使用。这个程序未经阿基米得设计师检验过,不 过它用起来似乎还不错。直到四月一日那天,好几千名使用者发现他们小孩使用的教学软件突然 跳出几张不堪入目的图片。不用多加说明,阿基
米得设计师们虽不用为这个意外负起责任,但是 他们还是必须设法控制那个修补程序造成的破坏。』
(7) 结构中立性
Java的设计目标是要支援网络应用程序。一般而 言,网络是由许多不同的系统构成,包括各种CPU与操作系统结构。为了让Java应用程序能够 在网络上任何地方执行,其编译器会产出一种具备结构中立性(architecture neutral)的目标文件格 式。编译后的程序码可以在提供Java runtime系统的多种不同处理器上面执行。这不仅对网络应用很有帮助,而且也很适合单一 系统软件流通。在目前的个人电脑市场上,应用程序发展者必须为他们的程式分别编写IBM PC 和Apple Macintosh相容版本。现在,PC市场正(透过Windows/NT)分散成许多CPU结构,而 Apple则从68000转向PowerPC,这些事实使得我们几乎不可能设计出能在所有平台上执行的软体。Java允许同一版本的应用程序在所有平台执行。Java编译器是借由产出与某一电脑结构无关的字节代码指令,以达到上述功能。它们能轻易地在任何机器上解释,并且动态地转换成原生模式 的机器码。『阿基米得是一家小公司。他们从设计PC软件起 家,因为那是最大的市场。经过一段时日之后,他们成长至规模够大的公司,因此有能力为 Macintosh平台开发软件,不过那确实是一项很大的工程,而且投资报酬并非真的很好。他们无力 负担将软件移植到PowerPC Macintosh或MIPS。NT机器所涉及的人力与成本。当第一波潮流来临 时,他们未能捉住机会,而竞争者即趁虚而入。』
(8) 可移植性
结构中立性是确保程序可移植的最重要部份,不 过除此之外还有很多条件必须配合。和C与C++不同的是,Java规范中并无任何『结构相依性』的陈述存在。它指定基本数据类型的大小,以及其算术运算元之执行行为。例如,"int" 代表一 个有符号的二进制补码32比特整数,而"float"代表一个32比特IEEE 754浮点数。这些选择在 今天的环境很适用,因为几乎所有CPU都具备这些特性。程序库属于系统的一部份,它定义了一些可移植 的程序接口。例如,它包括一个抽象的Windows类,并且提供了类在Unix、Windows和 Macintosh平台上的实现。Java系统本身具备相当好的移植性。编译器以Java 写成,而runtime程序使用ANSI C,并有一个实质上与POSIX相容的移植疆界(portability boundary)。"
(9) 解释
Java解释器(interpreter)可以直接在任何已移植解 释器的机器上解释、执行Java字节代码,不需存储。再者,由于其链结比较倾向于逐步增量与轻量过程, 因此发展程序更快、更精密。由于编译期间的信息属于字节代码资料流的一部 份,因此可以在运行期间携带更多的信息。这正是链结器类型检查的基础,它也让程序更容易执行除错。『阿基米得设计师花很多时间等候程序编译和链 结。他们也花很多时间追踪许多无法感测到的错误,因为有些更改过的原始档案未能完成编译程 序 (虽然他们使用了make公用程序) 而导致版本不一致。同时还要追踪一些在程序中许多声明不 一致的程序。于是,他们的软件发展日程又延诱
几个月。』
(10) 高效能
虽然解释过的字节代码性能已相当不错,不过有 些情形下还是要求程序达到更高执行效能。字节代码可以动态地(runtime)为执行应用程序的 特定CPU解释成机器码。这对于习惯使用一般编译器与动态载入器(loader)的设计者而言,有点类似将最终的机器码产生器放到动态载入器之内。字节代码格式在设计上即顾及机器码的产生,因 此实际的机器码产生程序相当简单。产出的机器码是有效的,编译器自动分配寄存器,而 在产出字节代码时也会进行一些优化。我们以解释码在一台Sun Microsystem SPARC Station 10上执行时,达到每秒三十万个方法调用(method calls)速率。字节代码转换 至机器码的速度性能,几乎和原生模式的C或C++没有两样。『当阿基米得公司刚成立时,他们 用Smalltalk设计原型程序。这协助该公司获得投资者赞助,不过那并没有真正的帮助他们生产产 品:为了让他们的模拟程序跑得够快并符合精简系统要求,他们只好用C重新设计。』
(11) 多线程
在真实世界中,许多事情同时发生在我们身边。 多线程(multithreading)是一种应用程序设计法。不幸的是,要设计一个一次同时处理许多事 件的程序,比设计传统单一线程C与C++程序来得复杂许多。Java拥有一组复杂的同步化基本单元,它们 是以广泛使用的C.A.R. Hoare监视器与条件变量图为基础。将这些概念溶合到语言之后,它们即变得 很容易使用且更为健壮。这项溶合方式大部份来自Xerox的Cedar/Mesa系统。多线程的其他效益包括更好的交互式回应能力 与实时执行行为。然而这会受到底层平台的限制:独立执行的Java运行环境有着很好的实时执行行 为,而若在其他系统例如Unix、Windows、Macintosh或Windows NT等之上执行则由于底层 平台的原因实时反应性将会受到影响。『阿基米得的模拟程序一次同时执行许多项实 验:拉动绳子、转动轮子和 「 杆,同时还追踪使用者输入。由于他们必须将这些模拟全部设计在一个单一线程的程序格式内,因此所有同时发生的动作 (不论彼此之问是否有关系) 必须用人工方式予以混合。使用一个事件回路可以让情况更清楚一些,不过那仍旧是一团混乱,系统变得很脆弱而难以理解。他们从整个网络撷取数据,不过最初他们只能一次抓一个区块,这种串行的网络通讯非常缓慢。当他们转移到多线程模式之后,网络通讯问题即迎刃而解。』
(13) 动态
从许多方面而言,Java是一种比C或C++更具动态 特性的语言。它在设计上强调为进化中的运算环境提供支援。例如,C++在生产环境中的主要问题之一在于该 程序码在一般设计上引起的负作用。如果甲公司设计一个对象类程序库,而乙公司采购后它并 放到自己的产品中使用,然后如甲公司更改其程式库并提供新的版本,那么乙公司将几乎可以确定必须重新编译和重新流通他们的新软件。当最终用户分别向甲乙两家公司购买软件 (例如甲 是作业系统厂商,而乙是应用程序厂商) ,那么问题就产生了。例如,如果甲公司为其程序库供应一种升级版 本,那么乙公司的所有软件都将必须修改。虽然C++可以避免此一问题,不过那极为困难,而且它也意味着不能直接使用该语言的任何面向对象功能。『阿基米得采用3DPC公司提供的面向对象图形程 式库建立他们的产品。3DPC后来推出新
版本的 图形程序库,并且有数家电脑制造商将它搭配在 新机器上出货。采购这些新机器的阿基米得客户很失望地发现他们的旧软件不能继续使用。 (在 真实世界中,这种情形只发生在Unix系统上。在PC领域里,3DPC将永远不会推出这样的程序库,因为他们更改和使用C++面向对象功能的能力受到极大的限制。 )』
Java是在稍后的阶段为模块与模块之间建立这些 连接,因此完全避免了这些问题,并能更直接地运用面向对象设计体系。程序库可以自由地增加 新方法和实例(instance)变量,而不会对它们的用户产生任何效应。Java能了解由Objective C引用过来的接口概念。 简单的说,接口就是规范一组与对象相对应的方法,但对象如何实现这些方法则留待解决。一个 类实现一个接口是要提供这个接口所包含的所有方法的实现。以此相反,派生子类则从父类继承 了一组方法以及它们的实现。一个Java类可以实行多个接口,但只能从一个父类继承。接口告诉连接对象它可以做什么而非怎么做,使得它在代码上更具有灵活性和可复用性。对象类有一种运行(runtime)表示法:它有一种类称 为Class,其内容包括runtime class定义。在C或C++程序中,如果你有一个指针指向一个对 向,但你不知道该对象的类型为何,那么你将没有办法找出它。然而,在Java中要根据runtime类 型信息寻找是很直截了当的。因为,在compile-time和run-time时都会检查数据类型转换,所以 你在Java中可以信任这种转换。另一方面,C与C++的编译器则只是相信你已做了正确的处理。此外,它也可能从一个包含名称的字串查找一个 对象类别定义。这意味着你可以演算一个数据类型名称,然后轻易地以动态方式链结到执行系统。『阿基米得为扩充营收源,希望让他们的产品能 利用新的 入式模块来扩充系统。这种扩充以前在PC上是可能做到的,只是很少实现。他们必 须增加一些新程序设计员,因为那些工作很复杂,而且也增加许多除错问题。』
(14)总结
Java语言提供一种强有力的工具支援程序设计员。 Java让程序设计变得更容易,因为它属于面向对象语言,而且提供了自动的内存垃圾收集功 能。再者,由于Java码具备结构中立性,因此其应用程序成为非同质性运算环境 (例如Internet) 的理想方案。