Java多态的动态绑定和静态绑定

这里写图片描述

前言

在上一篇文章《详解Java中的覆写与重载》中介绍了什么是覆写以及重载,如何理解区分这两者的概念还是十分重要的。而谈到覆写和重载又会引入一个概念,那就是多态。多态有两种具体的表现,那就是上面所说的覆写以及重载了。

多态是什么?

在说动态绑定和静态绑定之前有必要先说下多态的概念。

多态是同一个行为具有多个不同表现形式或形态的能力。

多态存在的三个条件:
1. 继承
2. 重写
3. 父类引用指向子类对象

案例驱动

先来看个小栗子:

public class Main {
    public static void main(String[] args) {
        Base  base = new Base ();
        Derived derived = new Derived();
        print(base, base);
        print(base, derived);
        print(derived, base);
        print(derived, derived);
    }
    private static void print(Base arg1, Base arg2){
        arg1.foo(arg2);
    }
}
class Base {
    public void foo(Base base){
        System.out.println("Base.Base");
    }
    public void foo(Derived derived){
        System.out.println("Base.Derived");
    }
}
class Derived  extends Base{
    @Override
    public void foo(Base base){
        System.out.println("Derived.Base");
    }
    @Override
    public void foo(Derived derived){
        System.out.println("Derived.Derived");
    }
}

它的输出结果如下:

Base.Base
Base.Base
Derived.Base
Derived.Base

这是因为在Java中,一个方法的参数在编译阶段常被静态的绑定(前期绑定),于是在print()方法中,由于参数arg2的类型是Base,所以参数arg1调用的foo()方法始终是foo(Base base)。而至于参数arg1的引用则是根据JVM在运行阶段决定的,这被称为动态绑定(后期绑定)。这里也可以看出多态是针对与方法的。

说起绑定,在Java中,几乎所有的方法都是后期绑定的,在运行时动态绑定方法属于子类还是父类。但对于static方法和final方法则是例外,这是由于声明了static或是final的方法不能被继承,因为在编译阶段就可以确定他们的值。需要说明的是,private声明的方法和成员变量不能被子类继承,所有的private方法都被隐式的指定为final的。

除了final、static、private和构造方法(构造器实际上是static方法,只是该static声明是隐式的)是静态绑定外,其他的方法全部为动态绑定。

再来看看下面这段代码:

public class Main {
    public static void main(String[] args) {
        Father father = new Son();
        System.out.println(father.age);
        father.name();
        father.age();
    }
}
class Father{
    public int age = 50;
    public static void name(){
        System.out.println("Father name");
    }
    public void age(){
        System.out.println("Father age is " + age);
    }
}
class Son extends Father{
    public int age = 20;
    public static void name(){
        System.out.println("Son name");
    }
    @Override
    public void age(){
        System.out.println("Son age is " + age);
    }
}

输出结果如下:

50
Father name
Son age is 20

解释如下:
1. 当执行main方法中的Father father = new Son()发生了向上转型,在编译期间father就是个Father对象,编译器并不知道father对象是Son类型,这是在运行期间由JVM判断。

  1. 通过以上可知成员变量、final、static、构造器都是静态绑定,所以在处理father.age并不是采用运行时绑定,而是直接静态绑定,即调用father类中的age成员变量。

  2. 在调用father.name()时,由于这是个static方法,所以仍然采用静态绑定,仍调用father类中的name()方法。

  3. 在调用father.age()时,采用动态绑定,此时father会被解析成它实际的对象,即Son对象,因此实际调用的是Son类中的age()方法。

如果我们现在想要调用子类的成员变量age该怎么办呢?最简单的方法就是将成员变量封装成getter形式。

class Father{
    public int age = 50;
    ...
    public int getAge(){
        return age;
    }
}
class Son extends Father{
    public int age = 20;
    ...
    @Override
    public int getAge() {
        return age;
    }
}

此时通过father.getAge()获取到的结果就是子类中的成员变量age。

还是上面这段代码,我们如果去掉子类中重写的age()方法,那么father.age()的打印结果会是怎样的呢?

打印出来的结果如下:

Father age is 50

这是由于在进行动态绑定时,JVM会先调用子类重写的age()方法,如果没有找到,就是向上转型去父类中寻找。

部分代码参考源于:https://blog.csdn.net/lingzhm/article/details/44116091

猜你喜欢

转载自blog.csdn.net/swpu_ocean/article/details/82113658