05_java编程思想多态详解

多态

多态(也称作动态绑定后期绑定运行时绑定),多态的作用是消除类型之间的耦合关系。

1. 再论向上转型

1.1 方法调用绑定

将一个方法调用同一个方法主体关联起来被称作绑定。

  • 若在程序执行前进行绑定(如果有的话,由编译器和连接程序实现),叫做前期绑定。它是面向过程语言中不需要选择就默认的绑定方式。例如,C只有一种方法调用,那就是前期绑定。

  • 后期绑定就是在运行时根据对象的类型进行绑定后期绑定(动态绑定或运行时绑定)。如果一种语言想实现后期绑定,就必须具有某种机制,以便在运行时能判断对象的类型,从而调用恰当的方法。也就是说,编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。后期绑定机制随编程语言的不同而有所不同,但是只要想一下就会得知,不管怎样都必须在对象中安置某种“类型信息”

  • 再谈final方法:

    1. java中除了static方法和final方法(private方法属于final方法)之外,其他所有的方法都是后期绑定。
    2. final方法可以防止其他人覆盖该方法。但更重要的一点是:这样做可以有效地关闭动态绑定,或者说,告诉编译器不需要对其进行动态绑定。这样,编译器就可以为final方法调用生成更有效的代码。然而,大多数情况下,这样做对程序的整体性能不会有什么改观。所以,最好根据设计来决定是否使用final,而不是出于试图提高性能的目的来使用final。
  • Java中所有方法都是通过动态绑定实现多态。

1.2 缺陷:“覆盖”私有方法
public class PrivateOverride(){
    //private方法被自动认为是final方法,且对导出类是屏蔽的
    private void f(){
        print("private f()");
    }
    public static void main(String[] args){
        PrivateOverride po = new Derived();
        po.f();
    }
}

class Derived extends PrivateOverride(){
    //继承类无法重写父类私有方法,所以该f()方法是一个新方法
    public void f(){
        print("public f()");
    }
}
/*Output:
private f()
*/

结论: 只有非private方法才可以被覆盖。

1.3 缺陷:域与静态方法

域是不具有多态性的,只有普通的方法调用是多态的。如果直接访问某个域,这个访问就将在编译期进行解析,即域是静态解析的。

class Super{
    public int field = 0;
    public int getField(){return field;}
}
class Sub extends Super{
    public int field = 1;
    public int getField(){return field;}
    public int getSuperField(){return super.getField();}
}
public class FieldAccess{
    public static void main(String[] args){
        Super sup = new Sub();  // Upcast
        System.out.println("sup.field = " + sup.field + ". sup.getField() = " + sup.getField());
        Sub sub = new Sub();
        System.out.println("sub.field = " + sub.field + ". sub.getFiled() = " + sub.getField() + ". sub.getSuperField() = " + sub.getSuperField());
    }
}
/** Output:
 *  sup.field = 0. sup.getField() = 1
 *  sub.field = 1. sub.getFiled() = 1. sub.getSuperField() = 0
 */

补充:

  • 静态方法也是不具有多态性的,静态方法是与类,而非与单个的对象相关联的。

  • 在类中使用static修饰的静态方法会随着类的定义而被分配和装载入内存中;而非静态方法属于对象的具体实例,只有在类的对象创建时在对象的内存中才有这个方法的代码段。

2. 构造器和多态

构造器并不具有多态性(它们实际上是static方法,只不过该static声明是隐式的)。

2.1 构造器的调用顺序
  1. 基类的构造器总是在导出类的构造过程中被调用。
  2. 按照继承层次逐渐向上链接,以使每个基类构造器都能被调用。
  3. 调用构造器时,先向上链接后进入构造器(类似递归)。
class Animal{
    Cat cat = new Cat();  //类似这些字段会按顺序被JVM优化到构造器中
    Animal(){
        System.out.println("Animal");
    }
}

class Cat{
    Cat(){
        System.out.println("Cat");
    }
}

class Dog extends Animal{
    Dog(){
        System.out.println("Dog");
    }
}
public class ExtendsDemo01 {
    public static void main(String[] args) {
        Dog dog = new Dog();
    }
}

注: 若进入最后一层调用时,

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

如果在构造器内部调用正在构造的对象的某个动态绑定方法,由于动态绑定是在运行时才决定的,而此时,该对象还正在构造中,所以它不知道自己属于哪个类(父类还是自己),并且方法所操纵的成员可能还未进行初始化,这可能会产生一引起难于发现的隐藏错误。

// PolyConstructors.java
// Constructors and polymorphism
// don't produce what you might expect
class Glyph{
    void draw(){
        System.out.println("Glyph.draw()");
    }
    Glyph(){
        System.out.println("Glyph() before draw()");
        draw();
        System.out.println("Glyph() after draw()");
    }
}
class RoundGlyph extends Glyph{
    RoundGlyph(int r){
        radius = r;
        System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
    }   
    private int radius = 1;
    void draw(){
        System.out.println("RoundGlyph.draw(), radius = " + radius);
    }
}
public class PolyConstructors {
    public static void main(String[] args){
        new RoundGlyph(5);
    }
}
/**Output:
 * Glyph() before draw()
 * RoundGlyph.draw(), radius = 0
 * Glyph() after draw()
 * RoundGlyph.RoundGlyph(), radius = 5
 */

注: 以上代码,构造RoundGlyph对象时,先调用父类构造器Glyph(),父类构造器中如我们所期,调用了多态的draw(),但是,由于 子类还没构造完成,所以打印的成员变量radius的值是0,而并不是我们想象的其默认的初始值1。

3. 初始化的实际过程

  1. 在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零。
  2. 如前所述那样调用构造器。
  3. 按照声明的顺序调用成员的初始化方法。
  4. 调用导出类(派生类)的构造器主体。

为了避免ClassCastException的发生,Java提供了 instanceof 关键字,给引用变量做类型的校验,格式如下:

4. instanceof 关键字
变量名 instanceof 数据类型 
如果变量属于该数据类型或者其子类类型,返回true。
如果变量不属于该数据类型或者其子类类型,返回falsepublic class Test {
    public static void main(String[] args) {
        // 向上转型  
        Animal a = new Cat();  
        a.eat();               // 调用的是 Cat 的 eat

        // 向下转型  
        if (a instanceof Cat){
            Cat c = (Cat)a;       
            c.catchMouse();        // 调用的是 Cat 的 catchMouse
        } else if (a instanceof Dog){
            Dog d = (Dog)a;       
            d.watchHouse();       // 调用的是 Dog 的 watchHouse
        }
    }  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值