深入理解Java动态绑定与静态绑定
动态绑定
以下是我的理解,可能和其他大佬们的理解略有偏差,如有想法不同的或者有错误的地方欢迎您指出来,谢谢
先上代码:
public class Father {
public void f() {
System.out.println("Father method");
}
public static void main(String[] args) {
Father person = new Son();
person.f();
}
}
class Son extends Father{
public void f() {
System.out.println("Son method");
}
}
这个比较简单,上述运行结果显而易见,输出Son method,这个比较简单,大概说一下,这是由于Java的动态绑定,Son继承Father,并且覆写了f()方法,Father person = new Son();创建了一个子类实例对象,将其引用给了父类person,其实在但其实在内存中还是一个子类对象,那既然子类和父类中都有f()方法,那么为什么person.f()的返回值是Son method,而不是Father method呢,这是和Java的动态绑定有关,下面是调用person.f() 的详细过程。
在程序的编译阶段,现在可以看出person被声明为Father类,所以编译器会查看Father类中以及他的父类中可能存在多个名字为f,但是参数返回值类型不同的方法,例如f(int a),f(String s),当然他的父类中的private的f()方法除外,现在编译器找到了所有有可能被调用的方法。
接着编译器开始逐个匹配刚才找到的候选方法,看名为f()的方法里哪个方法和f()的参数类型匹配,上面这个例子里这里就是匹配无参类型的f()方法,这个过程叫做重载解析,如果找不到就会报错,现在,编译器已经找到了需要被调用的方法。那它在这个例子中找到的是谁呢,他找到是Father类中f()方法没错吧,因为person.f()中的隐式参数被声明为Father了,所以它找到的是Father类中f()。
当程序运行时,并且采用动态绑定时(与之相对的是静态绑定,后面会讲)虚拟机一定会调用与person所引对象最合适的那个类的方法。现在我们知道person引用的其实是Son,也就是说person的实际类型为Son类,而不是Father类,所以也就是Son类中的f()方法。当然,如果在Son类中找不到的话,那么它就去Son的父类Father中继续寻找,找到的就是Father中的f()方法。所以现在到底要执行谁呢,肯定是执行Son中的f()方法,因为Son类覆写了Father的f()方法,刚开始声明的时候是将Son的对象声明为Father,看清楚,这里只是person引用了Son的实例对象,被声明为Father了,而不是真正的Father对象,所以在运行期间,虚拟机一定会调用与所引用对象的实际类型最匹配的方法,这里就是Son的f()对象了,所以打印出来Son method,说白了,在编译期间,虚拟机不知道这个 person是Son类,运行期间才知道person为Son类。
由于每次都要进行搜索,时间开销大所以虚拟机会预先给每个类创建一个方法表,列出了所有方法的签名以及实际调用的方法。这样每次调用方法的时候虚拟机直接查表就行。
静态绑定
先上代码:
public class A {
public int age = 50;
private void f() {
System.out.println("A method");
}
public static void main(String[] args) {
A a = new B();
a.f();
System.out.println(a.age);
}
}
class B extends A{
public int age = 20;
public void f() {
System.out.println("B method");
}
}
输出结果为A method 50,按动态绑定的理解,new了一个B的对象,只是引用给A,所以在运行期间,虚拟机会自动判定它实际是一个B的实例,应该调用B的f()方法,为什么调用A的呢,请注意,这里A类中的f()方法的限定词是private,这是只有A类可见的,所以引用Java编程思想这本书里面的解释是,由于private方法被自动认为final方法,而且对导出类,也就是子类是屏蔽的,所以在这种情况下,B类中的f()方法就是一个全行的方法,既然A中的f()方法在B类中不可见,所以甚至不能进行重写。
静态绑定:程序运行前方法已被绑定。即Java中编译期进行的绑定。
Java中private,final,static方法以及域或者构造器都是静态绑定,这也是Java中域不能被重写只能被隐藏的原因。
先说private方法,由于它是私有的,所以无法继承,更无法重写,就像上面的例子来说,子类中有和父类中相同函数名和参数列表的函数,只是修饰限定词不一样的方法,其实这两个方法没有半毛钱关系,是两个不同的方法,父类将这个方法对子类屏蔽了,所以这个在父类中定义的方法也只能被父类调用。也就是和父类进行了绑定。
final方法虽然可以被继承,但是无法被子类重写覆盖,所以子类或者父类在调用这个函数的时候都是调用父类的函数,当你想在子类中覆写这个final函数的时候,编译会出错,不会允许你进行覆写。所以谈不上动态绑定。
static方法就是静态,它是和类绑定在一起,它在类初始化的时候也会被直接被初始化,它是和类绑定到一块的,这里引用这篇博客中所说https://www.cnblogs.com/X-World/p/5686173.html static方法可以被子类继承,但是不能被覆写,但是可以被子类隐藏,也就是当父类中的static方法子类中没有的时候,子类调用父类的,但是当子类有这个static方法时,那么它调用子类的方法,当子类被向上转型为父类时,它则调用父类的方法。
由于域也是静态绑定,所以上面的输出就是50,不是20
总结:
根据这篇文章的https://www.jianshu.com/p/0677f366db08的反编译结果,在方法为public时,如果是父类引用子类对象,那么在程序的编译阶段,虚拟机会先和父类进行绑定(域和方法都是),在程序的运行阶段,编译器会再次进行判断,此时它识别出这个对象本质是一个子类对象,当方法为public时,如果子类已经对这个方法进行了重写,那么它就调用自己本身的方法,这就是动态绑定,但是当方法为private,final,static方法时,它采用的静态绑定,也就是说,父类引用子类对象后,在编译阶段这个父类引用的子类对象会和父类的方法进行绑定,在运行阶段,则不会进行二次的判断进行动态绑定,不管子类中有没有同名同参数的方法或者域,原因上面也说了,private方法屏蔽了子类,子类中的同名同参函数和父类中的方法可以认为是两个毫无相关的函数,域也一样。final方法可以被继承,无法被覆写,如果你想覆写,编译器会直接报错,所以也不会存在同名同参函数的final函数在父类子类中都有,更不用说是哪种绑定了,static方法,可以被继承,但是不能被覆写,子类有,调用子类的,子类没有,调用父类的,如果子类有,那么它在编译阶段就绑定子类的,如果没有,那么它直接直接绑定父类的,但是,这个完全多此一举,作为static方法,直接用类名进行调用最好,以免引起不必要的误解。
以上就是我的个人理解,如果有问题,希望您能够指出来