基本概念
动态绑定和静态绑定是Java中两个重要的概念。首先思考这么一个问题,当一个类中存在方法名相同但参数不同(重载)的函数或同一类层次结构下同一名称的方法(重写),程序在执行的时候该如何辨别区分呢?这里就需要用到Java中的动态绑定和静态绑定来解决。那么什么是动态绑定和静态绑定呢?
绑定:指一个方法的调用与方法所在的类关联起来
静态绑定:方法在程序编译期进行绑定
动态绑定:方法在程序运行时根据具体对象的类型进行绑定
静态绑定 VS 动态绑定
静态绑定发生在编译期(Compile time),而动态绑定发生在运行时(Runtime)
private, final and static方法和变量使用静态绑定,而虚函数(virtual methods)则会根据运行时的具体对象进行绑定
静态绑定使用的是类信息,而动态绑定使用的是对象信息
重载方法(overloaded methods)使用的是静态绑定,而重写方法(overridden methods)使用的是动态绑定
虚函数:在Java语言中, 所有的方法默认都是”虚函数”。只有以关键字 final 标记的方法才是非虚函数。虚函数是面向对象编程实现多态的基本手段。
静态绑定示例
例1:
class A {
public void hello(String str) {
System.out.println("a String instance in A");
}
public void hello(Object str) {
System.out.println("a Object instance in A");
}
}
public class BindingDemo {
public static void main(String args[]) {
String str = "";
Object obj = "";
A a = new A();
a.hello(str);
a.hello(obj);
}
}
输出结果:
a String instance in A
a Object instance in A
分析:可以看出,在A中有两个同名但参数不同的方法(重载),在调用方法a.hello()时,程序会自动根据输入的参数类型来选择具体调用哪个方法,其后的原理就是静态绑定,即在编译期根据参数类型进行静态绑定。
动态绑定实例
例2:
class A {
public void hello() {
System.out.println("hello in A");
}
}
class B extends A {
@Override
public void hello() {
System.out.println("hello in B");
}
}
public class StaticBindingDemo {
public static void main(String args[]) {
A a = new B();
a.hello();
}
}
输出结果:
hello in B
分析:B继承于A,并重写了A中的方法hello(),从结果可知,a.hello()调用的不是A中的hello(),而是B中的hello(),这正是因为程序在运行时发生了动态绑定。同时也说明了重写方法使用的是动态绑定。
相关问题探讨
Stack Overflow上探讨了这么一个问题,具体如下:
class A
{
int x = 5;
}
class B extends A
{
int x = 6;
}
class SubCovariantTest extends CovariantTest
{
public B getObject()
{
System.out.println("sub getobj");
return new B();
}
}
public class CovariantTest {
public A getObject()
{
System.out.println("ct getobj");
return new A();
}
public static void main(String[]args)
{
CovariantTest c1 = new SubCovariantTest();
System.out.println(c1.getObject().x);
}
}
输出结果:
sub getobj
5
看到这样的输出结果,很多人可能会很纳闷,从第一个输出结果sub getobj可以看出调用的是B对象,但从x输出值上看,却是A对象,这是怎么一回事?!先不急着解决这个问题,我们先来看下下面这段程序:
class A
{
int x = 5;
public void doSomething() {
System.out.println("A.doSomething()");
}
}
class B extends A
{
int x = 6;
public void doSomething() {
System.out.println("B.doSomething()");
}
}
public class Main {
public static void main(String args[]) {
A a=new B();
System.out.println(a.x);
a.doSomething();
}
}
输出结果:
5
B.doSomething()
分析:根据前面讲解的静态绑定和动态绑定知识,我们可以清楚的知道,在调用方法a.doSomething()时发生的是动态绑定,故输出结果是B.doSomething(),但当调用a.x时,又发生了些什么呢?! 这里,我们需要了解一个概念:在Java中,变量(fields)没有多态这个说法,只有方法(methods)有。即变量不存在动态绑定,它是在编译期进行绑定。 在编译期,变量x和方法doSomething()都是和A类进行绑定的,而在程序运行时,doSomething()方法会根据运行的对象类型进行动态绑定,即绑定B类,从而执行的是B类中的doSomething()方法。而x变量不进行动态绑定,所以执行的还是A类中的x变量。 再回到最开始的那个问题:在编译期,c1绑定的是类CovariantTest,则c1.getObject()返回的是类A,故c1.getObject().x指的是A.x;在运行时,c1.getObject()则会根据运行的对象进行动态绑定,即绑定类SubCovariantTest,而c1.getObject().x不会在进行动态绑定,所以x的值还是类A中的x的值。
有人可能对变量x到底是属于哪个类还是很疑惑,那么我们再通过一个例子来进行说明:虽然类A和类B都有一个变量x,但是它们并不是同一个x,既然这样,我们就可以用不同的名称表示这两个变量,将类B中的变量x改为y,具体如下
class A {
int x = 5;
}
class B extends A {
int y = 6;
}
class SubCovariantTest extends CovariantTest {
public B getObject() {
System.out.println("sub getobj");
return new B();
}
}
public class CovariantTest {
public A getObject() {
System.out.println("ct getobj");
return new A();
}
public static void main(String[] args) throws java.lang.Exception {
CovariantTest c1 = new SubCovariantTest();
System.out.println(c1.getObject().y);
}
}
在编译期,如果c1.getObject()返回的是类B,那么上面程序完全没问题,但实际上,会出现以下编译错误。说明,在编译期,c1.getObject()返回的是类A,c1.getObject().x指的是A.x,而且在运行期间不会发生改变。
上面的问题解决了,可能有人还会继续问,那么怎样才能达到变量被重写的效果?我们可以这样实现
class A {
int x = 5;
public int getX() {
return x;
}
public void doSomething() {
System.out.println("A.doSomething()");
}
}
class B extends A {
int x = 6;
public int getX() {
return x;
}
public void doSomething() {
System.out.println("B.doSomething()");
}
}
public class Main {
public static void main(String args[]) {
A a = new B();
System.out.println(a.getX());
a.doSomething();
}
}
输出结果:
6
B.doSomething()
显然,正是利用方法的动态绑定达到变量被重写的效果。
参考资料