Java编程思想拾遗(1)对象导论

544 阅读6分钟

抽象过程

汇编语言是对底层机器的轻微抽象,命令式语言在汇编语言基础上有了大幅的改进,但是它们所作的主要抽象仍要求在解决问题时基于计算机的结构,而不是基于所要解决的问题的结构。

OOP允许根据问题来描述问题,而不是根据运行解决方案的计算机来描述问题,但是它仍然与计算机有联系:每个对象看起来都有点像一台微型计算机--它具有状态,还具有操作,用户可以要求对象执行这些操作。

对象的概念与分类

万物皆为对象,程序是对象的集合,它们通过发送消息来告知彼此所要做的。

在程序执行期间具有不同状态而在其他方面都相似的对象会被分组到对象的类中,这就是关键字class的由来,每个类的成员或元素都具有某种共性,同时每个成员都有其自身的状态。

对象通过接口提供服务

面向对象程序设计的挑战之一,就是在问题空间的元素和解空间的对象之间创建一对一的映射。每个对象都只能满足某些请求,这些请求由对象的接口所定义,决定结构的便是类型(这里的接口是通用抽象概念,不是编程元素),接口确定了对某一特定对象所能发出的请求。

当正在试图开发或理解一个程序设计时,最好的方法之一就是将对象想象为服务提供者,程序本身将向用户提供服务,它将通过调用其它对象提供的服务来实现这一目的。

访问控制

访问控制存在的第一个原因是客户端程序员(或者说类库使用者)无法触及他们不应该触及的部分-这些部分对数据类型的内部操作来说是必需的,但并不是用户解决特定问题所需的接口的一部分。第二个原因是允许库设计者可以改变类内部的工作方式而不用担心会影响到客户端程序员(即实现解耦)。

聚合复用

最简单地复用某个类的方式就是直接使用该类的一个对象,此外可以将那个类的一个对象置于某个新的类中,我们称其为创建一个新的成员对象,新的类可以由任意数量、任意类型的其他对象以任意可以实现新的类中想要的功能的方式所组成。组合经常被视为has-a关系。

在建立新类时,应该首先考虑组合,因为它更加简单灵活,如果采用这种方式,设计会变得更加清晰,一旦有了一些经验后,便能够看出必须使用继承的场合了。

继承

类型不仅仅只是描述了作用于一个对象集合上的约束条件,同时还有与其他类型之间的关系,两个类型可以有相同的特性和行为,但是其中一个类型可能比另一个含有更多的特性。

有两种方法可以使基类与导出类产生差异,第一种:直接在导出类中添加新方法,基类与导出类的关系称为is-like-a,基类无法访问新添加的方法。第二种:覆盖,直接在导出类中创建方法的新定义即可,基类与导出类之间的关系称为is-a。

多态

在处理类型的层次结构时,经常想把一个对象不当作它所属的特性类型来对待,而是将其当做其基类的对象来对待,这使得人们可以编写出不依赖于特定类型的代码。

在试图将导出类型的对象当作其泛化基类型对象来看待,那么编译器在编译时是不可能知道应该执行哪一段代码。一个非面向对象编程的编译器产生的函数调用会引起所谓的前期绑定,编译器将产生对一个具体函数名称的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。

为了解决这个问题,面向对象程序设计语言使用了后期绑定的概念。当向对象发送消息时,被调用的代码直到运行时才能确定,编译器确保被调用方法的存在,并对调用参数和返回值执行类型检查(无法提供此类保证的语言被称为弱类型的),但是并不知道将被执行的确切代码。

为了执行后期绑定,Java使用一小段特殊的代码来替代绝对地址调用,这段代码使用在对象中存储的信息来计算方法体的地址。在Java中动态绑定时默认行为,不需要添加额外的关键字来实现多态。

void doSomething(Shape shape) {
    shape.erase();
}

Circle circle = new Circle();
doSomething(circle);

Line line = new Line();
doSomething(line);

把将导出类看做是它的基类的过程称为向上转型。

在单根继承结构中的所有对象都具有一个共用接口,所以它们归根到底都是相同基本类型。另一种(C++所提供的)结构是无法确保所有对象都属于同一类型。前者使垃圾回收期的实现变得容易得多,不会因为无法确定对象的类型而陷入僵局,对于系统级操作(如异常处理)显得尤为重要。

泛型

在Java SE 5前的容器存储的对象只能是Object,在元素置入时向上转型为Object,此时丢失了原身份信息,在元素取出时需要向下转型,而这一步是危险的,转错可能会抛出运行时异常。

通过创建这样的容器,它知道自己所保存的对象的类型,从而不需要向下转型以及消除犯错误的可能,这种解决方案被称为参数化类型机制,参数化类型就是一个编译器可以自动定制作用于特定类型上的类。

对象的存储与回收

C++为了追求最大的执行速度,对象的存储空间和生命周期可以在编写程序时确定,这可以通过将对象置于堆栈或静态存储区来实现,这样必须在编写程序时知道对象确切的数量、生命周期和类型。

Java采用了动态内存分配方式,在被称为堆的内存池中动态地创建对象,直到运行时才知道需要多少对象,它们的生命周期如何,以及它们的具体类型是什么。在堆栈中创建存储空间和释放存储空间通过个需要一条汇编指令即可(栈指针移动),而创建堆存储空间的时间依赖于存储机制的实现。Java提供了垃圾回收器,它可以自动发现对象何时不再被使用,并继而销毁它。