深入理解java——多态与动态绑定

引用了网上的文章。侵权删。

1.多态

首先,什么是多态?

根据定义上来讲,一个对象变量可以指示多种实际类型的现象被称为多态。

或者可以这样说,一个引用变量指向哪个类对象在编程时不能确定,要到要等到程序运行时才确认这个变量到底指向哪个类对象,调用的是哪个类的方法,这样可以在不修改代码的情况下,改变运行绑定的具体对象,让程序可以选择多个运行状态,这就是多态性。

比如说一个Manager类继承自Employee类,而一个Employee变量既可以引用一个Employee类对象,还可以引用Manager类,这种行为就称为多态

下面看一段代码:

Manager boss = new Manager();
Employee[] staff = new Employee[3];
staff[0] = boss;

在这段代码中,staff[0]和boss引用同一个对象。但是编译器将staff[0]看成是Employee对象,意味着,boss能调用子类的方法,但是staff[0]并不能调用Manager类的独有方法。
如:

boss.setBonus(); // OK
staff[0].setBonus();// Error

这是因为staff[0]声明的类型是Employee类。

同样的,也不能将超类的引用赋给子类变量。
这是因为一个超类可以有很多个子类,如果Manager类变量能引用Employee类,那么这个变量可以引用其他的Employee子类,这样的话调用其他子类独有的方法时运行时就会报错。

多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它根据参数列表的不同来区分不同的方法,通过编辑之后会变成两个不同的方法,在运行时不算多态。而运行时多态是动态的,它是通过***动态绑定***来实现的,也就是我们所说的多态性。

2.动态绑定

同样的,要理解多态就要理解动态绑定,那么,什么是动态绑定?

在运行时能够自动地选择调用哪个方法的现象称为动态绑定。

2.1方法调用的过程

在这里我们先来解释一下方法调用的过程。

下面假设要调用x.f(args),x为类C的一个对象。下面是调用过程的详细描述:

<1>编译器查看对象声明类型和方法名。假设调用x.f(param),且x为类C的一个对象。需要注意:这里有可能会存在多个名字为f,但是参数类型不一样的方法,比如可能存在f (int),f(long)。编译器将会一一列举所有C类中名为f的方法,和其超类中访问属性为public且名为f的方法(超类的私有方法不可访问)。
此时,编译器已获得所有可能被调用的候选方法。

<2>接下来,编译器将查看调用方法时提供的参数类型,如果在所有方法名为f的方法中存在一个与提供参数类型完全匹配,就选择这个方法。这个过程被称为重载解析。比如调用f(“hello,world!”),那么,编译器将选择f(string)而不是其他,不过由于允许类型转换(int可以转换成double,Manager可以转换成Employee,等等),这个过程可能会很复杂。如果编译器没有找到与参数类型匹配的方法,或者经过类型转换后发现有多个方法与之匹配,就会报告一个错误。
此时,编译器已获得需要调用的方法名字和参数类型。

<3>如果这个方法是private,static,final方法或者是构造器,那么编译器将可以准确的知道应该调用哪个方法,我们将这种调用方式称为静态绑定。与之对应,调用的方法依赖于变量的实际类型,并且在运行时实现动态绑定。

<4>当程序运行时,并且使用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法,如x的实际类型是D,D继承自C类。那么调用f时,先检查D类方法中的f(String),没有就再去D的超类中找f(String),以此类推。

最后,由于每次调用方法都要进行搜索,时间开销非常大。因此,虚拟机预先为每个类创建了一个方法表,其中列出了所有方法的签名(即方法的名字和参数类型,不包括返回类型)和实际调用的方法。这样的话,在真正调用方法时,虚拟机仅查找这个表就行了。在前面的例子中,虚拟机搜索D类的方法,以便寻找与调用f(String)相匹配的方法,这个方法既可能是D.f(String),也有可能是X.f(String).这里X是D的超类。这里需要提醒一下,如果调用super.f(param),编译器将对隐式参数的超类的方法表进行搜索。

3.举个例子

下面看一个实例,这是有关多态的经典例子,摘自:
http://blog.csdn.net/thinkGhoster/article/details/2307001

public class A {
    public String show(D obj) {
        return ("A and D");
    }

    public String show(A obj) {
        return ("A and A");
    } 

}

public class B extends A{
    public String show(B obj){
        return ("B and B");
    }
    
    public String show(A obj){
        return ("B and A");
    } 
}

public class C extends B{

}

public class D extends B{

}

public class Test {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();
        
        System.out.println(a1.show(b));   ①
        System.out.println(a1.show(c));   ②
        System.out.println(a1.show(d));   ③
        System.out.println(a2.show(b));   ④
        System.out.println(a2.show(c));   ⑤
        System.out.println(a2.show(d));   ⑥
        System.out.println(b.show(b));    ⑦
        System.out.println(b.show(c));    ⑧
        System.out.println(b.show(d));    ⑨     
    }
}

运行结果:

 ①   A and A
 ②   A and A
 ③   A and D
 ④   B and A
 ⑤   B and A
 ⑥   A and D
 ⑦   B and B
 ⑧   B and B
 ⑨   A and D

3.1分析:

①②③比较好理解,一般不会出错。④⑤就有点糊涂了,为什么输出的不是"B and B”呢?!!

先让我们来看一下这个博客上的一句话:

当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。
(但是如果强制把超类转换成子类的话,就可以调用子类中新添加而超类没有的方法了。)
实际上这里涉及方法调用的优先问题,优先级由高到低依次为:this.show(O),super.show(O),this.show((super)O),super.show((super)O)。

现在列出方法表,
A类:
show(D obj) -> A.show(D obj)
show(A obj) -> A.show(A obj)

B类:
show(B obj) ->B.show(B obj)
show(A obj) ->A.show(A obj)

上面列出的方法不完整,还有Object的方法就不列出了。

现在根据我自己的理解来分析⑤,即调用a2.show©的过程:

<1>在5中,a2声明的为A类,所以编译器将会提取a2可能被调用的候选方法,即show(D obj)和show(A obj)。

<2>接下来,编译器查看调用方法时提供的参数类型,但是show©既不属于show(D obj)又不属于show(A obj),于是到A的超类中找this.show((super)O),由于A没有超类(Object除外),所以将C类型转换,由于C的超类是B,A,因此C能转换成B,A。

<3>这里在A中找到了show(A obj),根据方法调用过程分析中的<4>,即当程序运行时,并且使用动态绑定调用方法时。虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法,所以由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A。

当然对<3>的解释也可以引用博客上的:

当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。

来理解。

动态绑定的一个非常重要的特性:无需对现存的代码修改,就可以对程序进行扩展。就假设增加一个新类Executive,并且变量e有可能引用这个类的对象,不需要对包含调用方法进行重新编译。虚拟机会自动调用这个类的这个方法。

以上,是我做的读书笔记,希望能对你理解多态和动态绑定有帮助。

本人才疏学浅,仅供参考,如有疑问或建议,欢迎提出,一起进步。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值