《Java编程思想》读书笔记之第8章-多态
Posted 二木成林
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Java编程思想》读书笔记之第8章-多态相关的知识,希望对你有一定的参考价值。
第8章 多态
几个问题:
- 多态是什么
- 多态有什么用?
多态的作用是消除类型之间的耦合关系。
- 什么地方用多态
8.1 再论向上转型
在论向上转型中,书中给出这样一个例子:
Note.java
package 第8章_多态.第1节_再论向上转型;
public enum Note {
MIDDLE_C,C_SHARP,B_FLAT;
}
Instrument.java
package 第8章_多态.第1节_再论向上转型;
public class Instrument {
public void play(Note n){
System.out.println("Instrument.play()");
}
}
Wind.java
package 第8章_多态.第1节_再论向上转型;
public class Wind extends Instrument{
// 重写父类的play()方法
public void play(Note n){
System.out.println("Wind.play() "+n);
}
}
Music.java
package 第8章_多态.第1节_再论向上转型;
public class Music {
public static void tune(Instrument i){
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute=new Wind();
tune(flute);
}
}
/**
* 打印结果:
* Wind.play() MIDDLE_C
*/
对于上面的结果类,最该关注的应该是Instrument类和Wind类,因为Wind类继承自Instrument类,并且重写了play()方法。但可能对于它们之间的关系还是不太直观,看下图:
然后才是关注Music类,在本类的tune()方法中,参数本该是Instrument类型的,但在main中调用传入的却是Wind类型的,即继承自Instrument类型的,不需要任何类型转换。
所以,如果在一个函数中形参是一个基类(父类),其下有子类,那么在调用该函数时,传入子类也是可以的,而不需要任何转换。
8.1.1 忘记对象类型
看标题可能不明白忘记对象类型是什么意思,即在上例的tune()方法中为什么不直接设置方法的参数是Wind类型,而要设置成Instrument类型。如果参数是Wind类型似乎更加直观,更加明确该方法的含义,但这样会引出来一个问题:需要为Instrument类下的所有子类都编写一个新的tune()方法。
具体示例代码如下:
Music2.java
package 第8章_多态.第1节_再论向上转型.第1目_忘记对象类型;
import 第8章_多态.第1节_再论向上转型.Instrument;
import 第8章_多态.第1节_再论向上转型.Note;
import 第8章_多态.第1节_再论向上转型.Wind;
class Stringed extends Instrument {
// 继承自Instrument类并重写了父类的play()方法
public void play(Note n) {
System.out.println("Stringed.play() " + n);
}
}
class Brass extends Instrument {
@Override
public void play(Note n) {
// 继承自Instrument类并重写了父类的play()方法
System.out.println("Brass.play() " + n);
}
}
public class Music2 {
public static void tune(Wind i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Stringed i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Brass i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
Stringed violin = new Stringed();
Brass frenchHorn = new Brass();
tune(flute);
tune(violin);
tune(frenchHorn);
}
}
/**
* 打印结果:
* Wind.play() MIDDLE_C
* Stringed.play() MIDDLE_C
* Brass.play() MIDDLE_C
*/
可以看到为每个Instrument类的子类都写了一个tune()方法,但方法内参数都是Instrument子类,如Stringed、Brass类,相当于重载,但是如果一旦有一个类继承了Instrument类,那么就要重新写一个tune()方法,需要大量工作。
所以说,方法内参数基类(父类)类型而不是导出类(子类)类型,可以节约工作,产生相同的效果,而且只与基类打交道,减少错误的产生。
下面代码是把Music2.java类的代码重写下,即在方法内参数用基类类型,看看代码省去了多少工作量:
Music3.java
package 第8章_多态.第1节_再论向上转型.第1目_忘记对象类型;
import 第8章_多态.第1节_再论向上转型.Instrument;
import 第8章_多态.第1节_再论向上转型.Note;
import 第8章_多态.第1节_再论向上转型.Wind;
class Stringed3 extends Instrument {
// 继承自Instrument类并重写了父类的play()方法
public void play(Note n) {
System.out.println("Stringed.play() " + n);
}
}
class Brass3 extends Instrument {
@Override
public void play(Note n) {
// 继承自Instrument类并重写了父类的play()方法
System.out.println("Brass.play() " + n);
}
}
public class Music3 {
public static void tune(Instrument i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
Stringed violin = new Stringed();
Brass frenchHorn = new Brass();
tune(flute);
tune(violin);
tune(frenchHorn);
}
}
/**
* 打印结果:
* Wind.play() MIDDLE_C
* Stringed.play() MIDDLE_C
* Brass.play() MIDDLE_C
*/
能够看到上述代码产生的结果和Music2.java产生的结果一摸一样,并且只写了一个tune()方法,而Instrument类下的所有子类都能够使用。
8.2 转机
在Music.java中,调用Wind.play()产生的确实是我们需要的结果。查看tune()方法:
public static void tune(Instrument i){
i.play(Note.MIDDLE_C);
}
可以查到参数是一个Instrument类型的,接受一个Instrument引用。那么思考下这个问题:编译器是如何知道是哪个对象传入的呢?是Wind还是Brass亦或者Stringed呢?
编译器是不知道的,而这涉及到了绑定。
8.2.1 方法调用绑定
(本节了解绑定这个概念即可)
绑定的定义:将一个方法调用同一个方法主体关联起来称为绑定。
前期绑定:在程序执行前进行绑定。在C中有所涉及。
后期绑定:在程序运行时根据对象的类型进行绑定。这里就是根据传入的是Wind类型还是Brass类型或者Stringed类型来找到正确的方法体并加以调用。Java中除了static和final方法之外,其他方法都是后期绑定。
所以final除了可以防止其他人覆盖该方法之外,还可以有效地“关闭”动态绑定。
8.2.2 产生正确的行为
一旦知道Java中所有方法都是通过动态绑定实现多态这个事实之后,我们就可以编写只与基类打交道的程序代码了,并且这些代码对所有的导出类都可以正确运行。或者换一种说法,发送消息给某个对象,让该对象去断定应该做什么事。
以上是关于《Java编程思想》读书笔记之第8章-多态的主要内容,如果未能解决你的问题,请参考以下文章