Java:前期绑定和后期绑定

概念

程序绑定

  绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对Java来说,绑定分为静态绑定和动态绑定;或者叫做前期绑定和后期绑定。

静态绑定

  编译器在编译的时候就能解析的绑定叫做静态绑定或者前期绑定,Java当中的方法只有final,static和private方法是前期绑定。

为什么static,final和private方法总是静态绑定的?
  静态绑定在性能方面更好(不需要额外开销)。编译器知道这些方法不能被重写并且一直都可以被本地类的对象访问。因此编译器很轻松就能确定类的对象(肯定是本地类),所以被这种方法绑定是静态的。
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class NewClass {
public static class superclass {
static void print(){
System.out.println("print in superclass");
}
}

public static class subclass extends superclass {
static void print() {
System.out.println("print in subclass.");
}
}

public static void main(String[] args) {
superclass A = new superclass();
superclass B = new subclass(); // 如果把此处的引用改成subclass,输出的就是"print in subclass"
A.print();
B.print();
}
}

输出:

1
2
print in superclass
print in superclass

分析:

  • 我们创建了一个subclass的对象和一个superclass的对象,并且引用了superclass
  • superclass的print方法是静态的,编译器知道它不会在子类中被重写,因此编译器在编译期间知道要调用哪种打印方法,因此不存在歧义

动态绑定

  在动态绑定中编译器不决定调用的方法,因为对象无法知道它是属于方法所在的那个类,还是属于那个类的导出类。重写是动态绑定的一个完美例子。在重写中子类和父类都有同样的方法。例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class NewClass {
// 和上一个代码的区别在于print()方法不再static了
public static class superclass {
void print(){
System.out.println("print in superclass");
}
}

public static class subclass extends superclass {
void print() {
System.out.println("print in subclass");
}
}

public static void main(String[] args) {
superclass A = new superclass();
superclass B = new subclass();
A.print();
B.print();
}
}

输出:

1
2
print in superclass
print in subclass

分析:

  • 这个代码中的方法不是静态的
  • 编译过程中,编译器不知道哪一个print方法被调用了,因为编译器只通过引用变量而不是对象类型来引用,于是绑定会被延迟到运行时而且因此对应版本的print会被根据对象类型调用

重点

  • private, final和静态成员(方法和变量)用静态绑定,然而对虚拟方法(Java方法默认为虚拟方法)绑定是在运行时基于运行时对象完成的
  • 静态绑定使用类型信息用于绑定然而动态绑定用对象来解析绑定
  • 重载方法通过静态绑定被解析(当有很多个同名方法时决定调用哪一个)然而重写方法用动态绑定

Q:
以下代码中发生了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal
{
void eat()
{
System.out.println("Animal is eating");
}
}

class Dog extends Animal
{
void eat()
{
System.out.println("Dog is eating");
}
}

public static void main(String args[])
{
Animal a=new Animal();
a.eat();
}

A:
这个例子是一个动态绑定,因为a的类型实在运行时被确定的,于是相似的方法被调用了
现在假设有以下两个方法:

1
2
3
4
5
6
public static void callEat(Animal animal) {
System.out.println("Animal is eating");
}
public static void callEat(Dog dog) {
System.out.println("Dog is eating");
}

把main方法改成

1
2
3
4
5
public static void main(String args[])
{
Animal a = new Dog();
callEat(a);
}

输出会是Animal is eating,因为对callEat的调用是静态绑定,编译器只知道a是一种Animal。


参考

Geeksforgeeks
stackoverflow