跳入JAVA
透明 译
我看到,很多人在问:我是否应该学习JAVA;我看到,很多人在问:JAVA和C++哪个更好?
我想我没有资格回答这些问题。但我想CUJ有资格。本文出自CUJ的import.java.*专栏 ——以CUJ而开设JAVA专栏,这本身已经证明广大C++程序员对JAVA的重视程度。究竟哪个好?好在何处?是否应该学?应该怎样学?应该学什么?……太多的问题。希望本文能起抛砖引玉之效。希望本文的读者能亲自看看这些专家指点,从而形成自己的判断。
本文作者Chuck Allison对程序设计有丰富的经验,并且有多年使用C++的历史。而我也是一个C++的狂热爱好者。因此,我想这篇文章应该很对你——C++开发者——的胃口。
一次JAVA旅游的质量取决于本次旅游的向导。我们是幸运的,Chuck Allison是一个经验丰富的向导,无论对于C++还是对于JAVA都是。
--------------------------------------------------------------------------------
再一次向你问好。“import java.*”欢迎你们——C/C++程序员们——来学习JAVA。在你以前的案例中,你可能有很多理由想要这样做。JAVA远不止是一种增强WEB页面的机制。它是一种用途广泛的面向对象程序设计语言,它建立在对软件工程工业的许多需求之上。当你开始学习JAVA之后,你将成为一个更好的C/C++程序员,因为JAVA的语法与C++非常相似。并且,如果你是一个喜欢面向对象编程原则的C++程序员,你将感到象是回到了自己的家。就如同很多C程序员选择了C++一样,许多C++程序员正在寻找自然转向JAVA的途径。
我将分析JAVA所有的特征:语言、库、以及思想,但是从C/C++的视角。这意味着我不是一个JAVA的盲目信仰者(可能是一个狂热爱好者,但不是一个盲目信仰者:-))。实际上,我对C++的掌握和爱好可能在某些时候让我偏向另一个方向。这也意味着我将可能做一些坏不堪言的事情:我将比较这两种语言并尝试说明一些有用的东西。在这篇文章中,我只提供对JAVA的一个大概的观察,而把具体的教学留在将来的部分中。但首先,我让你看一点背景。
关于“骗局”的一些话
在1984年与Apple MacIntosh的联系中,我第一次听到了“面向对象”这个术语。当我问“面向对象是什么意思”时,他们胡乱说了一通关于屏幕上的图形对象的话。这就是一个神话:图形化用户接口让某些东西变得面向对象。
在20世纪80年代中期,“面向对象”这个术语被滥用,以至于刺穿这个骗局唯一的方法就是:找到一种毫无疑义的面向对象编程语言并使用它。当我在1987年发现LISP时,我开始领悟面向对象的思想,并对“语言对封装、继承和多态的支持”有了第一手的经验。
在那一年的晚些时候,我参加了某个关系数据库管理系统(relational DBMS)厂商的现场销售演示会。我已经非常精通他们的产品,并且我是我们公司内的“专家”。在演示会的过程中我听到这样的说法:“<产品X>是面向对象的。”我跑到前面问那位演示者:“你说<产品X>是面向对象的,这是什么意思?”然后我在整个演示会中再也不说一句话。其后的讨论表明,<产品X>根本不是面向对象的——它只是他们经常说的一种东西:骗局。
你也许知道,70年代的时髦词汇是“结构化设计”,它最终在80年代的工业中得到了很好的应用;80年代的骗局是“面向对象”,而它正是今天的正统。那么,今天的骗局是什么?模式,我猜;以及,是的,JAVA。
基础 vs. 骗局
C++的声望在没有任何推销的情况下如此爆炸性的增长,这是件有趣的事。在1982年,C++的设计者Bjarn Stroustrup面临一个困难的选择:对“C with Classes”(后来被命名为C++)的用户的支持吞噬掉了他一大半的时间,但还没有足够的用户让他有资金成立一个正式机构。对于这种情况,Bjarne说:
“我看只有两种脱离困境的办法:
1. 停止支持C with Classes。
2. 开发一种更好的新语言,使它拥有足够多的用户,以获得足够的资金来支持和发展一个机构。当时我估计至少需要5000个工业用户。
第三种选择——通过推销(骗局)来增加用户数量——从来没有在我的脑海中出现过[1]。”
在1991年,C with Classes诞生后的第12年,C++的用户数量已经达到了四十万,而且这个数量每7.5个月就翻一番[2]。(一点点数学知识告诉我:如果这个增长率一直保持下去,到2000年时地球上的每个人都将是C++开发者——一个真正可怕的Y2K问题!)
因为没有计划周密的销售计划,而且,因为C++是如此“复杂的语言”(也许我们曾经听到过这种说法),我们可以相当有把握的说:C++只靠它的技术优势成为了面向对象开发工具的领袖。就和以前的C语言一样,C++从基础做起,它满足开发者的热情,它不断增强自己的能力以满足更多的需要。
但是,C++快速成长的故事与JAVA的故事没有任何相似之处。
JAVA人
在Bruce Eckel——著名的作者和教授——的JAVA公开讨论会上,他送给每个与会者一件T-shirt。当你穿上它时,在你的左胸上有一个图案:一个原始人正为生活而奔波。Bruce将这个人叫做“JAVA人”,因为他正代表着你和我和大多数现代计算机工业的从业者——我们正试图跟上JAVA的浪潮。稍微浏览一下JAVA库,你就会看到这个浪潮发展的速度。当太阳微系统公司(SUN)在1995年中期推出JDK(JAVA开发包)版本1.0时,它的库里有212个类和接口。其后1997年的1.1版有504个。现在的1.2版JDK库中有1592个类和接口(以及13635个方法),而且它还在beta测试阶段中!而且,好象几个新增加的语言特性(内嵌类、新的事件模型、Swing组件、可插入VM体系等等)还不足以让我们的脑子和手指忙乱似的,JAVA的每个版本还都会反对以前的许多类和方法——我们还必须忘掉他们。
美国公司在应用新技术方面都有略微保守的传统。我所工作过的三家大公司都只首先选用ANSI标准定义过的编程语言。这就是我在1978年我的第一份工作中仍然使用Fortran的原因。直到1986年,我才偷偷在我的工作中引入了C语言——C语言的标准化在几年之后才完成,但它当时已经有16年的历史、而且非常稳定。我第一次能正式在工作中使用C++是在1994年——在它被发明之后的第15年、公开之后的第9年、标准化之后的第5年。一般来说,IT公司只使用那些“得到验证的技术。”
那么,为什么几乎所有主要的软件厂商都不顾新的JAVA语言的不稳定性而飞快的提供了JAVA工具来跟上需要呢?为什么JAVA能对这么多通常谨小慎微的大公司造成如此的影响呢?相关的事件还包括:我在这个春天向一家非常保守的公司传授JAVA的知识,这样他们可以用JAVA来重写他们的国内记帐系统。这是为什么?
当然,答案很简单:Internet。当访问Internet已经成为家庭日常时,JAVA来了。“自由发布”当然没有错,但正是Internet让“自由发布”切实可行。添加applet的能力是无可争议的。applet让静态的页面活动起来,这从我国的CFO们手中诱惑出了无数的美圆。
所以,骗局——呃,我是指,推销——是答案中理所当然的一部分。在1996年我与Bjarn Stroustrup的一次会见中,我问他对“JAVA革命”有什么想法。他回答我:
“如果我没有兼容C语言的约束,我肯定也不会设计出JAVA这样的语言。可是,SUN能靠它获得如此多的美圆,这让我感到惊讶。这是不会被忘记的一课,并且这将是单个程序员、小公司和学院的恶兆。如果人们坚持要对C++和JAVA进行比较——看起来他们正在这样做——我建议他们看看D&E(The Design and Evolution of C++),看看C++为什么是现在这个样子,并考虑这两种语言在设计标准中的效果。C++和JAVA的差异不止是表面上的,并且不可能有哪个语言拥有所有的优点。[3]
牛肉在哪里?
推销可能可以吸引我们的注意力,但不能保证持续的成功。尽管JAVA革命仍在继续,但现在Javascript和GIF动画已经做了我们以前用applet做的大多数工作。JAVA获得持续的成功,那是因为在骗局的背后它有自己的价值。JAVA对程序员有吸引力,因为它是一种干净的、设计良好的软件开发工具,并且学习它不是“太困难”。JAVA对开发投资者有吸引力,因为它强调对象和包,还因为它的虚拟机体系,因为它支持可控的、划算的、(通常)安全的应用程序建造、发布途径。除了操作系统厂商之外,所有人都喜欢“编写一次,到处运行”的概念。而且JAVA对网络编程举世无双的支持让它在电子商务领域中获胜。让我们面对这样一个事实:JAVA很酷!
但是,不要就这样把你的C++编译器搁置起来。JAVA不可能做所有事。这也就是它支持JNI(JAVA固有接口)的原因。在运行速度确实重要的地方,C++仍然是胜利者(至少现在是)。而且,尽管SUN已经发布了嵌入式JAVA的规范,你仍然可以肯定:JAVA取代C作为嵌入式编程混合语言还需要一段时间。
另外,JAVA并不象你想象的那么容易学习。在我前面提到过的为某公司进行的教学的第四天,相当数量的学生带着疲惫的表情说:“伙计,这并不象我想象的那么容易!”(在我的记忆中,那天我们在谈论对象克隆、反射和I/O。)我想Larry O'Brien做出了最好的总结,他说:“说‘JAVA比C++简单’,就好象说‘2000年比珠穆朗玛峰短’一样。”JAVA是复杂的,但复杂的方式与C++不同。它是否真的更简单,这取决于你和你的需要。解决实际问题从来不会容易,不管你使用什么工具。
缺少了什么?
JAVA来自Oak,一种剥除了C++的一些特性以用于嵌入式系统的语言。虽然你现在从JDK里得到的JAVA比Oak大(并且不再适用于嵌入式应用),但JAVA还是不支持C++的下列特性:
显式指针和引用
delete运算符
析构子
默认参数
模板
全局函数和数据
局部静态数据
宏
算符重载
多继承
声明和定义分离
前两个条目看起来似乎是JAVA的一个大卖点。指针是最难掌握的概念之一,而“赶走指针”正是C/C++从业者共同的愿望。但是,你仍然必须理解“间接使用”的基础,否则你还是无法使用JAVA的对象(那么你也就无法使用JAVA了)。所有的JAVA对象都是动态的,并且存在于堆空间中。你使用new运算符来创建一个JAVA对象。
Foo f = new Foo();
f实际上还是一个指针,它指向堆空间中真正的Foo对象,但你只能把f作为访问对象的工具来使用;比如说,你不能对f做算术运算。为了让事情对我们这些C/C++程序员更简单,我们需要用“指针”或“引用”之外的一个术语来表示f。“句柄”看起来是个不错的术语。然后,为了访问一个类成员,你可以在句柄上使用普通的成员选择算符,例如f.x。如果你把一个对象当作参数传递给一个函数,实际上是句柄的一个拷贝被传递。就象C一样,所有的参数都是按值传递的,所以JAVA没有与C++的引用等价的东西。
这意味着你不能写一个函数来有效的交换它的两个参数,但函数可以修改参数对象的数据成员。举个例子,假设我定义了Foo类,它有一个int型成员x:
class Foo {
public int x;
}
那么下列的swap函数将交换其参数对象的x成员值:
public static void
swap(Foo f1, Foo f2) {
int temp = f1.x;
f1.x = f2.x;
f2.x = temp;
}
这样的行为和下列的C函数本质上是相同的:
void
swap(struct Foo* f1,
struct Foo* f2) {
int temp = f1->x;
f1->x = f2->x;
f2->x = temp;
}
现在看看上面这个列表的第二项。什么?没有delete算符?多么不负责任!不,不是的。程序员不需要将使用过的内存返回到堆空间中,因为JAVA拥有垃圾收集机制,它将自动回收所有不被引用的存储空间。好消息是:你不必担心内存泄漏。坏消息是:你不会知道垃圾收集将在何时运行、哪些对象将在何时被回收。这使你不可能通过垃圾收集来控制资源的回收,这也就是JAVA没有析构子的原因之一。如果你希望在某个特定时间点释放某些资源,你必须显式释放。
有什么?
我们讲了那么多JAVA没有的东西。它拥有的是对类和对象的牢固的支持,以及一个特点丰富的库——这个库能满足你能想象到的任何编程任务。C++程序员在学习JAVA时将发现下列重要的项目:
所有东西都属于某个类。(记住,没有全局的东西。)
你不必担心内存泄漏。
字符按照Unicode编码。
异常不是可选的。(做好处理异常的准备吧。)
库支持线程和网络编程。
内建(基本)类型在所有平台上的大小都一样。
为了说明第一点,我在这里给出了一个完整的“交换”的程序:
// Swap.java
class Foo {
public int x;
}
public class Swap {
public static void swap(Foo f1, Foo f2) {
int temp = f1.x;
f1.x = f2.x;
f2.x = temp;
}
public static void
main(String[] args) {
Foo f1 = new Foo();
f1.x = 1;
Foo f2 = new Foo();
f2.x = 2;
swap(f1, f2);
System.out.println(f1.x); // 2
System.out.println(f2.x); // 1
}
}
请注意,这里没有任何类似delete算符的东西。
正如这个列表所提到的,JAVA程序完全由类组成,每个类都包含字段(数据成员)和/或方法(成员函数)。这个简单的组织,再加上我将在以后的文章中介绍的方便的库包装机制,为大规模应用的管理提供了一种实用的规范。
为了使用JDK编译这个程序,我在命令行中做如下输入:
C:> javac Swap.java
JAVA源代码的文件扩展名是.java。每个文件中必须至少有一个public类,这个类的名字必须与文件名(不包括.java)相同。javac命令为每个选定的.java文件生成一个.class文件。.class文件包含字节码,字节码将由你的JVM(JAVA虚拟机)来执行。java命令会装载JVM,并指示JVM开始执行.class文件——首先翻译main方法中的字节码,就象这样
C:> java Swap
2
1
Swap类自己不包含数据,并且它所有的方法都是static的,这意味着它们不需要一个Swap对象就可以执行(就象在C++里一样)。实际上,Swap类的存在只是提供一个地方来安置swap方法和main方法。System.out就是JAVA中等价于stdout的东西,而println方法将把参数转换成字符串然后输出。
一个真正工作的JAVA程序通常包括多个.class文件。当一个类需要调用其他类的服务时,JVM搜索后者相应的.class文件。因为JAVA中的每个函数都是某个类的方法,所以在一个程序中有多个main方法也不会造成冲突。实际上,通常我们会出于测试的目的在每个public类中都放一个main方法。不存在唯一的程序级的main方法也就意味着main方法不会返回什么结果给操作系统,但JAVA为这个目的提供了一个特殊的方法,类似于C的exit函数。
正是虚拟机体系给了JAVA“编写一次,到处运行”的可移植性。只要一个平台支持JVM,它就可以运行来自任何平台的字节码。但是,因为JVM是一个解释器,在与纯编译语言的比较中,它也表现出了速度上的劣势。为了部分的补偿这一点,JDK现在使用了“即时”(JIT)编译器,它在类第一次被装载时将字节码编译成固有代码。这样代码只需要被解释一次,而不是每次被调用时都需要解释。你还可以使用其他的商用JIT编译器和纯固有代码编译器。
总结
正如你能看到的,JAVA的语法几乎与C++完全一样——语句以分号结尾,大括号内的复合语句。但是请注意,类的定义不以分号结尾。此外,JAVA中没有struct关键字(这不会让任何人烦恼)。你还必须为每个字段或方法定义单独指定访问级(public/private/protected),这逼迫我们更多的敲打键盘。也许最大的文化冲突是格式化I/O 。比如说,JAVA没有等价于scanf或printf的东西,这使JAVA的命令行I/O成为了对潜在的C/C++移居者纯粹的打击。但是,这种命令行I/O在今天这个图形化用户界面的世界里变得越来越不重要,并且我在今后的文章中将展示JAVA的I/O包中的一些非常完善的特性。
这就是所有了吗?不,这只是我的系列文章的开头。我在第一部分里的目的是为这个系列做一些铺垫,并稍微吊吊你的胃口。在后续的部分中,我将从基本数据类型、运算符和控制结构(简单的材料)开始,对JAVA语言做深入的观察。如果你对这个主题的书感兴趣,你最好浏览CUJ网站(www.cuj.com)的“编辑角”,那里有一个“基本读物列表”。我推荐下列两本最好的书:
1. Arnold & Gosling. The Java Programming Language, Second Edition (Addison-Wesley, 1998)。由JAVA的发明者所著的一本庄重、简练的语言介绍。
2. Bruce Eckel. Thinking in Java (Prentice-Hall, 1998) 。市面上能买到的最友好、最全面的JAVA书籍。
请开始享受你的JAVA之旅吧。
参考书目
[1] Bjarne Stroustrup. The Design and Evolution of C++ (Addison-Wesley, 1994), p. 63.
[2] Bjarne Stroustrup. The Design and Evolution of C++, p. 163-164.
[3] Chuck Allison. "C++: The Making of a Standard," C/C++ Users Journal, October 1996.