第6章 Java 抽象类与接口


试读版(第一发布草稿版,作者-王新华) - 1 - 第 6 章 抽象类与接口 本章简介: 抽象一词的意思是从一堆事物中找到共同特征,然后用一个词汇描述这个特征,同时对这一类事物进行 指代。面向对象的类的定义就是一个进行抽象的过程,本章讲述的抽象类与接口是比类还要进一步抽象的结 果。其中的接口是表达更抽象概念的方法,使用它还能表达一个事物的多性。 还是一样要提醒读者,如果读者了解甚至精通 C 或 C++语言,请把 C 或 C++的特性抛到一边。虽然有些 时候 Java 看上去很像 C 或 C++,但是在基础概念上却有很多差别,因此并不适合直接套用。 依然深度提醒:本章的示例提供的源代码往往是几个类存放在一个 Java 源程序文件中,这是为了方便 书中演示示例,但读者在进行实际实验是请分为多个 Java 源程序文件。 所含章节:  抽象类  接口 教学建议: 抽象类与接口是进一步比较深入的 OOP 概念,要讲清楚它们存在的意义才能使得学生能够真正接受其 概念以及语法。也能帮助学生进一步灵活运用。教学时教学者,一定要从实体类的概念、继承的概念与机制 出发,给出诉求,一步一步逻辑推导出其中的缘由。 还是一样,要鼓励学生不断的主动的推导和猜想然后通过实验和对比实验将其证明。这样才能将讲述的 推导与逻辑概念深入理解,并融汇到不同的应用场景中。 科学的精神就在于:大胆猜测,小心求证。 要进一步带领学生学会使用 Java 编译器以及 Eclipse 来实验、来学习 Java 语法及其概念。 建议本章整体教学课时:6~9 小时。 学习目标: 1) 深刻理解与记忆抽象类的概念与语法,并能熟练运用; 2) 深刻的理解接口的概念与语法,并能熟练运用。 第 6.1 节 抽象类 无论是通过面向对象的方式描述现实世界还是 OOP 程序设计都会遇到这样的逻辑问题,无法得到某种 类型的对象,因为它太抽象、太概念化。 例如:当有人诉求,创建一个动物类型的对象。那么请问该如何给一个动物类型的对象呢?你可能会创 建一只猫、一条鱼、一只乌龟、一个唐老鸭等等。但提出诉求的人可以全部驳回:我要的是一个动物,而不 是某种具体的动物类型的对象。你此时可能会爆粗口骂他混账。但你有没有办法发现其中的逻辑问题呢? 对,这个人所诉求的是一个高度抽象的概念类型的一个对象,这个类型要涵盖很多子类以及孙子类型的 共同特征,这种高度抽象的概念类型是无法 new 出对象,创建对象对这种类型来说也是没有意义的。 像这种高度抽象概念化的类,就是抽象类。 试读版(第一发布草稿版,作者-王新华) - 2 - 在 Java 语言中,也支持语法级别的抽象类的约束。只需要在 class 关键字之前增加一个 abstract 关键 字。抽象类的定义体可以和普通类完全一样。与如上总结出来的逻辑特点一致,Java 中的抽象类是不能被 new 的(也就是说,不能创建这种类型的对象),即便其拥有很多可用的构造方法(因为这个和构造方法直 接因果关系)。 那么现在可以尝试定义一个抽象类,并尝试 new 一下这个抽象类,看看是否会有错误,参看如下截图: 图 6-1 抽象类不可以被实例化 抽象类不可以被实例化,那有啥用呢?它只是提出一个抽象概念,它被继承之后,如果其子类不是抽象 类,那其子类则可以被实例化啊。所以,单单从整体逻辑角度讲抽象类只是提供了一个统一的高度抽象。 6.1.1 抽象方法 如果只是概念上的抽象类,事实上并不能体现其高度抽象性。那抽象类的高度抽象到底会有什么样的体 现呢? 抽象类在实例域层面上并没有太多的抽象,但是在实例方法层面上就会有问题。来 看看如下的一个问题, 以 Bird 鸟类为例,如果要给这个抽象类 Bird 添加一个 public 的 fly 方法,请问这个 fly 方法应该怎么定义? 有人说:那就让鸟飞起来不就完了嘛。那好,来看看如下的问题。 抽象类不可以被实例化,但它可以拥有若干子类,Bird 类作为抽象类可以有 Chicken(鸡)、Turkey(火 鸡)、 Duck(鸭子)、Swan(天鹅)等等这些若干子类。这些子类可以不是抽象类。那这些子类都会从 Bird 类 继承得到一个 fly 方法,那么请问,这些子类的 fly 方法都应该一样么? 换句话说,在 Bird 中随意的“那就让鸟飞起来不就完了嘛”是科学的么?是谁告诉你的所有类型的鸟都 会飞上天的?而且有的只会滑翔,有的只会扑腾翅膀,有的会飞 100 公尺高,有的则会达到千米高空,你能 一次为所有的鸟类定义相同的飞行方式? 那你就会说,那简单啊,让这些子类将 fly 方法覆盖掉呗。那请问:是不是所有的 Bird 的子类都应该覆 盖这个 fly 方法? 如果不是,那意味着如果有一个新的鸟的类型,它默认就应该被定义某种飞的方式?不合理。 那肯定就是,那既然所有的 Bird 子类都要覆盖 Bird 中的 fly 方法,那 Bird 中有必要定义 fly 方法么? 天啊,好矛盾啊!其实不矛盾,看看如下的分析: 1) 作为 Bird 类,声明清楚所有的鸟都应该有 fly 方法,是合情合理的; 试读版(第一发布草稿版,作者-王新华) - 3 - 2) 作为高度抽象的 Bird 类,替所有鸟类代言,将 fly 方法实现了,却不合情理; 3) 所有从 Bird 继承得来的具体子类型,都要覆盖这个 fly 方法,将其实现。 那么,也就是说 Bird 类得说明:1)所有的鸟都得有 fly 方法;2)Bird 不实现这个方法;3)其子类如果 不是抽象类,就得覆盖并实现这个 fly 方法。 这种在抽象类出现的具有特殊意义的方法被称之抽象方法。1)只存在于抽象类的定义体中;2)用于说 明某一个实例方法的抽象概念,在抽象类中只声明方法名称、参数、返回值,但并不实现具体过程;3)作 为其子类(非抽象类,非孙子类)必须覆盖并将其实现。 Java 中在抽象类中定义抽象方法的语法非常简单,在方法的返回值前增加一个 abstract 关键字,然后不 写方法体即可。接下来看看如下示例代码: abstract class Bird { /** * 鸟类飞行的方法 */ public abstract void fly(); //抽象方法,凡是继承于该类的非抽象类都必须实现该方法 } 如果一个具体的类型继承于抽象类的话,那就必须实现抽象类的抽象方法,否则这个实体类(非抽象类) 的继承得到的这个方法就很有问题。于是 Java 语法就规定,继承了抽象类的实体类,必须实现抽象类中没 有被实现的抽象方法,否则被视为语法错误。参看如下截图: 图 6-2 如果实体类不实现从抽象父类继承得来的抽象方法,则会报错 这个子类必须实现父类未被实现的抽象方法可以被形容为:父债子偿。 那如果继承了抽象类的子类也是一个抽象类呢?此时,这个子抽象类可以选择实现或者不实现继承得 来的抽象方法。1)如果抽象子类实现了继承得来的抽象方法,那么该抽象子类的具体子类则可以不用再实 现这个抽象发方法了;2)如果抽象子类没有实现继承得来的抽象方法,那么该抽象子类的具体子类还要实 现这个抽象方法。 再来问一个问题:抽象方法的访问权限是否能为 private 呢?或是否能是包内访问权限呢? 这个问题是可以推理得到答案的: 试读版(第一发布草稿版,作者-王新华) - 4 - ∵ 抽象方法的含义是在抽象类中声明方法但不予实现,需要让其子类继承抽象方法然后实现。 ∴ 抽象方法需要被子类继承并能访问到 又∵ private 修饰的内容,其子类是不能访问的 ∴ 假设 private 能够修饰抽象方法,那么该抽象方法就不能被子类访问并将其覆盖实现 ∴ 结论:抽象方法不能被 private 修饰。 这个结论是正确的,而且在 Java 语法层面就已经做出了约束,参看如下截图: 图 6-3 抽象方法不得为 private 那包访问权限修饰的抽象方法或 protected 呢?这些当然是可以的。但是包访问权限修饰的抽象方法 自然不能跨包被继承,就会产生矛盾了,而且不可调和。 6.1.2 实验 1:充分实验抽象方法 要求: 1) 给出具有三层及三层以上继承关系的类,其中参杂抽象类与实体类,定义若干抽象方法,查看其实 现与被实现的语法要求。 2) 同时,尝试使用 Eclipse 的提示与帮助功能。 3) 证明并实验,抽象方法只能在抽象类中声明。 4) 证明并实验,包访问权限修饰抽象类的抽象方法不能跨包被实现。 5) 证明并实验,抽象方法是否可以是静态的。 6.1.3 小试牛刀——抽象类示例 在了解了抽象类与抽象方法,那么咱们来试用一下。示例内容从一只鸟开始。这里采用叙述一个故事的 方式一步一步来思考和推理如何完善一只鸟的类的设计。 6.1.3.1 客户需要一只鸟 客户提出一个需求:需要一只鸟的类型。这只鸟要拥有体重、性别的属性,以及一个 fly 方法。在刚刚 试读版(第一发布草稿版,作者-王新华) - 5 - 学了面向对象方法,知道如何定义一个类的你很快给出了结果: public class Bird { private double weight; private boolean sex; public double getWeight() { return weight; } public void setWeight(double weight) { this.weight = weight; } public boolean isSex() { return sex; } public void setSex(boolean sex) { this.sex = sex; } public void fly(){ System.out.println("飞上天,飞上天咯"); } public Bird(){ } } 这个设计很快就不合适了,因为客户提出不同的鸟又有不同的扩展,而且有不同的 fly 方法。例如:野 鸭子不仅会飞还会游泳;天鹅则能非得很高;公鸡并不会飞,但却能打鸣等等。 6.1.3.2 用继承来满足不同的鸟类要求 此时的你已经掌握了继承的概念与作用。好的,各种不同的鸟类可以继承已有的鸟类,这样就拥有所有 鸟类应有的体重和性别属性,以及飞行的方法了。但是,你的经理可能对这个设计并不满意,他提出了一个 至关重要的问题:按照这个设计,凡是继承于 Bird 的类,都必须覆盖 fly 方法咯?否则这个鸟就必然能够飞 起来? 显然,经理提出的这个问题必须得解决。于是,你进一步想到了将 Bird 定义为一个抽象类。 6.1.3.3 使用抽象类来定义鸟类 对的,才华横溢的你想到了使用抽象类来定义 Bird,将其中的 fly 方法定义为抽象方法。那么这就要求 只能通过继承 Bird 类定义实体类来实现 fly 方法,而且凡是继承于 Bird 的实体类也都必须实现 fly 方法。 经理将这个设计交由用户使用,但很快又发现了问题:在具体使用的时候,发现山雀、家燕、鸽子、杜 鹃等等这些鸟类的 fly 方法是一致的;老鹰、秃鹫、雕等等这些鸟类的 fly 方法是一致的。好了,这问题就很 明显了,这些使用当前设计的使用者们,定义一个新的实体类型继承了 Bird 类之后都得实现 fly 方法,不管 也不会知道有没有另一个程序员实现过完全相同的 fly 方法。导致了大量的相同 fly 方法的重复。 这次抽象类、抽象方法的设计不仅没有节省团队的劳动,还导致了大量的重复代码。 试读版(第一发布草稿版,作者-王新华) - 6 - 于是你的经理找到你谈话,打算和你深入聊聊关于 Bird 类的设计。 6.1.3.4 你提出的建议 可以给出几种大的类型的鸟类作为 Bird 类型的抽象子类,例如:猛禽、小体型鸟类、水生鸟类、不会飞 的鸟类等等。然后让这些抽象子类来实现某种类型的 fly 方法。以后再有更加具体的鸟类则直接继承于对应 的这些比 Bird 高一级别的抽象子类即可。例如:如果要增加山鹰这一类,则让其继承于猛禽一类即可。使用 UML 图可以如下描述: 图 6-4 重新设计的鸟类体系 不仅如此,第二层的抽象类还可以扩展一些抽象方法,例如:水生的鸟类中增加一个游泳的抽象方法。 经理:这是一个不错的想法,但我有以下几个问题。 1)使用这个体系其他人员(开发者或客户)应该继承 Bird 类还是其下的抽象子类呢?这是不是暴露给 使用者过多的抽象类了? 2)对于水生鸟类的抽象方法“游泳”是不是还会出现类似于鸟类中的抽象方法“飞”一样的窘境,是 不是意味着在水生鸟类下还得定义若干抽象子类? 3)对于公鸡若定义一个打鸣的抽象方法,是不是也是同样的问题? 4)假如猛禽类中的某个子类别想要借用公鸡类中“打鸣”方法的一个实现又该如何呢?这里依然存在 重复代码编写的可能啊。 6.1.3.5 翅膀是鸟类飞与不飞的原因 经理提出的这些问题确实比较棘手,若按照如上的设计继续发展下去,将会形成不堪设想的复杂继承关 系!到时候使用这个体系的程序开发人员或客户将面对很多庞杂的抽象类与实体类。而且也不能很好的避免 重复代码的编写。如何是好呢? 经理:要知道,鸟类到底会不会飞,事实上取决于它所拥有的翅膀。意思就是飞的方法的实现应该交给 翅膀来完成。 经理给出了一个设计理念: 首先,Bird 类依然是个抽象类。此时再定义另一个抽象类,这个抽象类包含一个抽象的 flyAway 方法, 类的名字就可以叫做翅膀。 试读版(第一发布草稿版,作者-王新华) - 7 - 然后,将这个翅膀安插在鸟类的定义中,在鸟类中给出 fly 方法的实现,那就是调用翅膀对象的 flyAway 方法。 接着,设计不同的翅膀类的具体子类,实现 flyAway 方法(例如:猛禽的翅膀类,小体型鸟的翅膀,不 会飞的翅膀类等等)。 最后,具体的鸟儿类继承于抽象的 Brid 类,在构造方法中提供不同的翅膀类的具体子类的对象即可。 针对该设计,画出对应的 UML 图如下: 图 6-5 使用了翅膀的鸟类 仔细推敲一下这个设计,可以看到如下几个特点: 1) 方法实现的复用不再完全依赖于对鸟类的继承,而是从鸟类中抽离出来,代理给了翅膀类; 2) 从事实方面考虑,飞行的方法教给翅膀是非常合理的; 3) 这样可以使得继承的层次变得非常少,翅膀类的继承关系几乎只有两层,而鸟类可以控制在两层; 4) 新衍生出来的鸟类只需要继承与统一的鸟类即可,然后考虑是否拥有已有的可用的翅膀,没有则再 定义一个翅膀类,有的话,则直接使用已有的翅膀类,极大的增加的代码的复用度和程序结构的清晰度。 5) 不只是鸟的飞行,就连:游泳、吃、跑、睡觉等等都可以用类似的方式来代理给不同的抽象类,相 互交叉的话更增加了代码的复用,而且不用担心程序规模进一步扩大,因为这是一个清晰的设计。 那么,就给出鸟类关于飞行和翅膀的示例代码: 例 6-1 package 第 1 节.第 3 小节; /** * 所有翅膀类的抽象 提醒 聪明且细心的读者可能已经发现了,这不就是第 5 章中讲述多态时提供的示例么?不同的是 那时还没有提出抽象类的概念。 试读版(第一发布草稿版,作者-王新华) - 8 - * @author 王新华 */ abstract class Wing{ public abstract void flyAway(); } /** * 老鹰翅膀类 * @author 王新华 */ class EagleWing extends Wing{ @Override public void flyAway() { System.out.println("站在悬崖起飞,瞭望大地上的猎物"); } } /** * 水生鸟翅膀类 * @author 王新华 */ class RiverWing extends Wing{ @Override public void flyAway() { System.out.println("从水面起飞,优美典雅,跨过万里晴空"); } } /****************************华丽的分割线*******************************/ /** * 抽象类,鸟类,这里省略了不关键的体重和性别,增加了翅膀 * @author 王新华 */ abstract class Bird { private Wing wing; /** * 飞行的方法代理给了 wing 对象 */ public void fly(){ this.wing.flyAway(); } public Bird(Wing wing){ this.wing = wing; } public Bird(){ } } /** * 老鹰类,使用老鹰的翅膀 * @author 王新华 试读版(第一发布草稿版,作者-王新华) - 9 - */ class Eagle extends Bird{ public Eagle(){ super(new EagleWing()); } } /** * 天鹅类,使用水鸟的翅膀 * @author 王新华 */ class Swan extends Bird{ public Swan(){ super(new RiverWing()); } } public class BirdTest { public static void main(String[] args) { Eagle e = new Eagle(); e.fly(); Swan s = new Swan(); s.fly(); } } 最终使用了经理提出的这个设计上的建议,不仅很好的解决了 Bird 的 fly 问题,就连 swim、run、eat 等 等方法的问题都轻松解决了。 6.1.4 实验 2:完成小试牛刀的示例 要求:根据如上的故事,将整个演进过程都实验一次。反复揣摩和实验,充分理解其中的逻辑。 第 6.2 节 接口 在阐述继承时,产生了子类型与父类型的继承关系,但是单单从分类学的角度来看的话,子类与父类的 关系是一种范围大小的包含与被包含关系。 在现实世界中,某种类型的事物是有可能属于不同类型的划分的(例如:笔者按照生物学的理论划分则 从属于人类,但从社会的角度来划分还有很多可以被划分的类型,笔者还是 IT 工程师类型的一个个体,是 儿子类型的一个个体,是中华人民共和国的公民等等,怎么定义笔者所从属的类型呢?)。来总结一下其中 的逻辑规律,是因为事物的类型划分站在了不同的出发点上,使用了不同的划分方式,于是同一个事物从属 于不同类型。 那如果按照这个逻辑来讲的话,要定义清楚某种事物的类型的话,就要搞清楚这种事物类型从属于多少 个父类,也就是说这个类型的继承于多个父类。从而拥有多父类的可能性。这就是多继承的原则。这在 C++ 需要特别注意 掌握以上内容,需要对第 5 章中关于类类型的转换原理、多态要比较熟练,需要读者反复的 琢磨和实验才能深入理解并进一步灵活运用。 试读版(第一发布草稿版,作者-王新华) - 10 - 中是被支持的。 这个看似也是非常合理的,但是在讲述 Java 的时候,提到过 Java 是不支持多继承的。可是为什么呢? 现在来简单分析一下: 一个类型从属于多个父类,这是可行的,但是如果多个父类都各自定义了相同名称与参数的多个方法呢? 那么子类从哪个父类中继承哪个这个名称与参数的方法呢?这里用伪代码展示这个矛盾的一个示例,假设 一个多继承场景下的矛盾: 人类{ public 吃(){ 吃蛋白、淀粉、维生素和水; 消化后长身体; } } IT 工程师类{ public 吃(){ 书是人类进步的阶梯; 看本书,新技能 get; } } 笔者类 extends 人类, IT 工程师类{ } //此时笔者类应该继承得到一个“吃”方法吧,但是,笔者类的“吃”方法会是哪一个呢? 这就会出现一种模棱两可的状态。这也就是 Java 不愿意通过多继承的方式表达某种类型从属于多个类 型的语义。 回顾一下上一节中 Bird(鸟)和 Wing(翅膀)的示例。对于 Wing 翅膀来说有些比较奇怪的地方,Wing 只拥有一个抽象方法 flyAway,是一个甚至比抽象类更抽象的结果。提到这个,是想让这个来帮助表达的一 个类型从属于多个类型的语义。 什么?什么意思? 可以想象,一个类继承于多个实体类确实会遇到类似于如上的矛盾冲突,但是如果一个类继承于多个只 包含抽象方法的抽象类的话,这个矛盾就不存在了! 你会问:那还是存在多个抽象父类声明了同样的抽象方法的可能啊。 对,这个事实还是存在的,但并不引发矛盾!为什么?因为它们是抽象的,即便是同样的抽象方法,但 实体类只实现一次,这两个抽象父类的抽象方法不就都满足了么? OK,这样的话就既可以表达类似于多继承的语义,又能够避免如上的矛盾。这个方法很赞吧。Java 的语 法体系也是这样干的,但要更加高明一些。 可以想一下,抽象类虽然抽象(不能被实例化,可以包含抽象方法),但是它依然可以包含定义好的实 例方法啊,如果编程者不注意,还是有出现如上矛盾的可能性啊。而且还会产生语义上的二义性(编写子类 的程序员,需要知道继承的多个父类哪些是抽象的,哪些是非抽象的)。 那就在语法层面上彻底解决这个问题!那就是形成一套专门用于表达更抽象概念的一个语法结构,它只 包含抽象方法,一个类确实只可以有一个父类(无论是否为抽象父类),但却可以归属于多个这种表示抽象 概念的语法结构。这个在 Java 中被称之为接口,关键字是 interface。 试读版(第一发布草稿版,作者-王新华) - 11 - 如果是笔者的话,也会考虑这样做。 6.2.1 Interface 语法 此时对接口做一总结:接口就是用以表示比抽象类更加抽象的概念。这是笼统的逻辑定位的定义。要按 照如上达成类似于“多继承”的诉求,那这个接口还应该有如下的特征:  所包含的方法只能是抽象方法(实例方法、静态方法都不存在)。  不需要包含实例域(也不能包含实例域)。  使用另一种关键字表示类与接口的关系,不再用 extends 关键字表达,以免混淆,使用的关键字是 implements,被称之为实现。  一个类可以实现多个接口。 在满足这些基本特性的时候,Java 语法中还规定:  接口的抽象方法必须是 public 的,即便不写访问权限修饰词,默认也是 public。  接口的抽象方法前的 abstract 关键字可写可不写,因为接口包含的方法只是抽象方法,写不写都一 样。  接口唯一可以包含 final 修饰的静态域(也就是静态常变量)。 前 3 点加后 3 点,这就是 Java 中的 Interface(接口)的全部要点了。来看看如下一个完整的示例: 例 6-2 /*动物抽象类*/ abstract class Animal{ private double weight; private boolean sex; private int age; public abstract void run(); //作为动物应该会跑 public abstract void eat(); //作为动物应该会吃 public Animal(){ } } /*人类*/ class Human extends Animal{ private String name; //实现作为动物应该会的 跑 public void run() { System.out.println("双脚直立行走"); } //实现作为动物应该会的 吃 public void eat() { System.out.println("吃熟食,会烹饪"); } } /*软件工程师接口*/ interface SoftwareEngineer{ public void designSoftware(); //设计软件 public void service(); //提供服务(指的是为企业和客户) } 试读版(第一发布草稿版,作者-王新华) - 12 - /*公民接口*/ interface Citizen{ public static String CONTRY = "中华人民共和国"; //这里即便不写 public 也默认为 public, 不写 static 也默认为 static,不写 final 也默认为 final public void service(); //提供给服务(指的是作为公民的基本义务) public void payTaxes(); //纳税 public void enjoyment(); //享有权利 } /*IT 专栏作家类*/ class ITAuthor extends Human implements SoftwareEngineer , Citizen{ //来自于接口口 Citizen public void payTaxes() { System.out.println("我是合法的纳税人"); } //来自于接口口 Citizen public void enjoyment() { System.out.println("享有法律赋予的平等公平的权利"); } //来自于接口口 SoftwareEngineer public void designSoftware() { System.out.println("呦~ 设计软件,不用怕!"); } //来自于接口 SoftwareEngineer 与 Citizen public void service() { System.out.println("作为 SoftwareEngineer 则为企业创造营收"); System.out.println("作为 Citizen 则为国家和社会创造价值"); } public ITAuthor(){ } } 仔细阅读如上示例,对比如下的 UML 图。 图 6-6 接口示例 此 UML 图中描述类与接口的“实现”与“被实现”的关系时,使用的箭头的形状与继承是一样的,只 试读版(第一发布草稿版,作者-王新华) - 13 - 不过线段换成了虚线。 6.2.1.1 实验 3 :将如上的接口示例完成 要求:可以在如上的接口示例中根据自己的理解增加相关的内容,通过反复实验与对比实验来观察接口 与抽象类、实体类之间的差别。 并尝试推理接口存在的逻辑本质。 6.2.2 接口与类之间的关系 一个类实现一个接口之后就会与这个接口产生“实现”与“被实现”的关系。接口提出了一个概念,并 通过抽象方法说明了这个概念,实现了接口的类则将接口提出的概念落到实处。 一个类实现了一个接口之后,这个类也就属于这个接口所提出的概念范畴中,从某种意义上讲:这个类 所描述的范畴被这个接口所描述的概念范畴所包含!这事实上与继承最终表示的父类包含子类的逻辑是一 致的。那么,换句话说,实现了某个接口的类与被实现的接口之间,也是一种继承与被继承关系。 这样的话,那么继承关系中所达成的各种可能性,在接口中也就会有所体现。特别重要的就是类型兼容 特性。还是上一小节的 ITAuthor 的示例。看看如下的代码示例: ITAuthor wangxh = new ITAuthor(); System.out.println(wangxh instanceof Human); //打印结果 true System.out.println(wangxh instanceof SoftwareEngineer); //打印结果 true System.out.println(wangxh instanceof Citizen); //打印结果 true 如上代码说明了 wangxh 作为 ITAuthor 类的一个实例,事实上也是其父类与祖先类的实例,而且还是 ITAuthor 实现了的接口的实例。这就直接从 Java 语法层面证明了如上的推论。 不仅如此,接口因为表现的一种非常特殊的概念,而这个概念也是涵盖了一个门类的事物的,因此也被 认为是一种类型,能够使用接口说明的类型定义对象变量。参看如下示例: ITAuthor wangxh = new ITAuthor(); Human h = wangxh; //可行,当然,通过 h 只可以看到 wangxh 的 Human 的一面 SoftwareEngineer e = wangxh; //可行,当然,通过 e 只可以看到 wangxh 的 SoftwareEngineer 的一面 Citizen c = wangxh; //可行,当然,通过 c 只可以看到 wangxh 的 Citizen 的一面 6.2.3 接口之间的关系 接口描述的是高度抽象的概念,即便是如此为了让一些概念的描述能够被复用,或是层层进行抽象,依 然有从范围较大的笼统概念出发到范围较小的细分概念,那么细分概念与笼统概念就会出现包含与被包含 的关系,例如: 宗教是一个大范围的概念,而东正教、天主教、伊斯兰教、犹太教等等是分属于宗教概念下的子概念。 它们与宗教概念之间有什么关系呢? 对的,是继承关系。 接口与接口之间可以拥有继承关系。子接口会继承父接口所定义的抽象方法,那么在子接口中就不需要 将父接口已经定义的抽象方法再写一遍了,这样既能保证概念上的包含与被包含关系,又能节省代码的书写 (代码复用)。看如下的示例: 试读版(第一发布草稿版,作者-王新华) - 14 - 例 6-3 接口继承的示例 /*宗教 接口*/ interface Religion{ public void inherit(); //传承 public void interpretationNature(); //解释自然 public void socialIdeology(); //社会意识形态 } /*东正教 接口 继承于 宗教接口,因此其包含的抽象方法包括宗教中定义的抽象方法*/ interface OrthodoxEaster extends Religion{ public void believeInGod(); } /*伊斯兰教 接口继承于 宗教接口,因此其包含的抽象方法包括宗教中定义的抽象方法*/ interface Islam extends Religion{ public void believeInAllah(); } /*东正教教徒 实现 东正教接口 */ class OrthodoxEasterBeliever implements OrthodoxEaster{ public void inherit() { System.out.println("由专业的神职人员传承教义,帮助凡人与上帝对话"); } public void interpretationNature() { System.out.println("上帝创造了世界"); } public void socialIdeology() { System.out.println("要遵循上帝的教诲,这样才能升天堂"); } public void believeInGod() { System.out.println("要听从上帝的安排"); } } 6.2.4 小结 接口一词站在整个计算机科学的角度来看是一个非常笼统的概念,它表示的是一个事物与另一个事物 之间的使用与被使用的关系、数据传递的关系。在编程技术角度来看,接口往往表示方法或函数名称、返回 值和参数。 在 OOP 中,通常用接口一词表示一个类或对象暴露给外部可调用的方法,但这个接口并非本节中讲述 interface。 当我们回到本章中讲述的 interface 的概念,来与笼统的接口概念比较的话,你会发现他们的逻辑出发 点事实上是不一样的。本节中的 interface 表示的是高度抽象的概念,其作用是能够让某种类型分属于多种 概念。然而,本节中的 interface 也确实强制那些实现了接口的类暴露一些方法,这样就与笼统的接口概念相 关了。 另外,很多教材会直接将 Java 的接口机制描述为弥补其不能多继承的缺陷,甚至直接将其称之为 Java 的多继承。这种说法其实并不准确,首先需要真正明白多继承存在的理由与原因,然后需要知道纯粹的多继 承的矛盾,最后再来理解接口,你会发现:哦~原来接口是为了让一个类包含多个不同的方面(任何事物以 及其分类都是多面的)。 试读版(第一发布草稿版,作者-王新华) - 15 - 再次强调,Java 中的接口是表达高度抽象概念的语义。一个类可以实现多个接口表达的概念。 6.2.5 实验 4 要求: 1) 接口是否可以继承于实体类或抽象类呢?答案显而易见,是不可以的。至于为什么,读者可以做一 个逻辑推理进行证明。然后再做相关的代码实验再一次证明。 2) 回顾一下抽象类小试牛刀的讲述与实验,其中的翅膀抽象类就只包含一个抽象方法,那么既然如此 的话,为何不将其定义为接口呢?实验内容:将实验 2 中的翅膀定义为接口,重新将该示例完成一次。同时, 增加嘴巴、脚、五脏六腑的接口,帮助鸟实现这些方法:跑、跳、游泳、吃、消化、成长。 第 6.3 节 本章总结 本章讲述了 Java 的语法机制中又一个非常强悍的部分,那就是使用抽象类与接口表示抽象概念。无论 是 extends 还是 implements 其实都有继承的语义在其中。 6.3.1 是否达到学习目标 目标1) 是否完成了本章中所有的实验和示例?并理解了示例表达的含义? 目标2) 是否理解了抽象类的概念及其语法?并能在具体使用是判断所要定义的类是否应该是抽象类? 目标3) 是否理解了讲述抽象类时所描述的故事? 目标4) 是否理解了接口所要表达的语义及其语法?
还剩14页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

saoqin2005

贡献于2015-03-17

下载需要 5 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf