1 前言
做了一次口试题,发现了一题问答题,是对于 Java
绑定的:
过后做的时候是齐全不会的。。。
于是这里补上一篇 Java
绑定的文章。
2 绑定
首先来理解一下绑定的概念。绑定是指 一个办法的调用与办法所在的类关联起来。
很形象吧,举个例子,如果父类与子类存在同名办法,子类对父类办法进行了重写,那么就须要绑定来辨别调用的到底是父类的办法还是子类的办法。绝对简略的一种了解是,绑定是一个办法的调用与调用这个办法的类连贯在一起的过程。
而绑定具体又能够分为:
- 动态绑定:就是程序执行前,办法曾经被绑定,能够简略了解成编译期绑定
- 动静绑定:在运行时依据具体对象的类型进行绑定,通过一些机制去运行时判断对象的类型,并别离调用适当的办法
3 动态绑定
动态绑定也叫后期绑定、编译期绑定,在程序运行之前,也就是编译期间 JVM
可能确认办法由谁调用,这种机制就叫动态绑定。
如果一个办法由 private
、static
、final
任意一个关键字润饰,那么这个办法就是动态绑定的,起因很简略,因为:
private
润饰的办法,无奈由本类以外的类调用,也就是调用者只能是该类static
润饰的办法,通过类名. 办法名
进行调用,也能够惟一确定了调用的类final
润饰的办法,不能被子类进行重写,在编译期就能确定了调用的类
这三个关键字润饰的办法,都能够在编译期间就能惟一确定了调用的类,不存在子类调用的问题,因而应用动态绑定,而不是动静绑定。
4 动静绑定
动静绑定就是运行时依据对象的类型进行绑定,简略来说,JVM
在运行期间决定由哪个对象调用的过程称为动静绑定。
比方:
public class Main {public static void main(String[] args){A b = new B();
b.print();}
}
class A{public void print(){System.out.println("A");
}
}
class B extends A{
@Override
public void print(){System.out.println("B");
}
}
因为 B 类继承了 A 类,因而创建对象的时候:
A b = new B();
编译期并不知道 b 真正援用的是 A 类还是 B 类,在运行的时候才晓得 b 是一个 A 类对象,然而指向了 B 类的援用。
在 Java
中,所有的非 final
、private
、static
的办法都是动静绑定的,因为只有继承了就能重写。
5 区别
- 产生期间:动态绑定产生在编译期间,动静绑定产生在运行期间
- 灵活性:动静绑定的灵活性要比动态绑定高,因为动态绑定在编译的期间就确定了,而动静绑定在编译的时候并不知道是调用哪一个类的办法
- 速度:动态绑定调用办法的速度要快于动静绑定,因为动态绑定能够间接调用,而动静绑定须要去搜寻办法表
6 动静绑定的过程
在理解动静绑定的过程之前,先理解一些前置常识。
6.1 办法调用
Java
中的办法调用有两类:
- 静态方法调用
- 动静办法调用
而办法调用的指令有四个,别离是:
invokestatic
invokespecial
invokevirtual
invokeinterface
前两个是动态绑定的,而后两个是动静绑定的。
6.2 办法表
办法表是字节码文件的一部分,每个类都有一个办法表,办法表是为 invokevirtual
以及 invokeinterface
指令服务的。因为 Java
中的类都继承于 Object
,因而,在默认状况下,所有类的办法表中都有Object
的办法,如果重写了其中的办法,就会扭转其中的描述符。比方,Object
类的办法表能够简略了解如下:
而加载了 A 类的字节码后,因为 A 类并没有重写任何的 Object
办法,因而只是增加了 A 类自身的办法:
而加载了 B 类的字节码后,因为重写了print()
,因而办法表如下:
6.3 具体过程
理解了前置常识后看具体过程就会绝对简略一点了,动静绑定的过程能够分为三步:
- 虚拟机提取对象理论类型的办法表:
JVM
获取到对象的理论类型后,再获取该类型的办法表 - 虚拟机搜寻办法签名:当调用
b.print()
时,通过办法表发现理论办法是B.print()
- 调用办法:调用
B.print()
7 参考
- StackOverflow-How does dynamic binding happens in JVM?
- 博客园 -Java 多态实现原理