项目实践精解:Java核心技术应用开发
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.2 Java与面向对象

Java是纯粹的面向对象的编程语言。要想学好Java,首先就要建立起面向对象的思想。对于Java来讲,一切皆对象,所以我们首先介绍一下面向对象的基本概念,之后所有的内容都将围绕面向对象展开,例如类(Class)、对象(Object)、方法(Method)、实例(Instance)等,这对深入理解Java语言大有益处。

面向对象作为一种思想及编程方法,为软件开发的整个过程,包括从分析设计到维护,提供了一个完整的解决方案。面向对象堪称是软件开发技术发展取得的里程碑式的伟大成就。

从20世纪80年代后期开始,面向对象分析(OOA)、面向对象设计(OOD)和面向对象程序设计(OOP)等新的系统开发方式模型的研究相继展开,这些模型在有些文献中统称为OO范型。

1.2.1 传统的面向过程和现代的面向对象程序设计语言

读者可能接触过传统的程序设计语言,如Fortran,C等,这些语言都属于面向过程的程序设计语言,其编程思路主要专注于算法的实现。而随着计算机技术的发展,面向过程的语言就会产生很多局限性。人们需要一种新的程序设计思想,使得基于该思想开发的应用程序在数据抽象、信息隐藏、关联处理等方面具有良好的特性。这种特性就是原始的面向对象的程序特性。

所有的计算机程序都由两类元素组成:代码和数据。此外,从概念上讲,程序还可以以它的代码或者数据为核心进行组织编写。目前有两种类型的程序开发方法。第一种方法被称为面向过程编程(Process-Oriented Programming),用它编写的程序都具有线性执行的特点。面向过程编程可认为是代码作用于数据,像C这样的过程式语言采用这种方法是相当成功的。然而,当程序变得更大并且复杂时,就会出现问题。为了管理不断增加的复杂性,第二种方法,也就是面向对象编程(Object-Oriented Programming)被构思出来了。面向对象编程围绕它的数据(即对象)和为这个数据严格定义的接口来组织程序。面向对象的程序实际上是用数据控制对代码的访问。大量实践表明,将控制的实体变换为数据,会使程序在组织结构上更加优化。

纯粹的面向对象程序设计方法应该是如下这样的。

(1)所有的东西都是对象。可以将对象想象成为一种新型变量,它保存着数据,而且还可以对自身数据进行操作。例如,类Max中保留着数据的最大值,同时有方法updateMax根据新加入的price值产生最新的最大值,还有getMax方法返回数据的最大值。

(2)程序是一大堆对象的组合。通过消息传递,各个对象知道自己应该做些什么。如果需要让对象做些事情,则必须向该对象“发送一条消息”。具体来说,可以将消息想象成为一个调用请求,它调用的是从属于目标对象的一个方法。例如,面向对象的程序段应该是属于某个类的,比如说属于类Shopping,则Shopping中就包含了类Max的对象max,调用方法updateMax就相当于Shopping对象向max对象发出一条指令“updateMax”,要求对象max重新计算最大值。

(3)每个对象都有自己的存储空间。一个对象可容纳其他对象,或者说通过封装现有的对象可以产生新型对象。因此,尽管对象的概念非常简单,但是经过封装以后却可以在程序中达到任意高的复杂程度。

(4)每个对象都属于某个类。根据语法,每个对象都是某个“类”的一个“实例”。例如,max是类Max的实例。一个类的最重要的特征就是“能将什么消息发给它”,也就是类本身有哪些操作。

1.2.2 抽象的概念

面向对象编程的一个实质性的要素是抽象。抽象强调实体的本质、内在的属性,人们通过抽象(Abstraction)处理复杂性。例如,人们不会把一辆汽车想象成由上万个相互独立的部分所组成的一个设备,而是把它想成一个具有自己独特行为和整体功能的事物。人们可以忽略引擎、传动及刹车系统的工作细节,而将汽车作为一个整体加以利用。

面向对象的程序设计在此基础上跨出了一大步,程序员可以利用一些工具表达问题空间内的元素。由于这种表达非常普遍,所以不必受限于特定类型的问题。我们将问题空间中的元素,以及它们在方案空间的表示物称为“对象”(Object)。当然,还有一些在问题空间没有对应体的其他对象。通过添加新的对象类型,程序可以进行灵活的调整,以便与特定的问题配合。

另外,层级分类是管理抽象的一个很有效的方法,它允许人们根据物理意义将复杂的系统分解为更多、更易处理的模块。从表面上看,汽车是一个独立的对象,一旦到了内部,就会发现汽车由若干个子系统组成,例如驾驶系统、音响系统、安全系统、制动系统等。再进一步细分,这些子系统由更多、更详细的专用元件构成。例如,音响系统由收音机、CD播放器等组成。从这里得到的启发是,可以通过层级抽象对复杂的汽车(或任何复杂的系统)进行管理。

复杂系统的分层抽象也能被用于计算机程序设计中。传统的面向过程程序的数据经过抽象可用若干个组成对象表示,程序中的过程和步骤可看成是在这些对象之间进行消息收集。这样,每一个对象都有它自己的独特行为特征。人们可以把这些对象当做具体的实体,让它们控制消息作出反应,这是面向对象编程的本质。面向对象的概念是Java的核心,对程序员来讲,重要的是要理解这些概念怎么转变为程序。任何主要的软件项目,都不可避免地要经历概念提出、成长、衰老这样一个生命周期,而面向对象的程序设计,可以使软件在生命周期的每一个阶段都有足够的应变能力。例如,一旦定义好了对象和指向这些对象的简明的、可靠的接口,人们就能很从容自信地解除或更替旧系统的某些组成部分。

正如上面所说,对实体的抽象分为两部分:对属性的抽象和对行为的抽象。被抽象出的属性在程序设计语言中通常被称为成员变量(Variable Member)或者字段(Field),被抽象出的行为在程序设计语言中通常被称为方法(Method)。

1.2.3 面向对象编程的3个原则

面向对象编程的3个原则分别是:封装、继承和多态。

1.封装

封装(Encapsulation)是将程序代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。理解封装的一个方法就是把它想象成一个黑盒子,它可以阻止在外部定义的代码随意访问内部代码和数据。对黑盒子内代码和数据的访问由一个适当定义的接口严格控制。以汽车为例,比如汽车上的自动传送,自动传送中包含了有关引擎的信息,如汽车正在以什么样的加速度前进,行驶路面的坡度如何,以及目前的挡位等。驾驶员影响这个复杂封装的方法仅有一个:移动挡位传动杆。所以挡位传动杆是把驾驶员和传动连接起来的唯一接口。而且,传动对象内的任何操作都不会影响到外部对象,如挡位传动装置不会打开车门。因为自动传动被封装起来了,所以任何一家汽车制造商都可以选择一种适合自己的方式来实现它。然而,从驾驶员的角度来看,它们的用途都是一样的。与此相同的观点能被用于编程。封装代码的好处是每个人都知道怎么访问它,但却不必考虑它的内部实现细节,也不必担心使用不当会带来负面影响。

Java封装的基本单元是类。一个类(Class)定义了将被一个对象集共享的结构和行为(数据和代码)。一个给定类的每个对象都包含这个类定义的行为和结构,好像它们是从同一个类的模子中铸造出来似的。基于这个原因,对象有时被看做是类的实例(Instance)。所以,类是一种逻辑结构,而对象是真正存在的物理实体。

当创建一个类时,要指定组成这个类的代码和数据。从总体上讲,这些元素都被称为该类的成员(Members)。具体地说,类定义的数据称为成员变量(Member Variables)或实例变量(Instance Variables),操作数据的代码称为成员方法(Member Methods)或简称方法(Methods)。如果对C/C++熟悉,可以这样理解:Java程序员所称的方法,就是C/C++程序员所称的函数(Function)。在完全用Java编写的程序中,方法定义了如何使用成员变量。这意味着一个类的行为和接口是通过方法来定义的,类的这些方法对它的实例数据进行操作。

既然类的目的是封装复杂性,在类的内部就应该有隐藏复杂性的机制。Java采用三个显式(明确)关键字和一个隐式(暗示)关键字来设置类边界,即public,private,protected和暗示性的friendly。若未明确指定其他关键字,则默认为后者。这些关键字的使用和含义都是相当直观的,它们决定了谁能使用后续的定义内容。“public”(公共)意味着后续的定义任何人均可使用。“private”(私有)意味着除你自己、类的创建者以及这个类的内部方法成员,其他任何人都不能访问后续的定义信息。private在你与客户程序员之间竖起了一堵墙,若有人试图访问私有成员,就会得到一个编译错误。“friendly”(友好的)涉及“包装”或“封装”(Package)的概念——即Java用来构建库的方法。若某个对象是“友好的”,就意味着它只能在这个包装的范围内使用(所以这一访问级别有时也叫做“包装访问”)。“protected”(受保护的)与“private”相似,区别在于继承的类可访问受保护的成员,但不能访问私有成员。继承的问题下面将介绍。

类的公共接口代表类的外部用户需要知道或可以知道的事情。私有方法和数据仅能被该类的成员代码所访问,其他任何不是该类的成员的代码都不能访问私有的方法或变量。既然类的私有成员仅能被程序中的其他部分通过该类的公共接口访问,那么程序员就能保证不希望发生的事情一定不会发生。当然,公共接口应该仔细设计,不要过多暴露类的内部内容。

2.继承

对象的概念为我们带来了极大的便利,它在概念上允许我们将各种数据和功能封装到一起,这样便可恰当地表达“问题空间”的概念,不用刻意遵照底层机器的表达方式。在程序设计语言中,这些概念则反映为具体的类(使用class关键字)。我们费尽心思设计出某个类后,假如不得不重新创建一个类,令其实现大致相同的功能,那会是一件非常浪费时间的事情。但若能利用现成的类,对其进行“克隆”,再根据情况进行添加和修改,情况就会理想多了。“继承”正是针对这个目标而设计的,但继承并不完全等价于克隆。在继承过程中,若原始类(正式名称叫做基础类、超类或父类)发生了变化,修改过的“克隆”类(正式名称叫做继承类或者子类)也会反映出这种变化。在Java语言中,继承是通过extends关键字实现的。

继承(Inheritance)是一个对象获得另一个对象属性的过程。继承很重要,因为它支持按层分类的思想和概念,如前面提到的大多数知识都可以按层级(即从上到下)分类管理。例如,猎犬是狗类的一部分,狗又是哺乳动物的一部分,哺乳动物类又是动物类的一部分。如果不用层级的概念,就不得不分别定义每个动物的所有属性。使用了继承,一个对象就只需定义使它在所属类中独一无二的属性即可,因为它可以从它的父类那儿继承所有的通用属性。所以,可以这样说,正是继承机制使对象成为某个通用类的特定实例成为可能。下面将更具体地讨论这个过程。

如果人们想以一个抽象的方式描述动物,可以用身体大小、智力高低及骨骼系统的类型等属性进行描述。动物也具有确定的行为,它们也需要进食、呼吸,并且睡觉。这种对属性和行为的描述就是对动物类的定义。在描述某个更具体的动物类,比如哺乳动物时,它们会有更具体的属性,比如牙齿类型等。哺乳动物是动物的子类(Subclass),而动物是哺乳动物的超类(Superclass)。

由于哺乳动物类是需要更加精确定义的动物,所以它可以从动物类继承所有的属性。一个深度继承的子类继承了类层级(Class Hierarchy)中它的每个祖先的所有属性。继承性与封装性相互作用。如果一个给定定义的类封装了一些属性,那么它的任何子类都将具有同样的属性,而且还添加了子类自己特有的属性。这是面向对象程序在复杂性上呈线性而非几何性增长的一个关键原因。新的子类继承它的所有祖先的所有属性,它不与系统中其余的代码发生无法预料的相互作用。

3.多态

多态(Polymorphism)是指允许一个接口被多个同类动作(即方法)使用的特性,具体使用哪个动作与应用场合有关,下面以一个后进先出型堆栈为例进行说明。

假设有一个程序,需要3种不同类型的堆栈,一个用于整数值,一个用于浮点数值,一个用于字符。尽管堆栈中存储的数据类型不同,但实现每个栈的算法是一样的。如果用一种非面向对象的语言,就要创建3个不同的堆栈程序,每个程序都有一个名字。但是,如果使用Java,由于它具有多态性,就可以创建一个通用的堆栈程序集,它们共享相同的名称。

多态的概念经常被说成是“一个接口,多种方法”。这意味着可以为一组相关的动作设计一个通用的接口。

多态性允许同一个接口被属于同一类的多个动作使用,这样就降低了程序的复杂性。选择应用于每一种情形的特定的动作是编译器的任务,程序员无须手动选择,只需记住和使用通用接口即可。

4.多态、封装与继承相互作用

如果使用得当,在由多态、封装和继承共同组成的编程环境中可以写出比面向过程编程环境更健壮、扩展性更好的程序。精心设计的类层级结构是重用程序代码的基础,封装可以在不破坏依赖于类公共接口代码的基础上对程序进行升级迁移,多态则有助于编写清晰、易懂、易读、易修改的程序。

我们仍然采用汽车的例子帮助读者完整地理解这些概念。总的来说,汽车与程序很相似,所有的驾驶员依靠继承性很快便能掌握驾驶不同类型(子类)车辆的技术。不论是校车、私家轿车还是大卡车,司机差不多都能找到方向盘、制动闸和加速器,并知道如何操作。经过一段时间的驾驶,大多数人甚至能知道手动挡与自动挡之间的差别,因为他们从根本上理解了这两个挡的超类——传动。

人们在汽车上看见的总是封装好的特性。刹车和踏脚板隐藏着底层的复杂性,但接口却是如此简单,驾驶员只需用脚踩就可以。

最后来看多态性,它与汽车制造商基于相同的交通工具提供的多种选择在本质上是一致的。例如,刹车系统有正锁和反锁之分,方向盘有带助力和不带助力之分,引擎有4缸、6缸和8缸之分。无论设置如何,驾驶员都得脚踩刹车板来停车,转动方向盘来转向,脚踏离合器来制动。同样的接口能被用来控制许多不同的实现过程。通过封装、继承及多态性原理,各个独立部分组成了汽车这个对象。这在计算机程序设计中也是一样的。通过面向对象原则的使用,可以把程序的各个复杂部分组合成一个一致的、健壮的、可维护的程序整体。

1.2.4 类和实例对象的性质

根据以上的分析,封装、继承和多态是面向对象程序设计的基本原则。这里我们将在总结前面概念的同时,特别提出类和实例的关系。

使用面向对象的方法,将数据和对数据进行操作的方法结合在一起的完整定义,称为类(Class)。类是面向对象的基本概念,类在本质上是对对象的描述,是创建对象的模板。每个对象都是由相应的类来实现的,通过类的变化来确定对象的创建、对象包含的方法以及对消息产生的反应。

对象的创建事实上就是类的实例化过程,因此被创建的对象就称为类的实例(Instance)。对象创建后,要占用一定的内存空间。类实例中的成员变量被称为实例成员变量(或简称实例变量),而对象间通过消息传递进行交互。

最后需要指出的是,接受一种新的思维方式是需要时间与耐心的。而且,对于程序设计这一特殊任务而言,没有一定数量较精的编程实践,是很难掌握其方法和思想精髓的。所以,在后面的章节中将始终贯彻面向对象的思路,并且配合项目和实例讲解,以便读者能更扎实地掌握面向对象程序设计的方法。