《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章-多态的主要内容,如果未能解决你的问题,请参考以下文章

《Java编程思想》读书笔记之第7章-复用类

[读书笔记]Java编程思想

[读书笔记]Java编程思想第8章之多态

<java编程思想>第一章读书笔记二

Java编程思想读书笔记_第8章

《Java编程思想》阅读笔记之第4章-控制执行流程