“阿米戈,你喜欢鲸鱼吗?”
“鲸鱼?没感觉,没听说过呀。”
“它和牛长不多,不过比牛体块大些,而且会游泳。顺便一提,鲸鱼源自牛。嗯,或者至少他们拥有共同的祖先。那不重要。”
“请注意,我想告诉你 OOP 的另一个非常有用的功能:多态。它有四个特征。”
1) 方法重写。
假如你在为某个游戏写 Cow 类。其中有很多成员变量和方法。这个类的对象可以做很多动作:walk(行走)、eat(进食)、sleep(睡觉)。Cow 行走的时候,身上的铃铛还会响。假设,你已经把这个类里的所有内容实现到每一个细节。
突然,客户说他想发布新的游戏版本,在那个版本里,所有动作都发生在海洋里,而主角是一头鲸鱼。
你开始着手设计 Whale 类,并意识到它与 Cow 类只有细微的差别。这两个类的逻辑非常相似,于是你决定使用继承。
Cow 类很适合做父类:它已经包含所有必要的变量和方法。你要做的只是添加鲸鱼游泳的技能。但问题是:你的鲸鱼有四条腿、两个犄角和一个铃铛。毕竟,Cow 实施了这个功能。你该怎么办呢?
方法重写前来救场。如果我们继承的方法里有新类不需要的内容,我们可以用另一方法来替换。
具体怎们做呢?在子类中,我们声明想要更改的方法(与父类相同的方法签名)。然后,我们给这个方法写一个新代码。这样就可以了。就好像父类里的老方法并不存在一样。
下面是此代码的工作方式:
代码 | 说明 |
---|---|
|
这里我们定义了 2 个类:Cow 和 Whale 。Whale 继承 Cow 。
|
|
这个代码会在屏幕上显示我是一头奶牛。 |
|
这个代码会在屏幕上显示我是一条鲸鱼。 |
继承 Cow
并重写 printName
后,Whale
类实际上拥有如下数据和方法:
代码 | 说明 |
---|---|
|
对于老方法,我们一无所知。 |
“事实上,这正是我想要的。”
2) 但这并不是全部。
“假设 Cow
类有一个 printAll
方法,它会调用另外两个方法。之后,代码的工作方式就如下所示:”
屏幕会显示:
我浑身白色
我是一条鲸鱼
代码 | 说明 |
---|---|
|
|
|
屏幕会显示: 我浑身白色 我是一条鲸鱼 |
请注意,在 Whale 对象上调用 Cow 类的 printAll() 方法时,Whale 的 printName() 方法就会被使用,而不是 Cow 的。
重要的不是写入方法的类,重要的是此方法被调用时所在的对象的类型(类)。
“我明白了。”
“你可以只继承和重写非 static 方法。Static 方法并未被继承,因此,无法重写。”
如下就是我们应用继承并重写方法后 Whale 类的样子:
代码 | 说明 |
---|---|
|
这是我们应用继承和重写方法后 Whale 类的样子。对于原先的 printName 方法,我们一无所知。 |
3) 类型转换。
还有一个更有意思的知识点。由于子类会继承父类的所有方法和数据,所以,此子类的对象可以是父类的引用变量(以及父类的父类,父类的父类的父类等的变量,直至 Object 类)。请考虑以下示例:
代码 | 说明 |
---|---|
|
屏幕会显示: 我浑身白色。 |
|
屏幕会显示: 我浑身白色。 |
|
屏幕会显示: Whale@da435a. toString() 方法继承自 Object 类。 |
“太棒了。但是我们为什么要用它?”
“这是一个很重要的特征。将来,你会认识到它真的非常、非常重要。”
4) 后联编(动态分派)
下面是个后联编的例子:
代码 | 说明 |
---|---|
|
屏幕会显示: 我是一条鲸鱼。 |
|
屏幕会显示: 我是一条鲸鱼。 |
请注意,决定具体我们调用哪个 printName 方法(Cow 类或 Whale 类的方法)的不是变量的类型,而是那个变量所引用的对象的类型。
Cow 变量存储了 Whale 对象的引用,Whale 类中定义的 printName 方法会被调用。
“加入这些并不是为了清楚起见。”
“是的,并没有那么清楚。切记下面这条重要原则:”
你可以对一个变量调用的方法集实际上取决于该变量的类型。但是具体哪种方法/实现会被调用,则又取决于该变量所引用的对象的类型/类。
“我会去试试。”
“你会时不时地碰到这种情况,所以,你很快就会明白其中的道理,并且永远不会忘记。”
5) 类型转换。
转换在引用类型(类)中的运作方式不同于其在原始类型里运作的方式。但是,拓宽转换和窄化转换也可以应用于引用类型。请考虑以下示例:
拓宽转换 | 说明 |
---|---|
|
典型的拓宽转换。现在,你只可以在 Whale 对象上调用 Cow 类定义的方法。 编译器只让你使用 Cow 变量来调用这些 Cow 类型定义的那些方法。 |
窄化转换 | 说明 |
---|---|
|
典型的窄化转换且带有类型检查。Cow 类型的 cow 变量存储了 Whale 对象的引用。 我们检查发现事实就是这样,然后执行(窄化)类型转换。这也被称为类型转换。 |
|
你也可以对引用类型执行窄化转换,而不对对象进行类型检查。 这里,如果 cow 变量指向 Whale 对象以外的其他东西,它就会抛出一个异常 (InvalidClassCastException)。 |
6) 现在来看一些有趣的东西。调用原始方法。
有时候,重写继承的方法时,你不想全都替换它。有时,你只想在里面加一点点东西。
这时,你如果真的希望用新方法的代码来调用同样的方法,但却是针对基类的。Java 可以帮到你。具体做法如下: super.method()
。
下面是一些示例:
代码 | 说明 |
---|---|
|
|
|
屏幕会显示: 我浑身白色 下面这句是错的:我是一头奶牛 我是一条鲸鱼 |
“嗯。这是个教训。我的金属耳朵就要融掉了。”
“是的,事情从不简单。这会是你看到的最难的材料。教授说会提供一些其他作者写的材料的链接,所以,如果还有不明白的地方,你可以通过这些材料来扫除盲点。”
GO TO FULL VERSION