第七章 多形性
多形性的作用是消除类型之间的耦合关系;
一、上溯造型
1、一个对象可作为它自己的类型使用,或者作为它的基础类型的一个对象使用;
2、上溯造型:取得一个对象句柄,并将其作为基础类型句柄使用的行为;
3、继承类的对象上溯造型会缩小继承类的接口;
4、绑定(Binding):将一个方法调用同一个方法主体连接到一起;
(1)早期绑定:在程序运行以前执行绑定(由编译器和链接程序);
(2)后期绑定:绑定在运行期间进行,以对象的类型为基础。也叫作“动态绑定”或 “运行期绑定”;
(3)动态绑定要在对象中安插某些特殊类型的信息;
5、Java 中绑定的所有方法都采用后期绑定技术,若被声明成final,则通常不必决定是否应进行后期绑定——它是自动发生的;
6、把一个方法声明成final能防止其他人覆盖那个方法。有效地“关闭”动态绑定,或者告诉编译器不需要进行动态绑定;
7、上溯造型的句型:基础类 对象名 = new 继承类();
二、抽象类
1、“过载”和“覆盖”的区别:
(1)“过载”是指同一样东西在不同的地方具有多种含义;
(2)“覆盖”是指它随时随地都只有一种含义,只是原先的含义完全被后来的含义取代了。
2、若想通过该通用接口处理一系列类,就需要创建一个抽象类。
3、所有与基础类(抽象类)声明的签名相符的衍生类方法,都可以通过动态绑定机制进行调用是表达接口,而不是表达一些具体的实施细节。所以创建一个抽象类的对象是没有意义的,通常应禁止用户那样做。
4、抽象方法:只有一个声明,没有方法主体。abstract void X();
5、包含了抽象方法的类叫做“抽象类”。若一个类里包好了一个或多个抽象方法,就必须定义为抽象类;
6、如果从一个抽象类继承,而且想生成新类型的一个对象,就必须为基础类中的所有抽象方法提供方法定义,否则衍生类也会是抽象的,而且编译器会强迫我们用abstract 关键字标志那个类的“抽象”本质;
7、即使不包括任何abstract 方法,亦可将一个类声明成“抽象类”,即一个类没必要拥有任何抽象方法,而且我们想禁止那个类的所有实例;
三、接口
1、接口允许一个类的基本形式:方法名、自变量列表以及返回类型,但不规定方法主体。接口也包含了基本数据类型的数据成员,但它们都默认为static 和final;
2、接口只提供一种形式,并不提供实施的细节。采用了一个特定接口的所有代码都知道对于那个接口可能会调用什么方法。所以我们常把接口用于建立类和类之间的一个“协议”;
3、创建一个接口,要将class替换为interface。与类相似,我们可在 interface 关键字的前面增加一个 public 关键字(但只有接口定义于同名的一个文件内);或者将其省略,营造一种“友好的”状态;
4、使用 implements(实现)关键字生成与一个特定的接口(或一组接口)相符的类;
具体实现了一个接口以后,就获得了一个普通的类,可用标准方式对其进行扩展。在实现一个接口的时候,来自接口的方法必须定义成public。否则的话,它们会默认为“友好的”,而且会限制我们在继承过程中对一个方法的访问——Java 编译器不允许我们那样做。
5、“普通”类、“抽象”类还是一个“接口”的区别?
6、在一个衍生类中,只能从最多一个非接口继承,剩余的所有基本元素都必须是“接口”。我们将所有接口名置于 implements 关键字的后面,并用逗号分隔它们。每个接口都会成为一个独立的类型,可对其进行上溯造型;
7、按这种形式合并一个具体类与接口的时候,具体类必须首先出现,然后才是接口(否则编译器会报错)。
class Hero extends ActionCharacter (具体类)implements CanFight, CanSwim, CanFly(多个接口) { }
8、如果事先知道某种东西会成为基础类,那么第一个选择就是把它变成一个接口。只有在必须使用方法定义或者成员变量的时候,才应考虑采用抽象类;
9、利用继承技术,为一个接口添加新的方法,或者合并多个接口,得到一个新接口,如
interface Vampire extends DangerousMonster, Lethal { }//仅在继承接口时才可使用
interface DangerousMonster extends Monster {新的方法}
10、由于置入一个接口的所有字段都自动具有 static 和final 属性,所以接口是对常数值进行分组的一个好工具,它具有与C 或C++的enum 非常相似的效果;
11、字段不能是“空白 final”,但可初始化成非常数表达式;
12、字段是 static 的,它们会在首次装载类之后、以及首次访问任何字段之前获得初始化;
13、字段并不是接口的一部分,而是保存于那个接口的 static 存储区域中;
四、内部类
1、内部类:将一个类定义置入另一个类定义中。
2、一个外部类拥有一个特殊的方法,它会返回指向一个内部类的句柄
3、若想在除外部类非 static 方法内部之外的任何地方生成内部类的一个对象,必须将那个对象的类型设为“外部类名.内部类名”,
4、普通(非内部)类不可设为private 或 protected——只允许 public 或者“友好的”。
5、内部类的类型
(1) 在一个方法内定义的类
public class Parcel4 {
public Destination dest(String s) {
//PDestination 不可从 dest()的外部访问。
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
//在返回语句中发生的上溯造型——除了指向基础类Destination 的一个句柄之外,没有任何东西超出dest()的边界之外。
return new PDestination(s);
}
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Destination d = p.dest("Tanzania");
}
}
(2) 在方法的一个作用域内定义的类
public class Parcel5 {
private void internalTracking(boolean b) {
if(b) {
class TrackingSlip { //随同其他东西一起编译,没有创建条件
private String id;
TrackingSlip(String s) { id = s;}
String getSlip() { return id; }
}
TrackingSlip ts = new TrackingSlip("slip");
String s = ts.getSlip();
}
// 在定义它的那个作用域之外,不可使用
//! TrackingSlip ts = new TrackingSlip("x");
}
public void track() { internalTracking(true); }
public static void main(String[] args) {
Parcel5 p = new Parcel5();
p.track();
}
}
(3) 一个匿名类,用于实现一个接口
public class Parcel6 {
public Contents cont() {
return new Contents() { //匿名类
private int i = 11;
public int value() { return i; }
}; //;标志着用于包含匿名类的那个表达式的结束
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
Contents c = p.cont();
}
}
匿名内部类的语法其实要表达的是:
class MyContents extends Contents {
private int i = 11;
public int value() { return i; }
}
return new MyContents();
(4) 一个匿名类,用于扩展拥有非默认构建器的一个类
public class Parcel7 {
public Wrapping wrap(int x) { //x向基础类带自变量构建器传参数
return new Wrapping(x) {
public int value() { return super.value() * 47; }
};
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Wrapping w = p.wrap(10);
}
}
(5) 一个匿名类,用于执行字段初始化 ;
匿名类不能拥有一个构建器,可在定义自己的字段时进行初始化:
public class Parcel8 {
// 若试图定义一个匿名内部类,并想使用在匿名内部类外部定义的一个对象,则编译器要求外部对象为final属性。这正是我们将dest()的自变量设为final 的原因。如果忘记这样做,就会得到一条编译期出错提示。
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania");
}
}
(6) 一个匿名类,通过实例初始化进行构建(匿名内部类不可拥有构建器)
public class Parcel9 {
public Destination dest(final String dest, final float price) {
return new Destination() {
private int cost;
// Instance initialization for each object:
{
cost = Math.round(price);
if(cost > 100)
System.out.println("Over budget!");
}
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel9 p = new Parcel9();
Destination d = p.dest("Tanzania", 101.395F);
}
}
在实例初始化模块中,我们可看到代码不能作为类初始化模块(即if 语句)的一部分执行。所以实际上,一个实例初始化模块就是一个匿名内部类的构建器我们不能对实例初始化模块进行过载处理,所以只能拥有这些构建器的其中一个。
6、在一个方法甚至一个任意的作用域内创建内部类。有两方面的原因促使我们这样做:
(1) 正如前面展示的那样,我们准备实现某种形式的接口,使自己能创建和返回一个句柄。
(2) 要解决一个复杂的问题,并希望创建一个类,用来辅助自己的程序方案。同时不愿意把它公开。
7、链接到外部类
创建自己的内部类时,那个类的对象同时拥有指向封装对象(这些对象封装或生成了内部类)的一个链接。所以它们能访问那个封装对象的成员,内部类拥有对封装类所有元素的访问权限。
interface Selector {
//知道自己是否位于最末尾(end()),能观看当前对象(current()Object),以及能够移至 Sequence 内的下一个对象(next() Object)。
boolean end();
Object current();
void next();
}
public class Sequence {
//Sequence 只是一个大小固定的对象数组,有一个类将其封装在内部
private Object[] o;
private int next = 0;
public Sequence(int size) { o = new Object[size]; }
public void add(Object x) {
//我们调用add(),以便将一个新对象添加到 Sequence 末尾(如果还有地方的话)
if(next < o.length) {
o[next] = x;
next++;
}
}
private class SSelector implements Selector {
//SSelector 是一个私有类,它提供了 Selector 功能
//o 是个不属于 SSelector 一部分的句柄,而是位于封装类里的一个private 字段。
//内部类可以从封装类访问方法与字段
int i = 0;
public boolean end() {return i == o.length; }
public Object current() {return o[i]; }
public void next() { if(i < o.length) i++; }
}
//通过对getSelector()的一个调用生成一个Selector。并用它在Sequence中移动,同时选择每一个项目。
public Selector getSelector() { return new SSelector(); }
public static void main(String[] args) {
Sequence s = new Sequence(10);
for(int i = 0; i < 10; i++)
s.add(Integer.toString(i));
Selector sl = s.getSelector();
while(!sl.end()) {
System.out.println((String)sl.current());
sl.next();
}
}
}
一个内部类可以访问封装类的成员。内部类必须拥有对封装类的特定对象的一个引用,而封装类的作用就是创建这个内部类。随后,当我们引用封装类的一个成员时,就利用那个(隐藏)的引用来选择那个成员。我们现在可以理解内部类的一个对象只能与封装类的一个对象联合创建。在这个创建过程中,要求对封装类对象的句柄进行初始化。若不能访问那个句柄,编译器就会报错。
8、static 内部类
(1)内部类的对象默认持有创建它的那个封装类的一个对 象的句柄。
(2)static 内部类意味着:
为创建一个 static 内部类的对象,我们不需要一个外部类对象。
不能从 static 内部类的一个对象中访问一个外部类对象。
(3)static 成员只能位于一个类的外部级别,所以内部类不可拥有static 数据或static 内部类。
(4)倘若为了创建内部类的对象而不需要创建外部类的一个对象,那么可将所有东西都设为static。同时也必须将内部类设为static。
(5)通常,我们不在一个接口里设置任何代码,但 static 内部类可以成为接口的一部分。static 内部类只位于接口的命名空间内部:
(6)可考虑用一个static 内部类容纳自己的测试代码
9、引用外部类对象
(1)若想生成外部类对象的句柄,就要用一个点号以及一个this 来命名外部类。获得的句柄会自动具备正确的类型(这会在编译期间检查并核实,所以不会出现运行期的开销)。
(2)让其他某些对象创建它某个内部类的一个对象,必须在 new 表达式中提供指向其他外部类对象的一个句柄,必须利用外部类的一个对象生成内部类的一个对象就象下面这样:
public class Parcel11 {
class Contents {
private int i = 11;
public int value() { return i; }
}
public static void main(String[] args) {
Parcel11 p = new Parcel11();
Parcel11.Contents c = p.new Contents();
}
}
(3)除非已拥有外部类的一个对象,否则不可能创建内部类的一个对象。这是由于内部类的对象已同创建它的外部类的对象“默默”地连接到一起。然而,如果生成一个static 内部类,就不需要指向外部类对象的一个句柄。
10、从内部类继承
由于内部类构建器必须同封装类对象的一个句柄联系到一起,所以从一个内部类继承的时候,情况会稍微变得有些复杂。这儿的问题是封装类的“秘密”句柄必须获得初始化,而且在衍生类中不再有一个默认的对象可以连接。解决这个问题的办法是采用一种特殊的语法,明确建立这种关联:
class WithInner {
class Inner {}
}
public class InheritInner extends WithInner.Inner {
//InheritInner 只对内部类进行了扩展,没有扩展外部类
//! InheritInner() {} //不能只是传递封装对象的一个句柄
InheritInner(WithInner wi) {
wi.super();//提供了必要的句柄,以便程序正确编译
}
public static void main (String[] args){
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
11、内部类的覆盖,“明确”地从内部类继承: public class Yolk extends Egg2.Yolk
12、内部类标识符
(1)内部类也必须生成相应的.class 文件,用来容纳与它们的Class 对象有关的信息。这些文件或类的名字遵守一种严格的形式:先是封装类的名字,再跟随一个$,再跟随内部类的名字。例如,由InheritInner.java 创建的.class 文件包括:
InheritInner.class
WithInner$Inner.class
WithInner.class
(2)如果内部类是匿名的,那么编译器会简单地生成数字,把它们作为内部类标识符使用。
(3)若内部类嵌套于其他内部类中,则它们的名字简单地追加在一个$以及外部类标识符的后面。
13、为什么要用内部类:控制框架
(1)一个“应用程序框架”是指一个或一系列类,它们专门设计用来解决特定类型的问题。为应用应用程序框架,我们可从一个或多个类继承,并覆盖其中的部分方法。我们在覆盖方法中编写的代码用于定制由那些应用程序框架提供的常规方案,以便解决自己的实际问题。
(2)“控制框架”属于应用程序框架的一种特殊类型,受到对事件响应的需要的支配;主要用来响应事件的一个系统叫作“由事件驱动的系统”,“图形用户界面”(GUI),它几乎完全是由事件驱动的。
(3)这里正是内部类大显身手的地方。它们允许我们做两件事情:
在单独一个类里表达一个控制框架应用的全部实施细节,从而完整地封装与那个实施有关的所有东西。 内部类用于表达多种不同类型的action(),它们用于解决实际的问题。除此以外,后续的例子使用了 private 内部类,所以实施细节会完全隐藏起来,可以安全地修改。
内部类使我们具体的实施变得更加巧妙,因为能方便地访问外部类的任何成员。
14、构建器的调用顺序
(1) 调用基础类构建器。
(2) 按声明顺序调用成员初始化模块。
(3) 调用衍生构建器的主体。
15、当我们在衍生类的时候,必须能假定基础类的所有成员都是有效时候,必须覆盖衍生类中的finalize()方法——如果已经设计了某个特殊的清除进程,要求它必须作为垃圾收集的一部分进行。覆盖衍生类的 finalize()时,务必记住调用 finalize()的基础类版本。否则,基础类的初始化根本不会发生。
16、我们可对收尾的顺序进行控制。采用的最佳顺序与初始化顺序正好相反。
(1) 在采取其他任何操作之前,为对象分配的存储空间初始化成二进制零。
(2) 就象前面叙述的那样,调用基础类构建器。此时,被覆盖的draw()方法会得到调用(的确是在
RoundGlyph 构建器调用之前),此时会发现 radius 的值为 0,这是由于步骤(1)造成的。
(3) 按照原先声明的顺序调用成员初始化代码。
(4) 调用衍生类构建器的主体。
17、设计构建器时一个特别有效的规则是:用尽可能简单的方法使对象进入就绪状态;如果可能,避免调用任何方法。在构建器内唯一能够安全调用的是在基础类中具有final 属性的那些方法(也适用于private 方法,它们自动具有final 属性)
18、下塑造形在运行期间对类型进行检查的行为叫作“运行期类型标识” (RTTI)。