Binding
Binding指的是将一个方法调用关联到一个方法体的过程。如果binding在程序运行之前执行(通过编译器和链接器)就叫做early binding,比如C语言,只有early binding。如果binding发生在程序运行时就叫late binding,也叫动态binding或者runtime binding。
Java中的binding
java中的方法除了final,static和private的都是用late binding。
错误之一:override private method
先看代码:
public class Test6 { public static void main(String args[]){ Test6 t = new SubTest6(); t.test(); } private void test(){ System.out.println("father"); } } class SubTest6 extends Test6{ public void test(){ System.out.println("son"); } }程序输出“father”,可见是调用的Test6的test()方法。
当多态发生时,如果子类中没有重写父类引用调用的方法,则使用父类的。要注意不要认为只会在子类中寻找,如果在子类中找不到重写的时会去父类中找的。
public class Test6 { public static void main(String args[]){ Test6 t = new SubTest6(); t.test(); t.test1();//wrong } private void test(){ System.out.println("father"); } } class SubTest6 extends Test6{ public void test(){ System.out.println("son"); } public void test1(){ } }上面这段代码,当t.test1()时会报错, 编译器在确定某个方法能不能调用时会根据引用的类型,而不是实际指向的类型,因为编译器不能确定运行时实际指向的对象类型,因此会报错。
错误之二:多态的使用field和static methods
看代码:
public class Test7 { public int age = 0; public void printAge(){ System.out.println("Test7 is "+ age + " years old."); } public static void main(String args[]){ Test7 test = new SubTest7(); System.out.println(test.age); test.printAge(); SubTest7 test2 = new SubTest7(); System.out.println(test2.age); test2.printAge(); test2.printSuperAge(); } } class SubTest7 extends Test7{ public int age = 1; public void printAge(){ System.out.println("SubTest7 is "+ age + " years old."); } public void printSuperAge(){ System.out.println("Test7 is "+ super.age + " years old."); } }输出:
public class Test8 { public static void main(String args[]){ StaticSuper staticSuper = new StaticSub(); System.out.println(staticSuper.staticGet()); System.out.println(staticSuper.dynamicGet()); StaticSub staticSub = new StaticSub(); System.out.println(staticSub.staticGet()); System.out.println(staticSub.dynamicGet()); } } class StaticSuper{ public static String staticGet(){ return "Base staticGet()"; } public String dynamicGet(){ return "Base dynamicGet()"; } } class StaticSub extends StaticSuper{ public static String staticGet(){ return "Derived staticGet()"; } public String dynamicGet(){ return "Derived dynamicGet()"; } }输出:
Base staticGet()
Derived dynamicGet()
Derived staticGet()
Derived dynamicGet()
可见,static方法没有表现出多态特性。因为static方法是跟类关联的,而不是跟对象关联的。既然不跟对象关联,当然也就不会在运行时绑定,而是early binding,由编译器去做。
强调一下SuperObject so = new SubObject()的原理
当调用so的方法时,编译器还是检查so的类型SuperObject中是否有这个方法,如果没有,编译错误;如果是private的或final的或static的,则调用父类本身的,在编译阶段就确定下来了;如果是其它的,则在编译阶段不确定下来,在执行时动态的binding,看看具体指向的类型是否重写了这个方法,如果没有则还是调用父类的,否则调用具体的子类的。
当谈so调用的方法只能是父类中声明过的,父类规定了可以调用哪些方法,而子类只有重写父类的方法才有机会表现出不同的行为,如果子类中实现了一个父类中没有的方法,则so不会调用该方法,否则编译出错,虽然运行时指向的对象是子类的,也不行。
构造方法中的方法多态行为
看例子吧:
public class TestSomething { public static void main(String args[]){ new SubClass(); } } class BaseClass{ public void test(){ System.out.println("Base->test"); } public BaseClass(){ System.out.println("base con"); this.test(); test(); } } class SubClass extends BaseClass{ private int age; public void test(){ System.out.println("Sub->test"); System.out.println("age="+age); } public SubClass(){ System.out.println("Sub Con"); this.age=10; test(); } }输出:
base con
Sub->test
age=0
Sub->test
age=0
Sub Con
Sub->test
age=10
可见在父类的构造方法中调用重写的方法会表现出多态的行为,不论是this.test()还是test()都调用的子类的test()方法。因为我们new的是子类,子类只是在内部包了一个父类,调用父类构造函数的时候无论加不加this,都会按照多态的规则去找,因为子类重写了test,所以会调用子类的。
当然,如果是static或final或private的则调用父类的。在构造方法中调用其它方法产生的多态是不提倡的,因为子类还没有初始化。
返回值的多态
javase5之后支持返回值的多态,也就是说子类重写的方法可以返回原来方法返回的类型的子类。
看例子吧:
public class TestReturnType { public static void main(String args[]){ Base base = new Sub(); System.out.println(base.test());//cat } } class Base{ public Animals test(){ return new Animals(); } } class Sub extends Base{ public Cats test(){ return new Cats(); } } class Animals{ public String toString(){ return "animal"; } } class Cats extends Animals{ public String toString(){ return "cat"; } }
但是参数必须一样,重写的方法的参数不能是原来方法参数的子类型,否则就是overload,不是override。
public class TestParams { public static void main(String args[]){ BaseC bc = new SubC(); bc.test(new Son()); } } class BaseC{ public void test(Father f){ System.out.println("Father"); } } class SubC extends BaseC{ public void test(Son s){ System.out.println("Son"); } } class Father{ } class Son extends Father{ }输出“Father”,说明调用的是继承自父类的test方法,子类还有另外一个test方法,两个方法不是override关系,是overload关系,所以多态没体现出来。
“is a”和"is like a"
"is a"指的是子类只有父类的接口,没有额外的。如果有额外的叫做“is like a”。子类中额外的接口是不能由父类访问的,因此upcast之后不能访问子类额外的方法。
如果我们需要访问,就要downcast。在java中每个cast都会被检查,运行时检查downcast的类型对不对,这叫做runtime type information(RTTI)。如果不对,则抛出“ClassCastException”。
public class TestDownCasting { public static void main(String args[]){ Super1 s = new Sub1(); s.foo();//wrong ((Sub1)s).foo(); } } class Super1{ public void go(){ System.out.println("go"); } } class Sub1 extends Super1{ public void go(){ System.out.println("sub go"); } public void foo(){ System.out.println("foo"); } }只有先upcast后downcast的情况才有可能正确,否则报出ClassCastException。