关于多态以及动态绑定

时间:2023-01-27 16:19:36



           数据抽象,继承,多态是java的三大特征,可见理解多态对于学习java的重要性,多态不但能够改善代码的组织结构和可读性,还能够让程序更加的具有可拓展性。


       理解一下什么是多态。多态就是同一事物的多种不同形态,简单点来说就是:“一个接口,多种实现”。举个例子:体育课上体育老师只要喊一句:“开始活动”,则 打篮球的就开始打篮球,踢足球的就开始踢足球,体育老师不用对打篮球的喊“开始打篮球”,对踢足球的喊“开始踢足球”,即学生是一个“抽象”的事物,只要是学生就具备活动的能力,所以只要喊“开始活动”,则学生就会开始活动,至于活动的内容,则有不同的学生内部自己去实现去决定。


       这就使事情变得十分灵活了,即实际上多态让“做什么”和“怎么做”分离了,即体育老师只要发布“做什么”(一个接口),而学生可以去具体做各自的事“怎么做”(多种实现)。这就是多态的优点。所以我们看出只要存在继承,并且父类子类之间存在重写方法,则就存在多态。


       那么多态具体是如何实现的呢?即系统是如何能准确的调用到想要的那个类里的那个方法呢?这就要说到java的动态绑定。

      具体详细的解析看另一篇文章《Java动态绑定机制原理分析》。

       先来说一下两个概念:静态绑定和动态绑定。这里所谓的绑定,即一个方法的调用与方法所在的类(方法主体)关联起来。

      

       实现动态绑定一个很重要的东西就是在jvm方法区里每一个类都有一个方法表(实质是一个数组),这个方法表里记录了这个类的所有方法(包括       继承父类的方法)在内存里的地址。


       静态绑定(前期绑定):即在程序执行前,即编译的时候已经实现了该 方法与所在类的绑定,像C就是静态绑定。

                        具体过程就是执行这个方法,只要到这个类的方法表里拿出这个方法在内存里的地址,然后就可以执行了。

                         java中只有static,final,private,和构造方法是静态绑定,其他的都属于动态绑定,而private的方法其实也是final方法(隐式),而构造                         方法其实是一个static方法(隐式),所以可以看出把方法声明为final,第一可以让他不被重写,第二也可以关闭它的动态绑定。


     动态绑定(后期绑定):运行时根据对象的类型进行绑定,java中的大多数方法都是属于动态绑定,也就是实现多态的基础。

                                             java实现了后期绑定,则必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。                            也就是说,编译的时候该方法不与所在类绑定,编译器此时依然不知道对象的类型,但方法调用机制能自己去调查,找到                正确的方法主体。java里实现动态绑定的是JVM.

 比如: Father fa=new Son();

      fa.say();

 可以看到这就是一个多态,他的执行结果应该是调用Son里面的say()方法,但是他是如何准确找到Son中的say()方法呢。

 具体过程是,首先由于fa的类型是Father,所以一开始还是会到Father类的方法表里去找say()方法,然后获得到say()方法的索引项比如是第N项(即在方法表里的位置),如果此时Father的方法表里找不到say()方法那就直接编译报错了,得到say()在Father里的索引项之后,然后根据堆里new的具体实例的类型(这里是Son),去Son里面的第N项取出say()方法在内存里的地址,然后执行。


  可以看出动态绑定不及静态绑定那样一步到位,所以效率比较差,但是也有明显的优点,就是实现了多态。


 对比:

 静态绑定优点就是可以在编译期就发现我们程序中的错误,而不用等到运行期,这样就提高了我们程序运行的效率,所以建议像类中的一些常量,还有一些不希望被继承的方法等,都建议申明为private,这样就能在编译期就能绑定,提高程序效率。

 动态绑定,当然动态绑定是实现多态的基础,所以大多数方法还是动态绑定的,即可以被继承,可以被重写等,但是显然动态绑定牺牲了效率。



 下面看看动态绑定的这么一个过程:

 1:编译器检查对象的声明类型和方法名。假设我们调用animal.run()方法,并且animal已经被声明为Animal类的对象,那么编译器会列举出Animal类中所有的名称为run的方法和从Animal类的超类继承过来的run方法.即找出这个父类里的run这个方法,以及他所有子类里的run这个方法。
 2:接下来编译器检查方法调用中提供的参数类型。如果在所有名称为run的方法中有一个参数类型和调用提供的参数类型最为匹  配,那么就调用这个方法,这个过程叫做“重载解析”.即根据传来的参数类型,从所有的run方法里挑选到最合适的一个执行。 
 3当传进来的类型是Animal子类Dog的时候,按上面的过程应该是会找到Dog类里重写的run方法,但是如果未找到的话,则会调用其父类也就是Animal类的run方法。


   这就是多态。