Java中动态绑定和静态绑定小结

下面是我在学习Java SE的时候,写的一个有关动态绑定和静态绑定的例子:

// 方法是动态绑定的,属性是静态绑定的
public class TestDemo {
  public static void main(String[] args) {
    Koo koo = new Koo();
    Foo foo = koo;
    System.out.println(koo.a + ", " + koo.getA());// 输出:2, 2
    System.out.println(foo.a + ", " + foo.getA());// 输出:1, 2
    boolean isFoo = koo instanceof Foo;
    boolean isKoo = koo instanceof Koo;
    System.out.println(isFoo + ", " + isKoo); // 输出:true, true
  }
}

// 定义一个类Foo
class Foo {
  int a = 1;

  public int getA() {
    return a;
  }
}

// 定义一个类Koo继承于Foo
class Koo extends Foo {
  int a = 2;

  public int getA() {
    return a;// 这里相当于是this.a,如果换成super.a,则标识访问父类a
  }
}

一个简陋的堆栈图

图1:一个简陋的堆栈图

上面代码的执行过程是:

Koo koo = new Koo(); // 堆中开辟一个KOO内存区,由于KOO继承与FOO,子类实例化的时候,先父类再子类,而且属性是静态绑定,所以,内存区中先有一个FOO的a,再有一个KOO的a,此时,a都没有赋值,父类和子类的属性都有内存区后,再一个个赋值,现在给Foo的a赋值1,Koo的a赋值2。栈中的koo指向堆中koo的位置,可以理解为栈中的koo存储的值是堆中koo的地址标识。

FOO foo = koo; // 把koo的的值赋给foo,那么foo也存储着堆中koo的地址标识,如上图1所示。

System.out.println(koo.a + ", " + koo.getA()); // koo.a的值为2,由于属性是静态绑定,koo.a的值仍然取得是Koo类中的;koo.getA()的值是2,调用自身的方法,取得koo中的a,为2。

System.out.println(foo.a + ", " + foo.getA()); // foo.a的值为1,由于属性是静态绑定,foo.a的值仍然是Foo类中的;foo.getA()的值是2,方法是动态绑定的,foo.getA()调用的是子类的getA()方法,取得的是子类的a的值。如果这里想要取得父类的a的值,那么可以用super.a。

总之,一句话,属性是静态绑定,方法是动态绑定。在开发的时候,建议都要符合JavaBean规范,比如类的属性定义成private,使用getter和setter来访问,而且,父类和子类不要有同名属性,同名方法没有关系,方法可以动态绑定,但是属性不建议重名。

class TestMain {
  public static void main(String[] args) {
    // 编译时确定该类实例能运行哪些方法,运行时确定该类实例运行方法时具体调用哪个(子类还是父类)方法
    B b = new B();
    // B中有两个show方法,现根据参数类型从show方法的方法签名中去完全匹配,找到最符合的show方法,
    // B中最符合的show方法是show(C c),所以调用show(C c)。如果b中只定义了show(B b),那么参数new C()会
    // 向上转型,那么最符合的show方法就是show(B b)。
    b.show(new C()); // 输出:C c
    A a = b;
    // A中只有一个show方法,即show(B b),传入参数new C(),由于A中不存在完全匹配该类型的show方法,所以向上
    // 转型,那么最符合的show方法就是show(B b)。由于此时a所表示的子类实例b中也有show(B b)方法,那么这里最终
    // 调用的会是子类实例b的show(B b)方法。
    a.show(new C()); // 输出:B b
  }
}

class A {
  public void show(B b) {
    System.out.println("A b");
  }
//  public void show(C c) {
//    System.out.println("A c");
//  }
}

class B extends A {
  public void show(B b) {
    System.out.println("B b");
  }
  public void show(C c) {
    System.out.println("C c");
  }
}

class C extends B {
  
}

java当中的方法只有final、static、private和构造方法是静态绑定(前期绑定、非运行时绑定)

(1)final方法虽然可以被继承,但是不能被重写,虽然子类对象可以调用,但是调用的都是父类的final方法。所以被定义为final的方法,可以有效的防止被重写,并有效的关闭动态绑定。

(2)private方法是私有的,无法被继承,只能通过该类自身的对象来调用,所以private方法无法动态绑定。

(3)static方法可以被继承,也可以被重写,但是它可以看成是类的方法,与具体类绑定在一起,static方法无法动态绑定,参见下面这个例子:

public class TestMain {
  public static void main(String[] args) {
    B b = new B();
    A a = b;
    // The static method should be accessed in a static way
    a.hello(); // 输出:hello A,但是不应该这样调用
    b.hello(); // 输出:hello B,但是不应该这样调用
    b.bye(); // 输出:bye,但是不应该这样调用
    // 
    A.hello(); // 输出:hello A
    B.hello(); // 输出:hello B
    B.bye(); // 输出:bye
  }
}

class A {
  public static void bye() {
    System.out.println("bye");
  }
  public static void hello() {
    System.out.println("hello A");
  }
}

class B extends A{
  public static void hello() {
    System.out.println("hello B");
  }
}

(4)构造方法是不能被继承的,子类的构造方法会默认先调用父类的无参构造(如果有指定super(....)的参数,那么会先调用父类的指定构造函数),构造函数无法动态绑定,参见下面这个例子:

public class TestMain {
  public static void main(String[] args) {
    A a = new B("b"); // 输出: A no arguments B
  }
}

class A {
  public A() {
    System.out.print(" A no arguments");
  }
  
  public A(String str) {
    System.out.print(" A");
  }
}

class B extends A{
  public B(String str) {
    // super(); // 默认会先调用父类的无参数构造
    // super("b"); // 会调用父类指定的构造函数
    System.out.print(" B");
  }
}

本文标题:Java中动态绑定和静态绑定小结

本文链接:http://yedward.net/?id=232

本文版权归作者所有,欢迎转载,转载请以文字链接的形式注明文章出处。

相关文章