type
Dog = class (Animal)
...
end;
·Java:Java使用extends关键字来表述唯一一种继承类型,对应于C++中的public继承。Java不支持多重继承。Java类同样具有一个通用基类。
class Dog extends Animal {
...
}
·注意:关于基类的构造函数和初始化。在C++和Java中,基类的构造函数具有很复杂的结构。在OP中,初始化基类则是程序员的责任。这个主题比较复杂,所以我不打算进一步讲述。我会把注意力集中在通用基类、基类访问、多重继承、接口、后期绑定以及其它相关的内容。
所有类的祖先
· 特性描述:在一些OOP语言中,所有类都直接或间接的派生自某个特定的基类。这个类(通常被称为Object或其它类似的名字)具有所有类共有的基本功 能。事实上,所有类都继承自这个基类。因为最初在Smalltalk中便是如此设计的,所以大多数OOP语言采用了这个概念。
·C++:虽然在C++中没有这个概念,但许多应用程序框架引入了通用基类的概念。MFC是个很好的例子,它有一个CObject类。事实上,最初这是十分意义的,因为语言不具有模板特性(以及多重继承特性)。
·OP:每个类都自动的继承自TObject类。因为OP不支持多重继承,所以所有的类构成了一个巨大的派生树。TObject类可以处理RTTI,同时具有其它一些能力。
·Java:如同OP一样,所有的类继承自Object类。这个基类也具有一些有限的功能。
访问基类的方法
·特性描述:当编写一个类方法或者重载一个基类方法时,你经常需要引用基类的方法。而如果方法在派生类中重新被定义,那么使用方法的名字将调用新方法。OOP语言使用不同的技术或关键字解决访问基类方法的问题。
·C++:在C++中可以使用范围操作符(::)引用一个特定的类。你不仅可以访问基类,甚至可以访问继承链中更高层的类。
·OP:Object Pascal使用一个特殊的关键字完成同样的工作:inherited。在关键字后可以加上需要调用的基类方法的名称,或者(在某些情况下),简单的使用这个关键字来访问对应的基类方法。
·Java:Java中使用super关键字完成类似的工作。在Java和OP中,你无法访问更高一级的基类。看起来这似乎限制了什么,但是这样可以通过添加中间类来扩展继承链。同时,如果你不需要基类的功能,你也许可以不从这个基类派生你的新类。
子类兼容性
·特性描述:并不是所有OOP语言都是强类型的,就像我开始提到的,但是这里我们涉及的三种语言都是。这意味着不同类的对象之间是不兼容的。只有一个例外,就是派生类的对象与基类是兼容的(注意:反过来不成立)。
·C++:在C++中,子类兼容性规则只适用于指针和引用,对普通对象则不适用。事实上,不同的对象在所占用的内存不同,所以你不能将相同的内存分配给不同的对象。
·OP:子类兼容性适用于所有对象,因为OP采用了对象参考模型。此外,所有对象都与TObject类型兼容。
·Java:Java的情况与OP完全相同。
·注意:多态性。如同下一节将要描述的,子类兼容性对于实现后期绑定和多态性是十分重要的。
后期绑定(及多态性)
· 特性描述:当继承链中不同的类分别重新定义了它们基类的方法,那么如果能够通过一个兼容这些类的对象(感谢子类兼容性)调用合适的类的方法,将是十分有用 的。要完成这个工作,编译器需要支持后期绑定,它将不产生一个特定的函数调用,而是在运行期决定了对象的真正类型后,才进行函数调用。
·C++:在C++中,后期绑定只应用于虚拟方法(在调用速度上会有所减慢)。一个在基类中定义的虚拟方法将在它被重新定义时保持这种特性(当然方法的声明必须完全匹配)。一般情况,非虚拟方法并不允许后期绑定。
·OP: 在Object Pascal中,后期绑定通过关键字virtual或dynamic引入(这两个关键字的区别仅在于技术实现的不同)。在派生类重新定义方法时,应使用 override关键字(这样就强迫编译器检查方法声明是否匹配)。这是OP中特有的,它允许在基类做更多的改动。
·Java:在 Java中,所有的方法都使用后期绑定,除非你使用final关键字。final方法不能被重新定义,在调用速度上更快。在Java中正确的方法名称对于 多态性的实现是非常重要的。Java中默认后期绑定和C++中默认前期绑定这一事实表明了这两种语言不同的针对性:C++有时会牺牲OOP模型以获取性能 的提升。
·注意:构造函数和析构函数的后期绑定。与其它两种语言相反,Object Pascal允许定义虚拟构造函数。而这三种语言都支持虚拟析构函数。
抽象方法和抽象类
· 特性描述:当建立一个复杂的继承链时,为了实现多态性,经常需要为更高级的类引入一些方法,虽然这些方法未必是为这个类抽象概念而定义的。除了使用空方法 定义,许多OOP语言实现了一种特殊的机制:定义抽象方法。所谓抽象方法就是没有实现的方法。具有一个或多个抽象方法的类称为抽象类。
·C++:在C++中,抽象方法被称为纯虚函数,通过在方法定义后添加所谓虚定义符(=0)可以获得一个抽象方法。抽象类就是具有(或继承了)一个或多个抽象方法的类。不能创建抽象类对象。
·OP: Object Pascal使用abstract关键字声明抽象方法。同样,抽象类就是具有或继承了抽象方法的类,但是你可以创建抽象类的实例(虽然编译器会产生一个警 告信息)。这就隐含了调用抽象方法的危险,在运行期,这样会产生一个运行期错误,并会终止程序的运行。
·Java:在Java中,抽象方法和抽象类都用abstract关键字声明(事实上Java中的抽象类必须具有抽象方法,好像有一点多余)。同样,派生类如果没有重新定义所有的抽象方法,必须使用abstract关键字定义为抽象类。不能创建抽象类的实例。
多重继承和接口
·特性描述:一些OOP语言允许从多个基类派生新类。另一些语言只允许从一个类中派生新类,但是可以从多个接口(或者纯抽象类,只由纯虚函数构成的类)派生新类。
·C ++:C++是三种语言中唯一支持多重继承的。一些程序员认为这是一件好事,另一些程序员认为这是一件坏事,我不想过多的讨论这个问题。多重继承产生了很 多新概念,比如说虚基类,虽然功能强大,但并不好掌握。C++没有接口的概念,虽然它与多重继承的纯抽象类概念接近(接口可以看作多重继承的子集)。
·Java: Java,以及Object Pascal,都不支持多重继承,但是完全支持接口。接口的方法支持多态性,并且当需要一个接口对象时,可以通过一个对象实现接口。一个类只能继承自一个 基类,但可以implement(关键字)多个接口。Java的接口与COM模型非常吻合,虽然没有预先的考虑。举个例子:
public interface CanFly {
public void Fly();
}
public class Bat extends Animal implements CanFly {
public void Fly( ) { // the bat flies... }
}
·OP: Delphi 3在Object Pascal中引入了类似Java的接口,这些接口非常吻合COM(虽然技术上经常在非COM程序中使用)。接口构造了一个与类独立的继承链,但是与 Java一样,一个类可以继承自唯一的基类并实现多个接口。将类的方法映射为类实现的接口的方法是Object Pascal语言中令人迷惑的几个问题中的一个。
RTTI
·特性描述:在强类型OOP语言中,编译器完成所有类型检查的工作,所以很少需要运行程序保存类型的信息。然而,某些情况下需要某些类型信息。因此,这三种OOP语言都或多或少的支持运行期类型识别/信息(RTTI)。
·C++:最初的C++语言不支持RTTI。后来通过dynamic_cast的方式提供了部分的类型信息。你可以查询一个对象的类型,也可以检查两个对象是否具有相同的类型。
·OP: Object Pascal以及它的可视开发环境支持也需要大量的RTTI。不仅可以进行类型检查(使用is和as操作符),类也为它的published成员生成大量 的RTTI。事实上这个关键字负责部分RTTI的生成。属性、流结构(窗体文件以及始于对象观察器的Delphi环境很大程度上依赖于类的RTTI。 TObject类具有ClassName和ClassType方法。ClassType方法返回一个类类型变量——一个特殊类参考类型的实例(并不是类本 身)。
·Java:和Object Pascal一样,Java中也有一个基类用于跟踪类型信息。Object类的getClass()方法会返回一个元类(一个用于描述类的类型的对象), 你也可以使用getName()函数获得一个类名字符串。你还可以使用instanceof操作符。Java 1.0不支持更多内容的RTTI,但在未来的版本中可能会改变,以适应可视环境和组件的开发(所谓Java Beans)。
·例子:
// C++
Dog* MyDog = dynamic_cast <Dog*> (myAnimal);
// Java
Dog MyDog = (Dog) myAnimal;
// Object Pascal
Dog myDog := myAnimal as Dog;
异常处理
·特性描述:异常处理构想的出发点是简化程序的错误处理代码,提供标准内建机制,从而使程序更加健壮。异常处理的内容很多,这里我只是简述一些关键的要素和区别。
·C++:C++使用throw关键字来产生一个异常,用try关键字标志被保护的程序块,用catch关键字标志异常处理程序代码。异常是一些特殊类的对象,在这三种语言中都构成了各自的继承链。C++会对所有栈中的对象进行栈展开和销毁(调用析构函数)。
·OP: Object Pascal使用与C++类似的关键字raise,try和except,并且具有类似的功能。唯一真正的区别是因为没有对象会被创建于栈中,所以不会发 生栈展开。另外,你可以使用一个finally关键字,标志那些无论是否产生异常都被执行的代码。在Delphi中,异常类全部派生自 Exception。
·Java:Java使用和C++相同的关键字,但是其行为却更接近于Object Pascal,包括使用finally关键字。所有采用对象引用模型的语言基本都是如此。碎片回收程序的存在限制了finally关键字对类的应用,这些 类不仅占用了内存资源。Java认为所有能产生异常的函数都具有一个正确的异常子句,这个子句告诉Java哪些异常可能会被产生。这个假设十分严格,并由 编译器进行检查。这是一个非常有用的技术,即使这意味着程序员要做更多的工作。Java中的异常类必须派生自Throwable类。
模板(通用程序设计)
·特性描述:在不指定某些数据类型的情况下编写函数和类的技术,称为通用程序设计。在函数或类被使用的时候,特定的数据类型会代替函数或类中的未指定部分。所有情况都在编译器的监管之下,不会有任何问题遗留给运行期来决定。模板类的一个典型的例子就是容器类。
·C++:这三种语言中只有C++具有通用类和函数,这些类和函数用Template关键字表示。C++标准包含了一个巨大的模板类库,称为STL,用于支持一些特殊而有用的程序设计功能。
·OP:Object Pascal不支持模板。容器类通常被创建为TObject类对象的容器。
·Java:Java同样不支持模板。你可以使用对象容器,或采用其它类似的方法。
其它特殊特性
·特性描述:以下是其它一些我谈及的特性,它们不是基础特性,而且仅为一种语言所特有。
·C ++:我已经提到了多重继承、虚基类和模板。还有一些另外两种语言所不具有的特性。C++支持操作符重载,而Java中支持方法重载。C++还允许程序员 重载全局函数。你甚至可以重载类运算符,编写可能会在后台被调用的类型转换方法。C++的对象模型需要拷贝构造函数和赋值运算符重载,而其它两种语言则不 需要,因为它们基于对象引用模型。
·Java:只有Java在语言中支持多线程。对象和方法支持同步机制(使用synchronized 关键字):同一个类的两个synchronized方法不能同时运行。要创建一个新的线程只需从Thread类中派生新类,并覆盖run()方法。另一个 方法是实现Runnable接口(这是建立多线程applet的常用方法)。我们已经讨论过了碎片回收程序。Java的另一个关键特性是代码兼容性,但是 这并不是严格的与语言相关的。
·OP:Object Pascal的一些特性包括类引用,便利的方法指针(这是事件模型的基础),特别是属性。属性用来隐藏对数据成员的访问,这些访问大多是通过方法进行的。 属性可以直接映射为对数据成员的读写操作,也可以映射为访问函数。即使改变了访问数据成员的方式,也不需要改变调用的代码(虽然需要重新编译),这使得属 性称为了一个强大的封装特性。Java也将在1.1版中加入这个特性,以支持Java Beans。
标准
·特性描述:每个语言都需要有人建立一个标准,并检查是否所有的实现都符合这个标准。
·C ++:ANSI/ISO C++标准委员会已经完成了标准化工作。大多数编译器编写者都努力遵守这个标准,虽然还有很多的差异存在。理论上的发展已基本停止。但在实现上,新的 Borland C++ Builder虽然并不很成熟,但使很多人认识到C++迫切的需要一个可视开发环境。同时,广为流行的Visual C++将C++向另一个方向发展起来,例如,大量使用宏。我的意见是,每个语言都有它的开发模型,在不适于某种语言的环境下强行使用这种语言是毫无意义 的。
·OP:Object Pascal是一个私有语言,所以没有标准。Borland已经授权给一些OS/2编译器开发商,但是没有什么效果。在每一个新版本的Delphi中,Borland都扩展了这种语言。
·Java:Java也是私有语言,并且拥有一个同名的商标。但是Sun更愿意授权给其它编译器开发商。Sun自己控制着这种语言,并且好像并不想为其建立一个官方的标准,至少目前如此。Sun也在极力避免不遵守标准的虚拟机被开发出来。
结论:语言和开发环境
就 像我上面提到过的,虽然我尽力做到只比较语言的语法语义特性,但在适当的环境中考察它们是很重要的。这些语言为不同的目标开发出来,是为了以不同的途径解 决不同的问题的,并在不同的开发环境中被应用。虽然语言和它们的开发环境体现了彼此的一些特性,但它们是为了满足不同的需要而建立的,就像我们在对比这些 特性时看到的那样。C++的目标是强大的功能和控制能力,代价是复杂性提高;Delphi的目标是在不损失太多功能的情况下,尽可能简单以及可视化编程和 同Windows紧密结合;Java的目标是兼容性和分布式应用,为此不惜牺牲一些运行速度。
决定这三种语言命运的并不是我这篇文章中所涉及的那 些语言特性。Borland的财政状况,Microsoft对操作系统的控制,Sun在Internet世界的声望(许多人认为的反微软),Web浏览器 和Win32 API的前景,ActiveX(以及Delphi的ActiveForms)将扮演的角色,这些都是影响你选择的因素(往往超过了技术因素)。例如那个非 常优秀的语言Eiffel——Object Pascal和Java都从中吸取了很多灵感,没有抢到任何市场份额,虽然它在全世界的许多大学中都十分流行。
记住,“时髦”这个词已经在计算机 世界占有了前所未有的地位。就像用户喜欢使用今年新版本的软件(这大概就是为什么操作系统都以年份命名),程序员们也喜欢用最新的程序设计语言,并希望第 一个掌握它。我们可以说“Java并不是最新的OOP语言”,在未来的几年里,一些人会开发出更时髦的语言,而其他人则会蜂拥而上,全然忘记了这个世界上 大多数程序员还在他们的键盘上敲打着传统的Cobol语句!