其实在看Java编程思想之前,我对多态并没有什么概念,也没觉得多态有多重要。但是看完之后才发现,多态的存在能够使得继承更加灵活。甚至可以说,如果没有多态,子类的向上转型会更困难,父类方法的调用也会更加复杂。
多态的存在,可以更好的消除类之间的耦合关系,更方便的构建可扩展的程序,使得从同一父类继承的子类表现出一种共性。从这一点上说,称多态是OOP中即数据抽象和继承之后的第三种基本特征,实在是名副其实
向上转型
假设有一个父类A,一个从A继承的子类B。那么当存在一个接受A对象的方法时,该方法也能接受B或者其他继承自A的对象类
// A.class
public class A {
public void show(){
System.out.println("This show from A") ;
}
}
// B.class
public class B extends A {
public void show(){
System.out.println("This show from B") ;
}
public static void test(A item){
item.show() ;
}
public static void main(String[] args){
A a = new A() ;
A b = new B() ; // 注意这里
test(a) ;
test(b) ;
}
}
// =======Console output========
//This show from A
//This show from B
这样做的合理性在于B是继承自A,所以A中的接口在B中一定存在,所以在对B执行test的过程中,并不会出现问题。这样就能将A,B的功能切分开来,可以在A中实现那些比较通用的方法,而在B中实现通用性不那么强的方法。如果没有多态,就要写2个test函数,分别接受A,B生成的对象,这不但繁琐,而且也不便于同步。
动态绑定
动态绑定又称为后期绑定或运行时绑定。也就是说,声明为A的变量其实不一定真属于A类,也有可能属于B或者其他A的子类。而这些在静态编译的时候编译器是不知道的,所以称为动态绑定。
在上面那个例子中,虽然b在声明变量是是A,但如果调用b.show(), 就会发现其实是调用的B.show() 函数。 如果使用getClass函数,就会发现其实b是B类型的变量。
但是!只有普通方法的调用可以是多态的,对于域或者静态方法,是不会使用多态的。还有对于私有方法,因为无法继承,所以即使子类中有相同名字的方法,也不属于从父类继承所得,所以还是会使用父类中的对应方法。
//A.class
public class A {
public int field = 0 ;
public void show(){
System.out.println(this.getClass().getSimpleName()) ;
}
public void getField(){
System.out.println("The field value is "+this.field);
}
}
//B.class
public class B extends A{
public int field = 1 ;
public void show(){
System.out.println(this.getClass().getSimpleName()) ;
}
public static void test(A item){
item.show() ;
}
public void getField(){
System.out.println("The field value is "+this.field);
}
public static void main(String[] args){
A a = new A() ;
A b = new B() ;
System.out.println("=====a======");
System.out.println("this.field "+a.field);
a.getField();
System.out.println("=====b======");
System.out.println("this.field "+b.field);
b.getField();
}
}
// result
//=====a======
//this.field 0
//The field value is 0
//=====b======
//this.field 0
//The field value is 1
可以看到,如果直接调用b.field,返回的是A类中的field值0。即没有发生动态绑定。而如果调用 b.getField() , 则会输出1,也就是此时发生了动态绑定。
构造时发生多态的问题
这是一个潜在的问题,即如果B继承自A,A,B都有相同的方法假设为test,且A在初始化时调用了test,那么由于多态的存在,此时会调用B.test,如果在test调用了变量,那么就会发生问题。
A在初始化时,B是没有初始化的。当A调用test时,由于多态,会访问B中的域,这就是问题所在。 举例如下
// A.class
public class A {
public void test(){
System.out.println("test from A");
}
A(){
test(); // 此时B.field = 0
}
}
// B.class
public class B extends A {
private int value = 1 ;
B(){
//(调用构造函数前value被赋值为1)
test(); // 此时B.field = 1
this.value = 5 ;
test(); // 此时B.field = 5 (value被赋值为5)
}
public void test(){
System.out.println("test from B, value = "+this.value);
}
public static void main(String[] args){
B b = new B() ;
}
}
// output
//test from B, value = 0
//test from B, value = 1
//test from B, value = 5
需要养成的编程习惯
- 变量尽量设为private,转而使用public方法来调用
- 尽量不要对基类的域和导出类的域赋予相同的名字
- 不要滥用继承,尽量使用组合和代理的方法来实现功能
- 注意在基类构造函数中调用子类同名函数的情况