第八章:多态

多态

  • 在面向对象的程序设计语言中,多态是继数据抽象继承之后的第三种基本特征。多态也被称作动态绑定后期绑定运行时绑定

再论向上转型

  • 对象既可以作为它自己本身的类型使用,也可以作为它的基类类型使用。把某个对象的引用视为对其基类类型的引用的做法称为向上转型。来看下面的例子:
public class People extends Animal {
    String type = "人类";
    void run() {
        System.out.println(type + "run");
    }
    public static void test(Animal a) {
        a.run();
    }
    public static void main(String args[]) {
        test(new People());
        test(new Animal());
    }
}
class Animal {
    String type = "动物";
    void run() {
        System.out.println(type + "run");
    }
}
----------------- 运行结果:
人类run
动物run
  • 仔细看一下test方法的定义,它接收一个Animal的引用(所以可以传入Animal实例Animal导出类的实例的引用),这个很好理解。那函数体:a.run();该如何执行呢?a在我的函数定义里是一个Animal类型的引用,那为什么我传入Animal实例Animal导出类的实例的引用结果的不都是动物run?这里就是多态在起作用了。
  • 首先,将一个方法调用同一个方法主体关联起来被称作绑定。此处将run()与相应的方法体关联起来的行为就是绑定(其实调用test方法也是一种绑定,这个后面再说)。若在程序执行前进行绑定,叫做前期绑定。在运行时根据对象的类型进行绑定则被称为后期绑定,即多态。即使你将一个对象向上转型,由于对象中安置了某种类型信息(这个需要后续章节说明),能够在方法调用时得知其原来的类型,也就能找到正确的方法体。
  • 但并不是所有的方法都属于动态绑定,在Java中,static方法final方法(private方法属于final方法)是属于前期绑定的。啥意思呢,就比如这个test方法,他在没有运行的时候就已经得知会调用那个方法了。你肯定会反驳我,不调它调谁啊,龙总就一个?那我们再来个例子:

public class People extends Animal {
    String type = "人类";
    void run() {
        System.out.println(type + "run");
    }
    static void run2() {
        System.out.println("People run");
    }
    public static void test(Animal a) {
        a.run();
        a.run2();
    }
    public static void main(String args[]) {
        test(new People());
        test(new Animal());
    }
}
class Animal {
    String type = "动物";
    static void run2() {
        System.out.println("Animal run");
    }
    void run() {
        System.out.println(type + "run");
    }
}
-------------运行结果:
人类run
Animal run
动物run
Animal run
  • 区别不是很大,就是多加了一个static方法,并且在test方法里多加了一句调用。运行结果也很明显,无论我传入的对象本质是哪种类型,它只会调用Animal的静态方法。这个也证明了static方法是前期绑定的。至于final方法也是前期绑定的那就更好理解了。因为final方法是无法被覆盖的,所以调用final方法时不会产生多态,因为只有一个类型会有这个方法,而子类型只可能继承这个方法。
  • 既然方法有多态这个特性,那成员会不会也有这种特性呢?答案是否定的。这个在前一章里面也说过。在创建导出类对象时,该对象其实包含了基类的子对象,如果你认为它是一个基类对象的话(向上转型),那么其默认域则会是基类的子对象。意思就是说如果你访问上面代码的a.type,因为a是一个Animal引用。当你传入一个Animal对象的时候,那自然会返回动物。如果你传入一个People对象,它也会被向上转型指定其默认域为基类的子对象,此时再访问a.type依旧返回动物
  • 综上,只有普通的方法才会有多态的特性。

构造器和多态

  • 构造器不同于其他种类的方法。涉及到多态时仍是如此。尽管构造器并不具有多态性(可以认为构造器是static方法),但还是非常有必要理解构造器怎么通过多态在复杂的层次结构中运作。

构造器的调用顺序

  • 这个东西在前面已经有了说明,但现在有了多态,再重新简单说明一次。
  • 基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上链接,以使每个基类的构造器都能得到调用。这么做是有意义的,因为导出类只能访问它自己的成员,不能访问基类中的成员(你总不可能一直用super.xx去初始化吧?而且这种方法还无法初始化private变量)。只有基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。所以必须使所有的构造器都得到调用,才能正确的创建完整对象。这也是编译器为什么要强制每个导出类都必须调用基类构造器的原因。而在继承的时候,我们可能会访问基类中任何声明为public和protected的成员。因此必须先进行基类的构造。

继承与清理

  • 一般是由垃圾回收器自动清理,但是如果非要手动清理,要记住一个原则。先清理导出类,再清理基类;清理成员变量的顺序和定义的顺序相反。前者是因为导出类的清理可能会调用基类的清理函数。反正清理的方向与初始化的方向相反!

构造器内部的多态方法行为

  • 如果在构造器内部调用了一个多态方法会如何?他是会调用基类的方法呢,还是子类的方法?我们来看一个例子:
public class People extends Animal {
    String type = "人类";
    void run() {
        System.out.println("People " + type + " run");
    }
    public static void main(String args[]) {
        new People();
    }
}
class Animal {
    String type = "动物";
    Animal() {
        run();
    }
    void run() {
        System.out.println("Animal " + type + " run");
    }
}
---------------运行结果:
People null run
  • 怎么样,这个结果很神奇吧?其实仔细理解一下也不是非常神奇了。
    1. new People();为People对象分配固定的空间,并清理数据为0。
    2. 由于People是一个导出类,所以要先进行基类Animal的初始化。
    3. 省略Object的初始化,基类会先完成字段处的初始化,然后执行构造器内部,即调用run方法。重点就在这个地方,此时的run明显是一个多态方法。可以将它看成是this.run();
    4. 我们知道this指的是我们正在创建的这个对象,因为导出类重写了这个方法,由于动态绑定,所以调用导出类的run也不是无法理解。
    5. 那你会以为也应该输出People 人类 run是吗?nonono,前面我们已经说了,需要先进行基类的初始化,只有在完成了基类的初始化,才会继续进行导出类的初始化。而此时导出类还没有进行字段处的初始化呢,所以它此刻是默认值0,如果是引用类型的就是null。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值