您的位置:首页 > 编程语言 > Java开发

JAVA编程思想学习 --- 第七章 (多行性)

2017-09-11 11:10 218 查看
1.多行性的概念
    “多形性”( Polymorphism)从另一个角度将接口从具体的实施细节中分离出来,亦即实现了“是什么”与“怎样做”两个模块的分离。利用多形性的概念,代码的组织以及可读性均能获得改善。此外,还能创建“易于扩展”的程序。无论在项目的创建过程中,还是在需要加入新特性的时候,它们都可以方便地“成长”

2. 上溯造型
    将一个对象作为它自己的类型使用,或者作为它的基础类型的一个对象使用。取得一个对象句柄,并将其作为基础类型句柄使用的行为就叫作“上溯造型” —— 因为继承树的画法是基础类位于最上方

3.方法调用的绑定(后期绑定)
     后期绑定它意味着绑定在运行期间进行,以对象的类型为基础。后期绑定也叫作“动 态绑定”或“运行期绑定”。若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间判断对象 的类型,并分别调用适当的方法。也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去 调查,找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为:
它们都要在对象中安插某些特殊类型的信息。
     Java 中绑定的所有方法都采用后期绑定技术,除非一个方法已被声明成final。这意味着我们通常不必决定 是否应进行后期绑定—— 它是自动发生的。

4.抽象类和方法
    在基础类中的方法的作用是为从它衍生出去的所有类都创建一个通用接口,之所以要建立这个通用接口,唯一的原因就是它能为不同的子类型作出不同的表示.它为我们建立了一种基本形式,使我们能定义在所有衍生类里“通用”的一些东西。所以基础类中的方法主体是不需要的,针对这个问题,
Java 专门提供了一种机制,名为“抽象方法”。它属于一种不完整的方法,只含有一个声明,没有方法主体。
    包含了抽象方法的一个类叫作“抽象类”。如果一个类里包含了一个或多个抽象方法,类就必须指定成abstract(抽象)。否则,编译器会向我们报告一条出错消息。
    若一个抽象类是不完整的,那么一旦有人试图生成那个类的一个对象,编译器又会采取什么行动呢?由于不能安全地为一个抽象类创建属于它的对象,所以会从编译器那里获得一条出错提示。通过这种方法,编译器可保证抽象类的“纯洁性”,我们不必担心会误用它。
    如果从一个抽象类继承,而且想生成新类型的一个对象,就必须为基础类中的所有抽象方法提供方法定义。如果不这样做(完全可以选择不做),则衍生类也会是抽象的,而且编译器会强迫我们用abstract 关键字标志那个类的“抽象”本质。
    即使不包括任何 abstract 方法,亦可将一个类声明成“抽象类”。如果一个类没必要拥有任何抽象方法,而且我们想禁止那个类的所有实例,这种能力就会显得非常有用。

5.接口
    “ interface”(接口)关键字使抽象的概念更深入了一层。我们可将其想象为一个“纯”抽象类。它允许创建者规定一个类的基本形式:方法名、自变量列表以及返回类型,但不规定方法主体。接口也包含了基本数据类型的数据成员,但它们都默认为 static 和 final。接口只提供一种形式,并不提供实施的细节。
接口这样描述自己:“对于实现我的所有类,看起来都应该象我现在这个样子”。因此,采用了一个特定接口的所有代码都知道对于那个接口可能会调用什么方法。这便是接口的全部含义。所以我们常把接口用于建立类和类之间的一个“协议”。有些面向对象的程序设计语言采用了一个名为“ protocol”(协议)的关键字,它做的便是与接口相同的事情。
    为创建一个接口,请使用 interface 关键字,而不要用 class。与类相似,我们可在 interface 关键字的前面增加一个 public 关键字(但只有接口定义于同名的一个文件内);或者将其省略,营造一种“友好的”状态。
    为了生成与一个特定的接口(或一组接口)相符的类,要使用 implements(实现)关键字。我们要表达的意思是“接口看起来就象那个样子,这儿是它具体的工作细节”。除这些之外,我们其他的工作都与继承极为相似。
    可决定将一个接口中的方法声明明确定义为“ public”。但即便不明确定义,它们也会默认为 public。所以在实现一个接口的时候,来自接口的方法必须定义成public。否则的话,它们会默认为“友好的”,而且会限制我们在继承过程中对一个方法的访问—— Java 编译器不允许我们那样做。
    接口中定义的字段会自动具有 static 和 final 属性。它们不能是“空白 final”,但可初始化成非常数表达式。由于字段是 static 的,所以它们会在首次装载类之后、以及首次访问任何字段之前获得初始化。

6.单继承多实现
    在一个衍生类中,我们并不一定要拥有一个抽象或具体(没有抽象方法)的基础类。如果确实想从一个非接口继承,那么只能从一个继承。剩余的所有基本元素都必须是“接口”。我们将所有接口名置于 implements关键字的后面,并用逗号分隔它们

7.内部类
    在 Java 1.1 中,可将一个类定义置入另一个类定义中。这就叫作“内部类”。内部类对我们非常有用,因为利用它可对那些逻辑上相互联系的类进行分组,并可控制一个类在另一个类里的“可见性”。
    若想在除外部类非 static 方法内部之外的任何地方生成内部类的一个对象,必须将那个对象的类型设为“外部类名.内部类名”
    我们见到的内部类好象仅仅是一种名字隐藏以及代码组织方案。尽管这些功能非常有用,但似乎并不特别引人注目。然而,我们还忽略了另一个重要的事实。创建自己的内部类时,那个类的对象同时拥有指向封装对象(这些对象封装或生成了内部类)的一个链接。所以它们能访问那个封装对象的成员—— 毋需取得任何资格。除此以外,内部类拥有对封装类所有元素的访问权限

8.static内部类
    为正确理解 static 在应用于内部类时的含义,必须记住内部类的对象默认持有创建它的那个封装类的一个对象的句柄。然而,假如我们说一个内部类是 static 的,这种说法却是不成立的。 static 内部类意味着:
    (1) 为创建一个 static 内部类的对象,我们不需要一个外部类对象。
    (2) 不能从 static 内部类的一个对象中访问一个外部类对象。
    但在存在一些限制:由于 static 成员只能位于一个类的外部级别,所以内部类不可拥有 static 数据或static 内部类。倘若为了创建内部类的对象而不需要创建外部类的一个对象,那么可将所有东西都设为static。为了能正常工作,同时也必须将内部类设为 static。
    为直接创建内部类的一个对象,不能象大家或许猜想的那样—— 采用相同的形式,并引用外部类名Parcel11。此时,必须利用外部类的一个对象生成内部类的一个对象:
    Parcel11.Contents c = p.new Contents();
    因此,除非已拥有外部类的一个对象,否则不可能创建内部类的一个对象。这是由于内部类的对象已同创建它的外部类的对象“默默”地连接到一起。然而,如果生成一个static 内部类,就不需要指向外部类对象的一个句柄。

9.从内部类继承
    由于内部类构建器必须同封装类对象的一个句柄联系到一起,所以从一个内部类继承的时候,情况会稍微变得有些复杂。这儿的问题是封装类的“秘密”句柄必须获得初始化,而且在衍生类中不再有一个默认的对象可以连接。解决这个问题的办法是采用一种特殊的语法,明确建立这种关联: 
//: InheritInner.java
// Inheriting an inner class
class WithInner {
class Inner {}
}
public class InheritInner
extends WithInner.Inner {
//! InheritInner() {} // Won't compile
InheritInner(WithInner wi) {
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}

} ///:~
从中可以看到, InheritInner 只对内部类进行了扩展,没有扩展外部类。但在需要创建一个构建器的时候,默认对象已经没有意义,我们不能只是传递封装对象的一个句柄。此外,必须在构建器中采用下述语法:
    enclosingClassHandle.super();
    它提供了必要的句柄,以便程序正确编译
    当我们从外部类继承的时候,没有任何额外的内部类继续下去。然而,仍然有可能“明确”地从内部类继承:

10.内部类标识符
    由于每个类都会生成一个.class 文件,用于容纳与如何创建这个类型的对象有关的所有信息(这种信息产生了一个名为 Class 对象的元类),所以大家或许会猜到内部类也必须生成相应的.class 文件,用来容纳与它们的 Class 对象有关的信息。这些文件或类的名字遵守一种严格的形式:先是封装类的名字,再跟随一个$,再跟随内部类的名字。例如,由 InheritInner.java
创建的.class 文件包括:
    InheritInner.class
    WithInner$Inner.class
    WithInner.class
    如果内部类是匿名的,那么编译器会简单地生成数字,把它们作为内部类标识符使用。若内部类嵌套于其他内部类中,则它们的名字简单地追加在一个$以及外部类标识符的后面。
    内部类大显身手的地方。它们允许我们做两件事情:
    (1) 在单独一个类里表达一个控制框架应用的全部实施细节,从而完整地封装与那个实施有关的所有东西。内部类用于表达多种不同类型的 action(),它们用于解决实际的问题。除此以外,后续的例子使用了private 内部类,所以实施细节会完全隐藏起来,可以安全地修改。
    (2) 内部类使我们具体的实施变得更加巧妙,因为能方便地访问外部类的任何成员。若不具备这种能力,代码看起来就可能没那么使人舒服,最后不得不寻找其他方法解决。

11.构建器的作用及调用顺序
    检查对象是否得到了正确的构建。一个衍生类只能访问它自己的成员,不能访问基础类的成员(这些成员通常都具有private 属性)。只有基础类的构建器在初始化自己的元素时才知道正确的方法以及拥有适当的权限。所以,必须令所有构建器都得到调用,否则整个对象的构建就可能不正确。那正是编译器为什么要强迫对衍生类的每个部分进行构建器调用的原因。在衍生类的构建器主体中,若我们没有明确指定对一个基础类构建器的调用,它就会“默默”地调用默认构建器。如果不存在默认构建器,编译器就会报告一个错误(若某个类没有构建器,编译器会自动组织一个默认构建器)
    这意味着对于一个复杂的对象,构建器的调用遵照下面的顺序:
    (1) 调用基础类构建器。这个步骤会不断重复下去,首先得到构建的是分级结构的根部,然后是下一个衍生类,等等。直到抵达最深一层的衍生类。
    (2) 按声明顺序调用成员初始化模块。
    (3) 调用衍生构建器的主体
  
  前一节讲述的初始化顺序并不十分完整,而那是解决问题的关键所在。初始化的实际过程是这样的:
    (1) 在采取其他任何操作之前,为对象分配的存储空间初始化成二进制零。
    (2) 就象前面叙述的那样,调用基础类构建器。此时,被覆盖的draw()方法会得到调用(的确是在RoundGlyph 构建器调用之前),此时会发现 radius 的值为 0,这是由于步骤(1)造成的。
    (3) 按照原先声明的顺序调用成员初始化代码。
    (4) 调用衍生类构建器的主体
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: