动态绑定
以下是我的理解, 可能和其他大佬们的理解略有偏差, 如有想法不同的或者有错误的地方欢迎您指出来, 谢谢
先上代码:
- 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
来源: http://www.bubuko.com/infodetail-2850417.html