0%

第七章 多形性

摘要:

  • 上溯造型中方法的绑定
  • 抽象类和方法
  • 接口

方法的绑定(Binding)

  1. 早期绑定:由编译期和链接程序,在程序运行以前执行绑定
  2. 后期绑定:使对象在上溯造型后依然能调用正确的方法(衍生类的方法)

    若一种语言实现了后期绑定(动态绑定、运行期绑定),同时必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。不同的语言对后期绑定的实现方法是有所区别的。但我们至少可以这样认为: 它们都要在对象中安插某些特殊类型的信息。

java中所有的方法声明都是后期绑定,除非一个方法被声明为final。final能防止其他人覆盖那个方法,可有效的“关闭”动态绑定,或者告诉编译期不需要进行动态绑定。(这样编译期就可为final方法调用生成更高效的代码)

1
2
Shape s = new Circle();
s.draw(); //调用Circle.draw(),后期绑定


抽象类和方法

  1. abstract 关键字;
  2. 包含抽象方法的类叫抽象类。如果一个类里包含了一个或多个抽象方法,类就必须声明为abstract;
  3. 抽象类不能创建对象。(抽象类的目的是为从它衍生出的所有类都创建一个通用接口,而不是创建抽象类的对象,编译期会保证抽象类的“纯洁性”,我们不必担心误用它);
  4. 在衍生类中必须提供方法定义。如果从一个抽象类继承,而且想生成新类型的一个对象,就必须为基础类中的所有抽象方法提供方法定义。如果不这样做(可以选择不这样做),则衍生类也会是抽象的,而且编译期会强迫我们用abstract关键字标注那个类的“抽象”本质;
  5. 如果一个类没有任何抽象方法,而我们想禁止那个类的所有实例,也可以将一个类声明为抽象类;


接口 interface

  1. implements关键字;
  2. “纯”抽象类。常把接口用于建立类与类之间的一个协议(“protocol”);
  3. 接口也包含了基本数据类型的数据成员,但它们都默认static finalcompile-time constant)。它们不能是“空白final”,但可初始化成非常数表达式:
    1
    int rint = (int)Math.random();
  4. 可在interface 关键字钱添加public 关键字(但只有接口定义在同名的一个文件内),或者将其省略,默认friendly;
  5. 接口中的方法默认为public,也可明确定义为public。所以在实现一个接口时,来自接口的方法必须定义为public(否则编译期会报错);

接口的多重继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
interface CanFight{
void fight();
}
interface CanSwim{
void swim();
}
interface CanFly{
void fly();
}
class ActionCharacter{
public void fight() {}
}
class Hero extends ActionCharacter
implements CanFight, CanSwim, CanFly{
public void swim() {}
public void fly() {}
}
public class Adventure {
static void t(CanFight x) { x.fight(); }
static void u(CanSwim x) { x.swim(); }
static void v(CanFly x) { x.fly(); }
static void w(ActionCharacter x) { x.fight(); }
public static void main(String[] args){
Hero hero = new Hero();
t(hero);
u(hero);
v(hero);
w(hero);
}
}

P.S.

  1. 合并具体类和接口时,必须是具体类在前,接口在后(否则编译期会报错),基础类只能有一个,接口可以有多个(用逗号隔开),例:
    1
    2
    class Hero extends ActionCharacter
    implements CanFight, CanSwim, CanFly {}
  2. 尽管Hero 没有为 fight()明确地提供 一个定义,但定义是随同ActionCharacter 来的,所以这个定义会自动提供,我们可以创建Hero 的对象;
  3. 使用接口最重要的一个原因:能上溯造型至多个基础类
  4. 使用接口的第二个原因:防止客户程序员创建这个类的一个对象(与使用抽象类相同);
  5. 选择接口还是抽象类:
    如果事先知道某个东西会成为基础类,那么优先选择接口。只有在必须使用方法定义或成员变量的时候,才考虑抽象类

通过集成扩展接口

通过继承技术,可方便地为一个接口添加新的方法声明,也可以将几个接口合并成一个新接口(可用extends引用多个基础接口,用逗号隔开)。

1
2
interface Hero 
extends CanFight, CanSwim, CanFly {}

常数分组

由于置入接口的所有字段都自动具有static final属性,所以接口是对常数值进行分组的一个好工具,它具有与C的enum相似的效果。(JDK1.5引入了新类型——枚举 enum)

1
2
3
4
5
public interface Number {
int
ONE = 1, TWO = 2,
THREE = 3, FOUR = 4;
}

P.S.

  1. 根据java命名规则,static final的基本数据类型(即编译期常量)全部大写,用下划线分隔多个单词;
  2. 可以使用Number.ONE 来对值进行引用。但得到的只是一个int,所以并不安全(不像c++的enum那样拥有额外的类型安全性),(只提供了int值,所以本来想用来代表一个月份的int变量可能实际获得一个整数值)。疑惑???

    如果不想放弃安全性,可构建像下面这样的类:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Numbers {
    private String name;
    private Numbers(String s) { name = s; }
    public String toString() { return name; }
    public static final Numbers
    ONE = new Numbers("One"),
    TWO = new Numbers("Two");
    public static final Numbers[] numbers = { ONE, ONE, TWO };//提供一个多余的ONE,是偏移量加一

    public static void main(String[] args){
    Numbers num = Numbers.ONE;
    System.out.println(num);
    num = Numbers.numbers[1];
    System.out.println(num);
    System.out.println(num == Numbers.TWO);
    System.out.println(num.equals(Numbers.TWO));
    }
    }