前言
Java的生命周期包括加载、校验、准备、解析、初始化、使用、卸载这五个阶段,其中除了解析其他都是顺序执行的,但是解析有可能发生在初始化之后,具体是为了支持Java语言的动态绑定,这里我们就详细介绍一下Java的动态绑定以及与之对应的静态绑定
概念
绑定:一个方法的调用与方法所在的类关联起来。Java中的绑定分为静态绑定和动态绑定,又被称作前期绑定和后期绑定。
静态绑定:(final、static、private)在程序执行前已经被绑定,也就是说在编译过程中就已经知道这个方法是哪个类的方法,此时由编译器获取其他连接程序实现。
动态绑定:是在程序运行的时候判断调用哪个类的方法,一般用多态就会用到动态绑定。在程序编译器不知道要调用哪个方法,只有当程序运行起来的时候才发现原来是调用这个
区别比较
-
静态绑定发生在编译时期,动态绑定发生在运行时
-
使用private或static或final修饰的变量或者方法,使用静态绑定。而虚方法(可以被子类重写的方法)则会根据运行时的对象进行动态绑定
-
静态绑定使用类信息来完成,而动态绑定则需要使用对象信息来完成
-
重载(Overload)的方法使用静态绑定完成,而重写(Override)的方法则使用动态绑定完成
Java中静态绑定的三种类型的分析:
- private:首先一点它不能被继承,既然不能被继承那么就没办法通过它子类的对象来调用,而只能通过这个类自身的对象来调用。因此就可以说private方法和定义这个方法的类绑定在了一起
- static:static方法可以被子类继承,但是不能被子类重写(覆盖),但是可以被子类隐藏。(这里意思是说如果父类里有一个static方法,它的子类里如果没有对应的方法,那么当子类对象调用这个方法时就会使用父类中的方法。而如果子类中定义了相同的方法,则会调用子类的中定义的方法。唯一的不同就是,当子类对象上转型为父类对象时,不论子类中有没有定义这个静态方法,该对象都会使用父类中的静态方法。因此这里说静态方法可以被隐藏而不能被覆盖。这与子类隐藏父类中的成员变量是一样的。隐藏和覆盖的区别在于,子类对象转换成父类对象后,能够访问父类被隐藏的变量和方法,而不能访问父类被覆盖的方法)
- final:final方法虽然可以被继承,但是不能被重写(覆盖),虽然子类对象可以调用,但是调用的都是父类中的final方法(因此可以看出当类中的方法声明为final的时候,一是为了防止方法被覆盖,而是为了有效关闭java的动态绑定)
动态绑定分析:
若一种语言实现了后期绑定,同时必须提供一些机制,可在运行期间判断对象的类型,并分别调用适当的方法。调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。动态绑定的过程分为以下几个环节:
- 编译器查看对象的声明类型和方法名
- 编译器查看调用方法时提供的参数类型。例如x.f("hello"),编译器将会挑选f(String),而不是f(int),由于存在类型转换(int转换为double),所以可能会更复杂。如果编译器没找到参数类型匹配的方法,或者发现有多个方法与之匹配,就会报告一个错误。至此,编译器获得了需要调用的方法名字和参数类型
- 采用动态绑定调用方法的时候,一定调用与所引用对象的实际类型最合适的类的方法。如果x的实际类型是D,它是C类的子类,如果D定义了一个方法f(String),就直接调用它,否则将在D类的超类中寻找f(String),以此类推
每次调用方法都要进行搜索,时间开销太大,所以虚拟机预先为每个类创建一个方法表(method table),其中列出了所有方法的签名和实际调用的方法。这样在调用方法的时候,只需要查找这个表即可
下面举个例子来分析
public class Child extends Base{
public String name = "child";
@Override
public String getName() {
return name;
}
public String method() {
return "child method";
}
}
public class Child extends Base{
public String name = "child";
@Override
public String getName() {
return name;
}
public String method() {
return "child method";
}
}
public class Main {
public static void main(String[] args) {
Base base = new Child();
System.out.println(base.method());
System.out.println(base.name);
System.out.println(base.getName());
}
}
上面有一个基类有一个子类,子类隐式重写了基类的method方法,他们都有name属性,在测试的代码中使用多态进行向上转型,将父类对象指向子类对象,我们下面打印出这几个结果,并依次分析
child method
base
child
我们看到一个是调用method,这里子类是隐式的重写了基类的方法,这里肯定是使用动态绑定,直接使用子类的方法,所以打印的是“child method”。第二个结果是直接打印对象的属性,在Java中只会对类的方法进行动态绑定,不会对属性进行动态绑定,所以这里打印出来的就是基类的属性。最后一个其实一样是动态绑定调用的子类的属性,只不过这里是为了突出在子类的方法中使用的是子类的属性。
Java因为什么对属性要采取静态的绑定方法。这是因为静态绑定是有很多的好处,它可以让我们在编译期就发现程序中的错误,而不是在运行期。这样就可以提高程序的运行效率!而对方法采取动态绑定是为了实现多态,多态是java的一大特色。多态也是面向对象的关键技术之一,所以java是以效率为代价来实现多态这是很值得的