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.");
}
}
输出:
0
SubTest7 is 1 years old.
1
SubTest7 is 1 years old.
Test7 is 0 years old.
可见,age字段并不是运行时确定的,而是编译时确定的,所有的字段都是编译时确定的。
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。