Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型有声明该变量时使用的类型决定,运行时类型由实际赋给改变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态。
package polymorphism;
class Base {
public int book=6;
public void baseinfo(){
System.out.println("父类的普通方法!");
}
public void test(){
System.out.println("父类的被覆盖的方法");
}
}
public class Sub extends Base{
public String book="Java开发";
public void test(){
System.out.println("子类的覆盖父类的方法");
}
public void subinfo(){
System.out.println("子类的普通方法");
}
public static void main(String[] args) {
Base bc = new Base();
System.out.println(bc.book);
bc.baseinfo();
bc.test();
System.out.println("--------------------");
Sub sc = new Sub();
System.out.println(sc.book);
sc.baseinfo();
sc.test();
sc.subinfo();
System.out.println("--------------------");
Base bs = new Sub();
System.out.println(bs.book);
bs.baseinfo();
bs.test();
//因为bs的编译时类型是Base
//Base类没有提供subinfo()方法,所以下面代码编译时会出现错误
//bs.subinfo();
}
}
运行结果:
6
父类的普通方法!
父类的被覆盖的方法
--------------------
Java开发
父类的普通方法!
子类的覆盖父类的方法
子类的普通方法
--------------------
6
父类的普通方法!
子类的覆盖父类的方法
上面程序的main()显式创建了三个引用变量,对于前两个引用变量bc和sc,它们编译时类型和运行时类型完全相同,因此调用他们的成员变量和方法非常正常,完全没有任何问题。但第三个引用变量bs则比较特殊,它的编译时类型是Base,而运行时类型是Sub,当调用该变量的test()方法(Base类中定义了该方法,子类Sub覆盖了父类的该方法)时,实际执行的是Sub类中的覆盖后的test()方法,这就可能出现多态了。
因为子类其实是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无需任何类型转换,或者被称为向上转型(upcasting),向上转型由系统自动完成。
当把一个子类对象直接赋给父类引用变量时,例如上面的 Base bs = new Sub(); 这个bs引用变量的编译时类型是Base,而运行时是Sub,当运行时调用该引用变量的方法时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就可能出现:相同类型的变量、调用同一个方法时呈现出多种不同的行为特征,这就是多态。
上面的main()方法注释了bs.subinfo();这行代码会在编译时发生错误。虽然bs引用变量实际上包含subinfo()方法(例如,可以通过反射来执行该方法),但因为它的编译时类型为Base,因此编译是无法调用subinfo()方法。
与方法不同的是,对象的实例变量则不具备多态性,比如上面的bs引用变量,程序中输出它的book实例变量时,并不是输出Sub类里定义的实例变量,而是输出Base类的实例变量。
注意:
引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的的方法。因此,编写Java代码时,引用变量只能调用声明该变量时所用类里包含的方法。例如,通过Object p=new Person()定义了一个变量p,则这个p只能调用Object类的方法(子类重写父类方法时,调用子类重写后的方法),而不能调用Person类里定义的方法。
通过引用变量来访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。