java编程思想——Java中的动态、静态绑定(前期、后期绑定)

    技术2022-07-16  63

    文章目录

    概念:JAVA动态绑定的内部实现机制 总结:java编程思想——Java中子类是否可以继承父类的static变量和方法而呈现多态特性 参考资料:

    概念:

    方法可以在沿着继承链的多个类中实现,子类可以重写父类的方法。JVM决定运行时调用哪个方法。这就是动态绑定。

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

    2、若程序在执行前进行绑定,由编译器和链接程序实现,叫做前期绑定。C语言中只有一种方法调用,就是前期绑定。

    3、在运行时根据对象的类型进行绑定,叫做后期绑定,也叫动态绑定或运行时绑定,反之叫做前期绑定,也叫静态绑定。

    4、Java中除了static方法和final方法(private方法被自动认为是final方法)之外,其他所有的方法都是后期绑定。

    这句话很重要,在我们使用多态时,即将子类对象赋给父类引用时,通过父类引用调用某一方法。这个时候就会出现“方法调用绑定”问题 (这是面向对象程序设计的最重要的妙诀) 。如果此方法不是static或者final(private)的,那么绑定是后期绑定,即在编译时(在编译期,只是确保调用方法的存在,并对调用参数和返回值执行类型检查),方法调用和方法体没有关联起来,只要到了运行时,根据引用所指向的对象类型,将方法调用和方法体关联起来。在这个过程中有许多值得注意的地方。

    父类中有一个方法f(),如果子类中Override(覆写)了父类的f()方法,那么在运行期方法调用关联到的方法体是子类中的f()如果子类没有Override父类的f(),那么在运行期方法调用关联到的方法体是子类从父类中继承过来的f()。大家有没有想过一种情况:假如父类中没有f()方法,子类中有,在多态中,方法调用f()时,会出现什么情况呢?其实大家可以做一下实验,结果就是会编译出错!其实很好理解,因为在编译期,编译器只知道,父类引用调用了f()方法,如果父类中没有f()方法,当然会编译出错呀!

    一般情况说完了,说一下特殊的情况,关于final的,首先看一下下面的例子:

    package com.sailang.polymorphism; public class PrivateOverride { private void f() { System.out.println("private f()"); } public static void main(String[] args) { PrivateOverride po = new Derived(); po.f(); } } class Derived extends PrivateOverride { public void f() { System.out.println("public f()"); } }

    大家觉得结果会是什么呢?

    结果是private f()。如果各位看懂了我上面的讲解,应该就不难理解了吧!PrivateOverride中的f()方法时private的,即是final的,po.f()是前期绑定,在编译期方法调用就直接绑定到了PrivateOverride的f()方法体上了。进一步分析,PrivateOverride的f()方法时final的,从继承角度考虑,子类是不会继承f()方法的。Derived中的f()方法是一个全新的方法。

    如果方法是static的,看下面的例子:

    package com.sailang.polymorphism; public class StaticPolymorphism { public static void main(String[] args) { StaticSuper sup = new StaticSub(); System.out.println(sup.staticGet()); System.out.println(sup.dynamicGet()); } } class StaticSuper { public static String staticGet() { return "Base staticGet()"; } public String dynamicGet() { return "Base dynamicGet()"; } } class StaticSub extends StaticSuper { public static String staticGet() { return "Derived staticGet()"; } public String dynamicGet() { return "Derived dynamicGet()"; } } 输出: Base staticGet() Derived dynamicGet()

    结果很容易知道!

    不过我想问一下大家,子类中的dynamicGet()是Override了父类的dynamicGet()方法,对吧!那么子类中的staticGet()方法覆盖了父类的staticGet()方法了吗?大家可以做一下实验。我实验的结果是,static方法既不能被覆盖也不能被继承!不知道这个结论对不对!

    5、后期绑定是java中实现的多态的基础。注意多态只是对方法调用起作用的,对域(成员变量)是不起作用的,即对域的调用是编译器解析的。(这方面的知识,我就不写了,大家可以参考《Java编程思想》中文第四版的P156-157)


    JAVA动态绑定的内部实现机制

    JAVA虚拟机调用一个类方法时,它会基于对象引用的类型(通常在编译时可知)来选择所调用的方法。相反,当虚拟机调用一个实例方法时,它会基于对象实际的类型(只能在运行时得知)来选择所调用的方法,这就是动态绑定,是多态的一种。动态绑定为解决实际的业务问题提供了很大的灵活性,是一种非常优美的机制。

    1 、JAVA对象模型 JAVA虚拟机规范并没有规定JAVA对象在堆里是如何表示的。对象的内部表示也影响着整个堆以及垃圾收集器的设计,它由虚拟机的实现者决定。 JAVA对象中包含的基本数据由它所属的类及其所有超类声明的实例变量组成。只要有一个对象引用,虚拟机就必须能够快速地定位对象实例的数据。另外,它也必须能通过该对象引用访问相应的类数据(存储于方法区的类型信息),因此在对象中通常会有一个指向方法区的指针。当程序在运行时需要转换某个对象引用为另外一种类型时,虚拟机必须要检查这种转换是否被允许,被转换的对象是否的确是被引用的对象或者它的超类型。当程序在执行instanceof操作时,虚拟机也进行了同样的检查。所以虚拟机都需要查看被引用的对象的类数据。

    不管虚拟机的实现使用什么样的对象表示法,很可能每个对象都有一个方法表因为方法表加快了调用实例方法时的效率。但是JAVA虚拟机规范并未要求必须使用方法表,所以并不是所有实现中都会使用它。

    下面是一种JAVA对象的内存表示:

    方法数据存放在类的方法区中,包含一个方法的具体实现的字节码二进制。方法指针直接指向这个方法在内存中的起始位置,通过方法指针就可以找到这个方法。

    2 动态绑定内部机制

    方法表是一个指向方法区中的方法指针的数组。方法表中不包含static、private等静态绑定的方法,仅仅包含那些需要动态绑定的实例方法。

    在方法表中,来自超类的方法出现在来自子类的方法之前,并且排列方法指针的顺序和方法在class文件中出现的顺序相同,这种排列顺序的例外情况是,被子类的方法覆盖的方法出现在超类中该方法第一次出现的地方。


    总结:

    1、动态绑定体现了Java的继承与多态 2、在运行时根据对象的类型进行绑定,叫做后期绑定,也叫动态绑定或运行时绑定,反之叫做前期绑定,也叫静态绑定。 3、Java中除了static方法和final方法(private方法被自动认为是final方法)之外,其他所有的方法都是后期绑定。 4、static方法既不能被覆盖也不能被继承

    java编程思想——Java中子类是否可以继承父类的static变量和方法而呈现多态特性


    参考资料:

    方法调用绑定–前期绑定和后期绑定

    Processed: 0.008, SQL: 9