微信公众号:吉姆餐厅ak
学习更多源码知识,欢迎关注。
概述
在以前面试过程中,会遇到一些这样的面试题:说一下java语言 静态绑定 和 动态绑定的本质区别?
这道题听起来好像没怎么接触过,换个问法可能就很清晰了:重载和重写的实现原理和区别。
具体来分析以下这两点:
方法调用
方法表
方法调用
java是一种半编译半解释型语言,也就是class文件会被解释成机器码,而方法调用也会被解释成具体的方法调用指令,大致可以分为以下五类指令:
- invokestatic:调用静态方法;
- invokespecial:调用实例构造方法,私有方法和父类方法;
- invokevirtual:调用虚方法;
- invokeinterface:调用接口方法,在运行时再确定一个实现此接口的对象;
- invokedynamic:在运行时动态解析出调用点限定符所引用的方法之后,调用该方法;
注意:invokedynamic 指令是jdk1.7才加入的,但是在jdk1.7中并没有开始使用。在jdk1.8中才开始大量使用,主要就是我们大量用的 lambda 表达式。
如果在编译时期解析,那么指令指向的方法就是静态绑定,也就是private,final,static和构造方法,也就是上面的invokestatic
和invokespecial
指令,这些在编译器已经确定具体指向的方法。而接口和虚方法调用无法找到真正需要调用的方法,因为它可能是定义在子类中的方法,所以这种在运行时期才能明确类型的方法我们成为动态绑定。
具体来看一下:
public class StaticBind {
public static void main(String[] args) {
Father son = new Son1();
son.run();
}
}
class Father{
public void run(){
System.out.println("father");
}
}
class Son1 extends Father{
@Override
public void run(){
System.out.println("son1");
}
}
通过javap -c看一下编译后的机器码指令,如下:
可以看到第4行是invokespecial
指令,构造方法 init 指令。第9行是invokevirtual
指令,Method com/iyb/ak/study/Father.run:()
该指令表明是父类的方法,但这只是在编译时确定的,在执行时却会从方法表中找到真正重写的方法。
在来看一下重载时的一种虚引用的一种场景:
/**
* Created by zhangshukang on 2018/8/2.
*/
public class StaticBind {
public void run(Father father){
System.out.println("father");
}
public void run(Son1 son1){
System.out.println("son1");
}
public void run(Son2 son2){
System.out.println("son2");
}
public static void main(String[] args) {
Son1 son1 = new Son1();
Son2 son2 = new Son2();
StaticBind staticBind = new StaticBind();
staticBind.run(son1);
staticBind.run(son2);
}
}
class Father{}
class Son1{}
class Son2{}
javap -c看一下编译后的机器码指令:
上述代码中,在编译阶段,Java编译器会根据参数的静态类型决定调用哪个重载版本,但在有些情况下,重载的版本不是唯一的,这样只能选择一个“更加合适的版本”进行调用,所以不建议在实际项目中使用这种模糊的方法重载。