Polymorphism

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。

你可能感兴趣的:(java)