首先介绍一下绑定的概念:绑定指的是一个方法的调用与方法所在的类(方法主体)关联起来。对java来说,绑定分为静态绑定(前期绑定)和动态绑定(后期绑定)。
静态绑定(前期绑定)是指在程序执行前方法已经被绑定,可以理解为程序编译期的绑定。只有final,static,private和构造方法是前期绑定,因为private方法是不能被继承的,所以和final方法有相同的效果,也可以说是用来有效的关闭动态绑定的,而static方法在编译时就分配了方法区的内存,所以都是静态绑定。也许还不太好理解,但是理解了动态绑定也就理解了静态绑定;
动态绑定(后期绑定)即运行时绑定,指在运行期间判断对象的类型,并调用适当的方法。也就是说,编译器不知道对象的类型,但方法调用机制能自己去调查,找到正确的方法主体。
动态绑定的过程如下:
1.编译器检查对象的声明类型和方法名,然后的实际类型的方法表。加入A类中对象a调用fun(obj)方法,那么编译器会列出所有A类中fun()方法,和A类父类中所有fun()方法以及A父类的父类的所有方法以及更上层的。
2.编译器检查方法调用中提供的参数类型,如果在所有fun()方法中有一个参数类型和调用提供的参数类型最为匹配,那么就调用这个方法。这个过程叫做“重载解析”。
3.当程序运行并且使用动态绑定调用方法时,虚拟机必须调用同a所指向的对象的实际类型相匹配的方法,如果A类中有合适的方法则调用,否则在A的父类中找以及更上层。
光说不练嘴把式,废话说了这么多,咱也来点实际的! Look:
父类
public class Father{ public void f1(Object o){ System.out.println("Object of Father"); } public void f1(double[] d){ System.out.println("double[] of Father"); } }
子类
public class Son extends Father{ public void fi(Object o){ System.out.println("Object of Son"); } }
测试类
public class Demo{ public static void main(String[] args) { double []d = {1.2,3.5}; Father f = new Son(); Father father = new Father(); Son son = new Son(); f.f1(d);//输出double[] of Father father.f1(d);//输出double[] of Father son.f1(d);//输出double[] of Father } }
f.f1(d);输出double[] of Father,这里既是动态绑定也是上转型,下面再做介绍。
father.f1(d);输出double[] of Father,这个是肯定的,不用介绍。
son.f1(d);输出double[] of Father,这是为什么呢,Son类里面有f1(Object o)方法,传进去的double数组也适合。这个就解释了动态绑定,调用f1()方法前,先列出Son及Son的父类Father中所有f1()方法,然后看哪个合适,其实Son和Father中的f1()方法都合适,那么JVM就会选出最合适的一个。我们可以看出不管传入什么参数,f1(Object o)方法都合适,但是f1(double[] d)不一定适合,可以说第二个方法精度更高,所以在传入一个double数组时,第二个方法更合适。我们都知道继承后,如果对象没在自己类找到方法会在父类中找,找到的话就调用父类中的方法。所以son虽然是Son类对象,但是父类Father中有更合适的方法,当然就会调用父类中更合适的方法。
如果在本类和父类及更上层都找不到合适的方法,则编译阶段就无法通过。
下面来介绍一下向上转型:
向上转型就是创建父类引用,然后将子类对象赋给这个父类引用,再调用方法,如果子类重写了该方法,那么执行子类方法,如 果子类没有重写,那么执行父类方法。就像上面例子,f在子类中没有找到该合适方法,那么就执行父类合适的方法,如果父类中没有合适方法,那么就执行父类的父类及上层的方法。
1.虽然Father f 这里创建的是父类的引用,但是给其赋了子类对象,所以f指的是子类对象。
2.变量是可以被继承的,但是不能被覆盖(重写),如:
父类中有:public String str = "父类";
子类中有:public String str = "子类";
测试类中有:Father f = new Son(); System.out.println(f.str);
输出的当然是"父类",这会是很多人一下迷茫了,上面明明说了f是子类的对象,那么f.str当然调用的是子类的变量了,这里调用的为什么是父类的变量呢?接着往下看:
父类:
public class Person { public String temp = "人类临时变量"; public void say(){ System.out.println("人类说话方法"); } public void eat(){ System.out.println("人类吃饭方法"); } }
子类:
public class Student extends Person{ public String temp = "学生临时变量"; public String only = "学生独有变量"; public void say(){ System.out.println("学生说话方法"); } public void eat(){ System.out.println("学生吃饭方法"); } public void study(){ System.out.println("学生学习方法"); } }
测试类
public class Test { public static void main(String [] args){ Person p = new Student(); p.say();//输出学生说话方法 p.eat();//输出学生吃饭方法 p.study(); System.out.println(p.temp);//输出人类临时变量 System.out.println(p.only); } }
看上去这段代码毫无问题,其实呢第六行会报错:"The method study() is undefined for the type Person";第八行会报错:"only cannot be resolved or is not a field"。
向上转型会使子类对象遗失和父类不同的属性和方法,所以会报错。子类中的属性是子类自己独有的属性,因为属性是不能被重写的(上面说了),所以在上转型中,子类丢了这个属性,所以调用的当然是父类的属性了。而方法呢,子类重写了某方法,在上转型中,对象调用该方法,当然是执行的子类中重写的方法,如果没重写,那么肯定是执行父类的方法。
那么既然会丢失子类独有的属性和方法,这不是违背了多态么?为什么还要用呢?不用上转型,就丧失了面向抽象的编程特色降低了可扩展性(这个我还不是很清楚),其实不仅仅如此,向上转型还可以减轻编程工作量,请看下面的例子:
父类
public class Monitor{//显示器类 public void displayText() {} public void displayGraphics() {} }
子类1
public class LCDMonitor extends Monitor {//液晶显示器类 public void displayText() { System.out.println("LCD display text"); } public void displayGraphics() { System.out.println("LCD display graphics"); } }
子类2
public class CRTMonitor extends Monitor {//阴极射线管显示器类 public void displayText() { System.out.println("CRT display text"); } public void displayGraphics() { System.out.println("CRT display graphics"); } }
如果没有上转型
public class MyMonitor { public static void main(String[] args) { run(new LCDMonitor()); run(new CRTMonitor()); } public static void run(LCDMonitor monitor) { monitor.displayText(); monitor.displayGraphics(); } public static void run(CRTMonitor monitor) { monitor.displayText(); monitor.displayGraphics(); } }
你会发现上述代码有很多重复代码,而且也不易维护。。。。如果有了上转型呢:
public class MyMonitor { public static void main(String[] args){ run(new LCDMonitor());//向上转型 run(new CRTMonitor());//向上转型 } public static void run(Monitor monitor){//父类实例作为参数 monitor.displayText(); monitor.displayGraphics(); } }
由此可见向上转型的强大吧。。。嘿嘿嘿
如果说要用子类独有的属性和方法,那当然创建子类对象,像前面的例子:创建的是Son son = new Son(); 那么要调用父类中继承来的属性,就用super.属性,这样就得到的父类中的属性。如果子类没重写父类方法,当调用的时候,自动调用的父类中的该方法,如果重写了,用super.方法,调用父类中的该方法。