Java 面向对象概述


本文部分摘自 On Java 8程序员


面向对象编程

在说起面向对象时,不得不提到另外一个概念:抽象。编程的最终目的是为了解决某个问题,问题的复杂度直接取决于抽象的类型和质量。早期的汇编语言经过对底层机器做轻微抽象,到后来的 C 语言又是对汇编语言的抽象。尽管如此,它们的抽象原理依然要求咱们着重考虑计算机的底层结构,而非问题自己编程

面向对象编程(Object-Oriented Programming OOP)是一种编程思惟方式和编码架构。不一样于传统的面向过程编程,面向对象编程把问题空间(实际要解决的问题)中的元素以及它们在解决方案空间中的表示以一种具备广泛性的方式表达出来,这种表示被称做对象(Object)。对于一些在问题空间没法对应的对象,咱们还能够添加新的对象类型,以配合解决特定的问题。总而言之,OOP 容许咱们根据问题来描述问题,而不是根据解决问题的方案。每一个对象都有本身的状态,而且能够进行特定的操做,换言之,它们都有本身的特征和行为架构

根据面向对象编程的概念,能够总结出五大基本特征:编程语言

  • 万物皆对象
  • 程序是一组对象,能够互相传递消息以告知彼此应该作什么
  • 每一个对象都有本身的存储空间,以容纳其余对象
  • 每一个对象都有一种类型
  • 同一个类的全部对象能接收相同的消息

对对象更简洁的描述是:一个对象具备本身的状态、行为和标识,这意味着对象有本身的内部数据(状态)、方法(行为),并彼此区分(每一个对象在内存中都有惟一的地址)函数


接口

全部对象都是惟一的,但同时也是具备相同的特征和行为的对象所归属的类的一部分。这种思想被应用于面向对象编程,并在程序中使用基本关键字 class 来表示类型,每一个类都有本身的通用特征和行为。建立好一个类后,可生成许多对象,这些对象做为要解决问题中存在的元素进行处理。事实上,当咱们在进行面向对象程序设计时,面临的最大的一项挑战就是:如何在问题空间与方案空间的元素之间创建理想的一对一映射关系?工具

若是没法创建有效的映射,对象也就没法作真正有用的工做,必须有一种方法能向对象发出请求,令其解决一些实际的问题,好比完成一次交易、打开一个开关等。每一个对象仅能接收特定的请求,咱们向对象发出的请求是经过它的接口定义的编码

好比有一个电灯类 Light,咱们能够向 Light 对象发出的请求包括打开 on、关闭 off,所以在 Light 类咱们须要定义两个方法 on() 和 off(),而后建立一个 Light 类型的对象,并调用其接口方法设计

也行你已经发现了,对象经过接受请求并反馈结果,所以咱们能够将对象当作是某项服务的提供者,也就是说你的程序将为用户提供服务,而且它能还能经过调用其余对象提供的服务来实现这一点。咱们的最终目标是开发或调用工具库中已有的一些对象,提供理想的服务来解决问题对象

更进一步,咱们须要将问题进行分解,将其抽象成一组服务,并有组织地划分出每个功能单1、做用明确且紧密的模块,避免将太多功能塞进一个对象里。这样的程序设计能够提升代码的复用性,同时也方便别人阅读和理解咱们的代码继承


封装

咱们把编程的侧重领域划分为研发和应用两块。应用程序员调用研发程序员构建的基础工具类作快速开发,研发程序员开发一个工具类,该工具类仅向应用程序员公开必要的内容,并隐藏内部实现的细节,这样能够有效避免工具类被错误使用和更改。显然,咱们须要某些方法来保证工具类的正确使用,只有设定访问控制,才能从根本上解决这个问题

所以,使用访问控制的缘由有如下两点:

  1. 让应用程序员不要触碰他们不该该触碰的部分
  2. 类库的建立者能够在不影响他人使用的状况下完善和更新工具库

Java 提供了三个显式关键字来设置类的访问权限,分别是 public(公开)、private(私有)和 protected(受保护),这些访问修饰符决定了谁能使用它们修饰的方法、变量或类

  1. public

    表示任何人均可以访问和使用该元素

  2. private

    除了类自己和类内部的方法,外界没法直接访问该元素

  3. protected

    被 protected 修饰的成员对于本包和其子类可见。这句话有点太笼统了,更具体的归纳应该是:

    • 基类的 protected 是包内可见的
    • 若子类与基类不在同一包中,那么子类实例能够访问从基类继承而来的 protected 方法,而不能访问基类实例的 protected 方法
  4. default

    若是不使用前面三者,默认就是 default 权限,该权限下的资源能够被同一包中其余类的成员访问


复用

代码和设计方案的复用性是面向对象的优势之一,咱们能够经过重复使用某个类的对象来实现复用性,例如,将一个类的对象的做为另外一个类的成员变量使用。所以,新构成的类能够是由任意数量和任意类型的其余对象构成,这里涉及到了组合和聚合两个概念:

  • 组合(Composition)常常用来表示拥有关系(has-a relationship)例如,汽车拥有引擎。组合关系中,整件拥有部件的生命周期,因此整件删除时,部件必定会跟着删除
  • 聚合(Aggregation)表示动态的组合。聚合关系中,整件不会拥有部件的生命周期,因此整件删除时,部件不会删除

使用组合能够为咱们的程序带来极大的灵活性。一般新建的类中,成员对象会使用 private 访问权限,这样应用程序员没法对其直接访问,咱们就能够在不影响客户代码的前提下,从容地修改那些成员。咱们也能够在运行时改变成员对象,从而动态地改变程序的行为。下面提到的继承并不具有这种灵活性,由于编译器对经过继承建立的类进行了限制


继承

对象的概念为编程带来便利,它容许咱们将各式各样的数据和功能封装到一块儿,这样能够恰当表达问题空间的概念,而不用受制于必须使用底层机器语言

经过 class 关键字,能够造成编程语言中的基本单元。遗憾的是,这样作仍是有不少问题:在建立一个类以后,即便另外一个新类与其具备类似的功能,你仍是不得不从新建立一个新类。若是咱们能利用现有的数据类型,对其进行克隆,再根据状况进行添加和修改,那就方便许多了。继承正是为此而设计,但继承并不等价于克隆。在继承过程当中,如原始类(基类、父类)发生了变化,修改过的克隆类(子类、派生类)也会反映出这种变化

基类通常会有多个派生类,并包含派生自它的类型之间共享的全部特征和行为。后者可能比前者包含更多的特征,并能够处理更多消息(或者以不一样的方式处理它们)

使用继承,你将构建一个类型层次结构,来表示你试图解决的某种类型的问题。常见的例子是形状,每一个形状都有大小、颜色、位置等等,每一个形状能够绘制、擦除、移动等,还能够派生出具体类型的形状,如圆形、正方形、三角形等等。派生出的每一个形状均可以具备附加的特征和行为,例如,某些形状能够翻转,计算形状面积的公式互不相同等等

类型层次结构体现了形状之间的类似性和差别性,你不须要在问题描述和解决方案描述之间创建许多中间模型。从现有类型继承并建立新类型,新类型不只包含现有类型的全部成员(尽管私有成员被隐藏起来并不可访问),更重要的是它复制了基类的接口。也就是说,基类对象能接收的消息派生类对象也能接收。若是基类不能知足你的需求,你能够在派生类添加更多的方法,甚至改变现有基类方法的行为(覆盖),只需在派生类从新定义这个方法便可


多态

在处理类的层次结构时,一般把一个对象当作是它所属的基类,而不是把它当成具体类,经过这种方式,咱们能够编写出不局限于特定类型的代码。例如上述形状的例子,方法操纵的是通用的形状,而不关心具体是圆仍是三角形什么的。全部形状均可以被绘制、擦除和移动。所以方法向其中任何表明形状的对象发送消息都没必要担忧对象如何处理信息

这种能力改善了咱们的设计,减小了软件的维护代价。若是咱们把派生对象类型统一当作是它自己的基类,编译器在编译时就没法准确地获知具体是哪一个形状被绘制,那一种车正在行驶,这正是关键所在:当程序接受这种消息时,程序员并不关心哪段代码会被执行,绘图方法能够平等地应用到每种可能的形状上,形状会依据自身的具体类型执行恰当的代码

所以,咱们就能添加一个新的不一样执行方式的子类而不须要更改调用它的方法,更利于程序扩展。那么编译器如何肯定该执行哪部分的代码呢?通常来讲,编译器不能进行函数调用,对于非 OOP 编译器产生的函数调用会引发所谓的早期绑定,这意味着编译器生成对特定函数名的调用,该调用会被解析为将执行的代码的绝对地址。而面向对象语言使用了一种后期绑定的概念,当向对象发送信息时,被调用的代码直到运行时才肯定,编译器要作的只是确保方法存在,并对参数和返回值执行类型检查,但并不知道要执行的确切代码

为了执行后期绑定,Java 使用一个特殊的代码位来代替绝对调用,这段代码使用对象中存储的信息来计算方法主体的地址。所以,每一个对象的行为根据特定代码位的内容而不一样。当你向对象发送消息时,对象知道该如何处理这条消息。在某些语言如 C++ 必须显式地授予方法后期绑定属性的灵活性,而在 Java 中,动态绑定是默认行为,不须要额外的关键字来实现多态性

发送消息给对象时,若是程序不知道接收的具体类型是什么,但最终执行是正确的,这就是对象的多态性。面向对象的程序设计语言是经过动态绑定的方式来实现对象的多态性的,编译器和运行时系统会负责控制全部的细节。咱们只须要知道要作什么,以及如何利用多态性更好地设计程序


对象建立与生命周期

在使用对象时要注意的一个关键问题就是对象的建立和销毁方式。每一个对象的生存都须要资源,尤为是内存。当对象再也不被使用时,咱们应该及时释放资源,清理内存

然而,实际的情形每每要复杂许多。咱们怎么知道什么时候清理这些对象呢?也许一个对象在某一系统中处理完成,但在其余系统可能还没处理完成。另外,对象的数据在哪?如何控制它的生命周期?在 C++ 设计中采用的观点是效率第一,它将这些问题的选择权交给了程序员。程序员在编写程序时,经过将对象放在栈或静态存储区域中来肯定内存占用和生存空间。相对的,咱们也牺牲了程序的灵活性

Java 使用动态内存分配,在堆内存中动态地建立对象。在这种方式下,直到程序运行咱们才能知道建立的对象数量、生存类型和时间。在堆内存上开辟空间所需的时间可能比栈内存要长(但并不必定),但动态分配论认为:对象一般是复杂的,相比于对象建立的总体开销,寻找和释放内存空间的开销微不足道。对于对象的生命周期问题,在 C++ 中你必须以编程方式肯定什么时候销毁对象,而 Java 的垃圾收集机制能自动发现再也不被使用的对象并释放相应的内存空间,使得 Java 的编码过程比用 C++ 要简单许多

相关文章
相关标签/搜索