当前位置: 首页 > 图灵资讯 > 技术篇> Java中的绑定—动态绑定与静态绑定机制

Java中的绑定—动态绑定与静态绑定机制

来源:图灵教育
时间:2023-04-07 10:23:51

首先,让我们了解绑定的概念:绑定是指一种方法的调用和方法的类别(方法主体)关联起来。Java静态绑定和动态绑定机制;或称为前期绑定和后期绑定

在介绍静态绑定和动态绑定之前,让我们了解以下单词的概念:

结构器:方法名与类名相同,也分为有参结构、无参结构即使你在java中对某一类没有写构造器,程序也会默认将这一类无参构造器用于初始化。

实例方法:属于对象的方法,由对象调用。

显式参数:方法中明确定义的参数。

隐形参数:this修改的变量。

类别方法:使用static修饰(静态方法)属于整个类别,不属于某个例子,只能处理static域或调用static方法。

一、静态绑定

即使用变量或结构方法的private或static或final修改

private:如果不能继承,就不能通过子类对象调用,只能通过子类本身的对象调用。因此,可以说,private方法与方法所属的类别绑定;

final:可以继承,但不能重写(覆盖)。虽然子类对象可以调用,但调用的是父类中的final方法(所以可以看出,当类中的方法声明为final时,一方面可以防止方法被覆盖,另一方面可以有效关闭java的动态绑定。编译程序时会绑定)(想到final突然想到string是用final修改的,所以我们都知道他引用的对象值不能修改。当然,有时候string类型的值发生了变化,本质不是修改对象本身,而是引用关系);

static:被子类可以继承,但不能重写(覆盖),但可以隐藏。如果父类中有static方法,如果其子类中没有相应的方法,则当子类对象调用此方法时,将使用父类中的方法。如果子类中定义了相同的方法,则会调用子类中定义的方法。唯一的区别是,当子类对象转化为父类对象时,无论子类中是否定义了这种静态方法,对象都会使用父类中的静态方法。

  • 动态绑定

即根据具体对象信息绑定在运行中。

如果一种语言在运行过程中实现了后期绑定,则必须提供一些机制来判断对象的类型,并分别调用适当的方法。换句话说,编译器此时我们仍然不知道对象的类型,但方法调用机制可以自己调查,找到正确的方法主体。不同的语言不同于以后绑定的实现方法。但我们至少可以认为,他们都必须在对象中插入一些特殊类型的信息。

动态绑定过程:

1.提取虚拟机对象实际类型的方法表;

2.虚拟机搜索方法签名;

3.调用方法。

通过动态绑定的概念和过程,不难看出,在在java中,几乎所有的方法都是后期绑定的,动态绑定方法属于子类或基类。但也有特殊之处。由于static方法和final方法不能继承,他们的价值可以在编译过程中确定,属于早期绑定。特别是private声明的方法和成员变量不能被子类继承,所有private方法都被隐式指定为final(所以我们也可以知道,将方法声明为final类型的方法是防止方法被覆盖,有效关闭java中的动态绑定)。Java中的后期绑定是由JVM实现的。我们不需要明确声明它,而C++则不同。我们必须明确声明某种方法具有后期绑定。

Java代码:

package hr.test;

//被调用的父类

class Father{

public void f1(){

System.out.println("father-f1()");

}

public void f1(int i){

System.out.println("father-f1() para-int "+i);

}

}

//被调用的子类

class Son extends Father{

public void f1(){ ///覆盖父类的方法

System.out.println("Son-f1()");

}

public void f1(char c){

System.out.println("Son-s1() para-char "+c);

}

}

//调用方法

import hr.test.*;

public class AutoCall{

public static void main(String[] args){

Father father=new Son(); //多态

father.f1(); //打印结果: Son-f1()

}

}

上述源代码有三个重要概念:多态(polymorphism) 、方法覆盖 、方法重载 。每个人都知道打印的结果,但是JVM是如何知道f的呢?.F1()调用子类Sun而不是Father中的方法?在解释这个问题之前,让我们简单地谈谈JVM管理中一个非常重要的数据结构——方法表 。

在JVM加载类的同时,会在方法区为该类存储大量信息。有一种数据结构叫做方法表。它以数组的形式记录了当前类和所有超类的可见方法字节码在内存中的直接地址。方法表有两个特点:(1) 父类方法在子类方法表中继承,如Father extends Object。 (2) 在所有类别的方法表中,相同的方法(相同的方法签名:方法名和参数列表)索引相同。

对于上述源代码,编译器将首先使用它将main方法编译成以下多态调用字节码指令:

0 new hr.test.Son [13] ///在堆中开辟Son对象的内存空间,并将对象引用到操作数栈中

3 dup

4 invokespecial #7 [15] // 调用初始化方法初始化堆中的Son对象

7 astore_1 ///弹出操作数栈的Son对象引用压入局部变量1

8 aload_1 ///取出局部变量1中的对象引用压入操作数栈

9 invokevirtual #15 ///调用f1()方法

12 return

其中invokevirtual指令的详细调用过程如下:

(1) invokevirtual指令中的#15是指Autocall类常量池中第15个常量表的索引项。这个常量表(CONSTATN_Methodref_info ) 记录方法F1信息的符号引用(包括F1所在的类名、方法名和返回类型)。JVM首先会根据这个符号引用找到调用f1类的全限定名: hr.test.Father。这是因为Father声明Father类型是Father类型。

(2) 如果在Father类型的方法表中找到方法F1,则将方法F1中的索引项11(如上图所示)记录在AutoCall类常量池中的第15个常量表中(常量池分析 )。这里有一点需要注意:如果Father类型方法表中没有方法F1,即使Son类型中有方法表,在编译时也无法通过。Father的声明是Father类型,因为调用方法f1的对象father。

(3) 在调用invokevirtual指令之前,有一个aload_1指令,它将开始在堆中创建的Son对象的引用压入操作堆栈。然后根据Son对象的引用,invokevirtual指令将首先在堆中找到Son对象,然后进一步找到Son对象类型的方法表。

(4) 这是#15常量表中方法表的索引项11,可以定位到Son类型方法表中的方法F1(),然后通过直接地址找到方法字节码所在的内存空间。

很明显,根据对象(father)的声明类型(Father)调用方法f1的位置无法确定,f1方法的位置必须根据father在堆中实际创建的对象类型Son来确定。这种在程序运行过程中通过动态创建对象的方法表来定位方法的方法,我们称之为动态绑定机制。

基于上述理论,所有私有方法、静态方法、结构和初始化方法都采用静态绑定机制。在编译阶段,常量池中的符号引用指示了调用方法,JVM运行时只需进行常量池分析即可。在运行过程中,必须采用动态绑定机制调用类对象方法。

Java静态绑定可以让我们在编译期间发现程序中的错误,而不是在操作期间。这可以提高程序的运行效率!动态绑定的方法是实现多态,多态java的一个主要特点。多态也是面向对象的关键技术之一java以效率为代价实现多态化是值得的。实践是检验真理的唯一标准。我希望我们能理解java的绑定及其动态绑定机制可以熟练运用到实践中。